0

我试图理解为什么这两个评估:(init . cuts) [1,2,3]并且init . cuts [1,2,3]是不同的,其中:

cuts :: [a] -> [([a],[a])]
cuts xs = zipWith splitAt [0..length xs] (repeat xs)

第一个给出了我期望的结果:[([],[1,2,3]),([1],[2,3]),([1,2],[3])],但是第二个返回此错误:

<interactive>:389:8:
    Couldn't match expected type `a0 -> [a1]'
                with actual type `[([a2], [a2])]'
    In the return type of a call of `cuts'
    Probable cause: `cuts' is applied to too many arguments
    In the second argument of `(.)', namely `cuts [1, 2, 3]'
    In the expression: init . cuts [1, 2, 3]

我假设,init . cuts [1,2,3] = init (cuts[1,2,3])这是正确的吗?

谢谢,
塞巴斯蒂安。

4

1 回答 1

5

这是因为固定性或操作顺序。当编译器看到

(init . cuts) [1, 2, 3]

它将 this 解析为inits . cuts带有参数的函数[1, 2, 3]。当它看到

init . cuts [1, 2, 3]

它将 this 解析为由 functioninits组成的函数cuts [1, 2, 3],但由于cuts [1, 2, 3]不是函数,因此会引发类型错误。这是因为函数应用程序总是优先于运算符应用程序。这就是为什么你可以写出像这样的表达式

f = zipWith (+) [1..] . filter (< 10) . take 5

代替

f = (zipWith (+) [1..]) . (filter (< 10)) . (take 5)

如果运算符优先级可能高于函数应用程序优先级,那么您可能必须根据运算符优先级使用后一种形式,然后表达式变得更难编译器和程序员解析。这可能意味着可能存在歧义,例如

x 1 <#> y

因为它可以被解析为

(x 1) <#> y

或者

x (1 <#> y)

取决于优先级(我只是组成了一个 operator <#>)。


正如@chepner 正确指出的那样,如果您想避免使用括号(就像许多 Haskeller 所做的那样),您可以改用$运算符,它具有精心选择的固定性,可以让您这样做

init . cuts $ [1, 2, 3]

$ 运算符的经验法则是,它 将出现在右侧的任何内容并用括号括起来具有相同的效果1 ,因此复杂的表达式如

func (func (func (func (func 1))))

可以改为更好的形式

func $ func $ func $ func $ func 1

甚至

func . func . func . func . func $ 1

1正如@Cubic 所指出的,有些人可能认为这$是 Haskell 的一些特殊语法。不是这种情况。它非常简单地定义为

($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $

事实上,这个函数和函数是一样的id,只是表示为一个与右边关联的优先级很低的中缀运算符,而这种固定性使得该运算符对于非常清晰地表达代码很有用。它可以等同于使用更多的括号,但这并不能使其成为它们的替代语法。这只是另一个功能。

于 2014-05-06T17:15:19.457 回答