Haskell 函数应用程序绑定的括号

Haskell parentheses for function application binding

我正在学习 Haskell。我定义了以下函数(我知道我不需要 addToList 我也可以做 Point-free notation 我只是在玩语言概念的过程中):

map :: (a -> b) -> [a] -> [b]
map f []    = []
map f (x:xs)  = addToList (f x) map f xs
  where
    addToList :: a -> [a] -> [a]
    addToList x [] = [x]
    addToList x xs = x:xs

这会产生编译错误:

with actual type `(a0 -> b0) -> [a0] -> [b0]'                                                                                                                   
Relevant bindings include                                                                                                                                                   
  f :: a -> b (bound at PlayGround.hs:12:5)                                                                                                                                 
  map :: (a -> b) -> [a] -> [b] (bound at PlayGround.hs:11:1)                                                                                                               
Probable cause: `map' is applied to too few arguments                                                                                                                       
In the second argument of `addToList', namely `map'                                                                                                                         
In the expression: addToList (f x) map f xs   

如果我在地图周围加上括号,它会起作用:

map :: (a -> b) -> [a] -> [b]
map f []    = []
map f (x:xs)  = addToList (f x) (map f xs)
  where
    addToList :: a -> [a] -> [a]
    addToList x [] = [x]
    addToList x xs = x:xs

我知道函数应用程序比运算符绑定得更紧密(如 Haskell - too few arguments 中所述),但是,我不明白编译器在没有括号的情况下如何以不同方式解析上述内容。

查看错误的简单方法是注意表达式:

addToList (f x) map f xs

将 4 个参数应用到 addToList 而:

addToList (f x) (map f xs)

将两个参数应用到 addToList(这就是 addToList "expects")。

更新

请注意,即使 map 有两个参数,这个表达式:

addToList a map c d

解析为:

(((addToList a) map) c) d

所以这是对 GHC 的想法的可能解释...

  1. addToList (f x) 具有类型 [a] -> [a] - 即它是一个接受列表的函数。

  2. map 的类型为 (c -> d) -> [c] -> [d]。它不是一个列表,但可以通过附加参数生成一个列表。

  3. 因此,当 GHC 看到 addTolist (f x) map 并且无法对其进行类型检查时,它会看到 if map 只有几个参数,如下所示:

    addToList (f x) (map ...)
    

至少 addToList 的第二个参数是一个列表 - 所以这可能就是问题所在。

解析是一个独特的步骤,它在类型检查发生之前完成。表达式

addToList (f x) map f xs

对解析器的意义与 s1 (s2 s3) s4 s2 s5 对你的意义一样大。它不知道名称 的意思 是什么。它采用字符串的词法结构并将其转换为像

这样的解析树
                        *5
                      /   \
                     /    xs
                    *4
                  /   \
                 /     f
                *3
              /   \
             /     map
            *2
          /   \
  addToList    *1
              / \
             f   x

一旦解析树完成,每个节点就会被标记上它的类型,并且可以进行类型检查。由于函数应用程序只是通过并列来表示,类型检查器知道节点的左子节点是一个函数,右子节点是参数,根节点是结果。

类型检查器可以大致如下进行,对树进行预序遍历。 (我会稍微改变类型签名以区分不相关的类型变量,直到它们统一为止。)

  1. addToList :: a -> [a] -> [a],所以它需要一个 a 类型的参数和 returns 类型 [a] -> [a] 的函数。 a 的值尚不清楚。
  2. f :: b -> c,所以它需要一个 b 类型的参数和 returns 类型 c 的值。 bc 的值尚不清楚。
  3. x 的类型为 dd 的值尚不清楚。
  4. b ~ df可以应用到x,所以*1 :: c
  5. a ~ caddToList应用于*1,所以*2 :: [a] -> [a]
  6. 呃哦。 *2 需要类型为 [a] 的参数,但它被应用于 map :: (e -> f) -> [e] -> [f]。类型检查器不知道如何统一列表类型和函数类型,这会产生您看到的错误。