所以这不起作用:
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]当我使用$但是当我不使用它时它不会吗?
答案 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
应用于参数x
,y
和z
&#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; [α]