在尝试编写一个用于转置列表列表的函数时,我看到了一些非常好奇的东西。我试过了:
> let abc xs | null (head xs) = [] | otherwise = map head xs : abc $ map tail xs
并收到错误消息。然后我试了一下:
> let abc xs | null (head xs) = [] | otherwise = map head xs : abc ( map tail xs )
> abc [[1,2,3], [4,5,6], [7,8,9]]
[[1,4,7],[2,5,8],[3,6,9]]
我被认为可以使用$
运算符代替括号,而且更多的是Haskellish。为什么我收到错误?
答案 0 :(得分:9)
运算符是可以应用于中缀位置的函数。所以$
是一个函数。
在Haskell中,您可以定义自己的函数,这些函数可以在参数之间的中缀位置使用。然后,您还可以使用infix
,infixr
,infixl
定义函数应用程序优先级和关联性,也就是告诉编译器是否将a $ b $ c
视为(a $ b) $ c
的线索},或a $ (b $ c)
。
$
的优先顺序是,您的第一个表达式被解释为(map head xs : abc) $ ...
例如,要将$
声明为中缀,请将其名称放在()
中:
($) :: (a->b) -> a -> b
f $ x = f x
或作文:
(.) :: (b->c)->(a->b)->a->c
(f . g) x = f $ g x
Arithemtic“运算符”也被定义为类Num中的中缀函数。
此外,您可以使用其他函数作为中缀,在应用程序站点的反引号中引用它们。有时它使表达看起来更漂亮:
f `map` xs == map f xs
(不是在这种特殊情况下,它使它看起来很漂亮,只是为了展示一个简单的例子)
答案 1 :(得分:0)
添加Sassa的正确答案,让我们进一步剖析您提供的代码段:
map head xs : abc $ map tail xs
此处使用了两个运算符:(:)
和($)
。如上所述,默认情况下,它们被解释为中缀,因为它们的名称仅由符号组成。
每个运营商都有一个优先权,决定如何“紧紧地”'它绑定,或者更有用的是,首先应用哪个运算符。您的代码可以解释为
((map head xs) : abc) $ (map tail xs)
其中(:)
更紧密地绑定(之前应用)($)
或
(map head xs) : (abc $ (map tail xs))
($)
绑定得更紧密。请注意,我已将括号放在功能应用程序周围(例如map
也应用于tail
和xs
)。函数应用程序比任何操作符绑定得更紧密,因此始终首先应用。
要确定解释代码的两种方法中的哪一种是正确的,编译器需要获取有关哪个运算符应该更紧密地绑定的信息。这是使用像
这样的固定声明来完成的infix 8
或一般
infix i
其中i
介于0和9之间。i
的值越高意味着运算符绑定越紧密。 (infixr
和infixl
可用于额外定义关联性,如Sassa的答案中所述,但这并不会影响您的具体问题。)
事实证明,($)
运算符的固定性声明是
infixr 0 $
如Prelude documentation所示。 (:)
更多的是魔法'因为它被硬编码到Haskell语法中,但是Haskell报告specifies它优先于5。
现在我们终于有了足够的信息来得出你的代码的第一个解释确实是正确的:(:)
,5的优先级高于($)
的优先级,0。作为一般经验法则($)
通常与其他运营商没有很好的互动。
顺便说一下,如果一个表达式包含两个具有相同优先级的不同运算符(例如(==)
and (/=)
),那么应该应用它们的顺序是不清楚的,所以你必须使用括号来明确地指定它