Haskell中的运算符和函数优先级

时间:2014-05-08 21:46:45

标签: function haskell operator-keyword infix-notation operator-precedence

所以这不起作用:

take 50 iterate (* 2) 1

因为它需要在第二个参数中使用括号。我的问题是为什么。

Haskell理所当然地看到了类型中的差异:

 Couldn't match expected type `[a0]'
            with actual type (a1 -> a1) -> a1 -> [a1]'

我的问题是:

1)似乎haskell首先尝试在迭代函数之前解析take 50函数。为什么haskell会这样做?在数学中,如果你有(x)你首先在做其他任何事情之前评估u(x)。为什么haskell在这种情况下开始评估f?

2)Haskell非常聪明,可以检测到实际类型是:

(a1 -> a1) -> a1 -> [a1]

现在,如果它看到这个函数的输出是[a1],一个与预期类型[a0]统一的类型,为什么haskell不统一呢?

3)为什么$运营商会解决这个问题?我知道:

($) :: (a -> b) -> a -> b

所以这个操作员基本上做的就是说"写FUNCTION $ ARGUMENT"并获得在该参数中计算的函数的值。在take 50案例中,类型为:

take 50 :: [a2]->[a2]
take 50 $ :: a->b 
where a ~a2 and b~b2 then
take 50 $ :: [a2]->[a2]

所以基本上我和第一种情况一样,没有使用括号或$。这种情况是我需要类型[a2]的参数(haskell称之为[a0]但它是相同的。所以..为什么haskell统一[a2]与(a1 - > a1) - > a1 - > [a1]当我使用$但是当我不使用它时它不会吗?

5 个答案:

答案 0 :(得分:8)

函数应用程序(用“juxtaposition”表示,将函数及其参数放在一起)必须以某种方式进行解析,并且它必须是左关联或右关联才能被明确解析。 SUP> 1

函数应用程序实际上是左关联的,所以你写的等同于

((((take 50) iterate) (* 2)) 1)

如果它是右关联的话,你会有

(take (50 (iterate ((* 2) 1))))

这也不是你想要的,而且作为默认选择更糟糕的是:很少有程序会让右关联运算符感觉更自然。

由于没有统一的解析规则可以生成您想要的程序,所以程序员必须通过$或某些括号的形式提示,告诉haskell你的实际含义。< / p>

至于为什么$有帮助:它被定义为在解析时具有非常低的优先级,因此写作

take 50 $ iterate (* 2) 1

解析为

(take 50) $ ((iterate (* 2)) 1))

这实际上就是你想要的。

1 明确的解析是一个非常理想的属性,用于拥有可理解的程序,并且让类型检查器决定如何解析事物(正如你所建议的那样)可能会非常混乱。 / p>

答案 1 :(得分:5)

f g t w u (x)也不是数学中的函数应用程序序列。但是,在数学中,如果我们将.作为函数组合运算符,则(f . g . t . w . u) (x)首先应用u函数,然后应用w函数,依此类推。在Haskell中也是如此:(f . g . t . w . u) x

在您的第一个类型错误中,它尝试将类型[a0](a1 -> a1) -> a1 -> [a1]类型统一,而不是类型[a1]。这是不可能的,因此它会产生错误。

并置是Haskell中的函数 application ,而不是函数组合(顺便说一下,数学并置通常是乘法)。这意味着像f x y z这样的表达式(基本上)意味着&#34;将函数f应用于参数xyz&#34 ;。这相当于((f x) y) z。 Haskell中的每个函数只需要一个参数。这意味着像f x y z = x + y * z这样的东西实际上与f = \x -> \y -> \z -> x + y * z相同。函数应用程序是关联的,因此f x y z与编写((f x) y) z相同,即将f应用于x首先获取生成的函数并将其应用于y }。最后,将该函数应用于z

这意味着您的原始表达式被解释为(再次转换为标准数学符号):take(50, iterate, (* 2), 1)

答案 2 :(得分:1)

take 50 iterate (* 2) 1与使用take (50, iterate, * 2, 1)的括号调用的语言相同,但您想要的是take (50, iterate(* 2, 1))$完全符合&#34的目的;在这里打开一个括号并尽可能地关闭它&#34;。它就像调用函数一样,但是右关联而不是左关联。您可以使用$在没有take 50 (iterate (*2) 1)运算符的情况下编写它。

答案 3 :(得分:1)

函数应用程序是左关联的,所以

f g a b

((f g) a) b

所以你有

(((take 50) iterate) (*2)) 1

这是不正确的,因为它将take 50 :: [a] -> [a]应用于类型为iterate的{​​{1}},显然不正确。

答案 4 :(得分:0)

(我会给你一些更冗长的答案,涵盖我认为其他答案未被发现的一些角落,至少没有明确说明)。

在Haskell中,括号表示函数调用。它们只是用于分组。所以u (x)与Haskell中的u x完全相同。

首先显示的类型错误是由Haskell尝试使用iterate作为take的第二个参数生成的:

take    :: Int -> [a0]                     -> [a0]
take       50     iterate                  :: t 
iterate ::        (a1 -> a1) -> a1 -> [a1]
                  ------------------------
                  [](...) and (->)(...) do not match

在解决iterate函数后,它不会尝试“解析take 50函数”。它还没有考虑你剩余的长篇大论。它只是尝试使用iterate 。这恰好是一个功能。

函数只是Haskell中的值,这就是为什么没有特殊的语法来调用它们,就像各种foo(args){exps;};种语言一样。

对于编译器来说,尝试纠正你的表达式太危险了:太多的拼写错误或错误会突然出现意想不到的意思。这最多可能是一些假设的交互式开发环境的特征。

当你写take 50 $ iterate (*2) 1时,第二个表达式的类型(在$运算符之后)是

iterate ::        (a1 -> a1) -> a1 -> [a1]
iterate           (*2)          1  :: [Int]  -- it's actually (Num a => [a]) 
                                             -- but that's besides the point

并且数字列表类型(:: Num a => [a])与take调用中的第二个参数匹配,不是类型裸体未申请iterate本身

take    :: Int ->               [a0]       -> [a0]
take       50     (iterate (*2) 1  )       :: t 
iterate (*2) 1    :: (Num a) => [a ]
                  ------------------
                  a0 ~ (Num a)=> a            t ~ (Num a) => [a]

所以它“typechecks”,即它有效。事实上,在GHCi中,我们得到了

  

前奏&GT; :t取50(迭代(* 2)1)
      取50(迭代(* 2)1)::(Num a)=&gt; [α]