【问题标题】:Trying to understand a Haskell code example from the presentation "Why Do Monads Matter?"试图从演示文稿“Monads 为何重要?”中理解 Haskell 代码示例
【发布时间】:2014-03-28 16:58:44
【问题描述】:

我在看 presentation :“为什么 Monads 很重要?”

我简化了一个代码example,它可以编译并运行(请参见下文),但我仍然不明白它是如何工作的。

更准确地说:我不了解 composePref 函数。根据类型定义,它应该接受2个Ize类型的参数,并返回一个Ize类型的结果。 (Ize 在匈牙利语中的意思是“whatdoyoucallit”/thingy/某事。)

但是它需要三个参数(f g x),谁能解释一下composePref函数的工作原理以及f、g、x、y和c的类型是什么?

我不得不承认我是 Haskell 的初学者。也许我不明白在这种情况下柯里化是如何工作的?

module Dependence where
main = putStrLn (f "foo" cfg)
         where f = right `composePref` right `composePref` left
               cfg = 2

left :: Ize
left s = \i -> (repeatString i "< ") ++ s

right ::Ize
right s = \i -> s ++ (repeatString i " >")

repeatString :: Integer -> String -> String
repeatString i s = if (i <= 0)
                    then ""
                    else s ++ repeatString (i - 1) s

type Ize = String -> Integer -> String

composePref :: Ize -> Ize -> Ize
composePref f g x = \c -> let y =  (g x) c 
                          in       (f y) c

产生输出:

< < foo > > > >

【问题讨论】:

    标签: haskell monads


    【解决方案1】:

    您认为允许这种行为的是柯里化是正确的。如果我们查看Ize 的定义,它只是String -&gt; Integer -&gt; String 的类型同义词。如果我们将其插入到composePref 的类型签名中,我们会得到

    composePref :: (String -> Integer -> String) -> (String -> Integer -> String) -> (String -> Integer -> String)
    

    (我希望你现在明白为什么使用类型别名,它大大缩短了签名)。由于类型签名中的-&gt; 是右关联的,这意味着类似

    a -> b -> c -> d
    

    等价于

    a -> (b -> (c -> d))
    

    所以我们可以进一步简化签名(使用一些额外的类型别名,因为我不想全部输入)

    type I = Integer
    type S = String
    
    composePref :: (S -> I -> S) -> (S -> I -> S) -> S -> I -> S
    composePref f g x = \c -> ...
    

    然后是f :: (S -&gt; I -&gt; S)g :: (S -&gt; I -&gt; S)x :: S。我包括了那个labmda的开头,这样我就可以说c :: I。你实际上可以把这个函数写成:

    composePref :: Ize -> Ize -> Ize
    composePref f g x c = let y = (g x) c in (f y) c
    

    也相当于

    composePref f g x c = let y = g x c in f y c
    -- (map show) [1, 2, 3] === map show [1, 2, 3]
    

    或者

    composePref f g x c = f (g x c) c
    

    甚至

    composePref f g = \x c -> f (g x c) c
    

    这些都是composePref 的等价定义。我认为最后一个可能最清楚地表明它是一个接受两个函数并返回相同类型的新函数的函数。


    为了更清楚地说明,我会写一些带有类型注释的非法语法,你不应该使用它们:

    composePref (f :: Ize) (g :: Ize) = h
        where
            h :: Ize
            h (x :: String) (c :: Integer) =
                let (y :: String) = (g x) c
                in (f y) c
    

    【讨论】:

    • 感谢您的解释,编译器是如何计算出这一切的?如果 f g 和 x 没有明确的类型声明,这不是模棱两可吗?编译器如何知道 x 是整数而不是整数,比如 Int -> String?通过查看函数体中的表达式?如果编译器不能明确地推断出 f g 和 x 的类型,编译器会抱怨吗?
    • 是的,如果有歧义,编译器会报错。
    • @jhegedus 编译器使用一些非常智能的算法来推断类型。如果它无法弄清楚,那么它别无选择,只能抛出错误。由于您为函数composePref 提供了Ize -&gt; Ize -&gt; Ize 的类型签名,因此它会将其简化为无别名的长格式,并且可以计算出x :: Stringc :: Integer
    • @bheklilr 我说的是“the”,因为它是主要的。所以很特别。 :) 诚然,它与给出的不一样。
    • @augustss 您的陈述绝不是错误的,我只是想确保为那些可能不完全了解编译器的类型推断能力的人澄清它。
    【解决方案2】:

    如果您在函数中扩展最后一个 Ize 的类型,您会得到:

    composePref :: Ize -> Ize -> (String -> Integer -> String)
    composePref f g x = \c -> let y =  (g x) c 
                              in       (f y) c
    

    相同
    composePref :: Ize -> Ize -> String -> Integer -> String
    

    这也与:

    composePref :: Ize -> Ize -> String -> (Integer -> String)
    

    更符合您对composePref 的定义。现在fg 都是Izex 是一个字符串,c 是一个Integer

    【讨论】:

      【解决方案3】:

      您可以使用以下替代定义,如果这样可以使事情更清楚

       composePref :: Ize -> Ize -> Ize
       composePref f g = \x -> \c -> let y =  (g x) c 
                                     in       (f y) c
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-30
        • 1970-01-01
        • 2019-05-09
        • 2015-05-19
        相关资源
        最近更新 更多