Haskell 美元与()
我开始学习Haskell,遇到了一个我无法理解的问题。我有一个从键值列表(from)中查找值的方法: 我尝试了一下,并决定通过以下方式摆脱$sign:Haskell 美元与(),haskell,functional-programming,Haskell,Functional Programming,我开始学习Haskell,遇到了一个我无法理解的问题。我有一个从键值列表(from)中查找值的方法: 我尝试了一下,并决定通过以下方式摆脱$sign: let findKey key xs = snd . head . filter (\(k,v) -> key == k) ( xs ) 然而,它甚至不解析(过滤器应用于太多的参数错误)。我读过$sign被用来简单地替换括号,我不明白为什么这个简单的代码更改是不好的。有人能给我解释一下吗?操作员的优先级最低,所以 snd . head .
let findKey key xs = snd . head . filter (\(k,v) -> key == k) ( xs )
然而,它甚至不解析(过滤器应用于太多的参数错误)。我读过$sign被用来简单地替换括号,我不明白为什么这个简单的代码更改是不好的。有人能给我解释一下吗?操作员的优先级最低,所以
snd . head . filter (\(k,v) -> key == k) $ xs
读作
(snd . head . filter (\(k,v) -> key == k)) xs
而你的第二个表达是
snd . head . ( filter (\(k,v) -> key == k) xs )
中缀运算符($)
只是“函数应用程序”。换句话说
f x -- and
f $ x
都是一样的。由于在Haskell中,括号仅用于消除优先级歧义(以及元组表示法和),因此我们还可以用其他几种方式编写上述内容
f x
f $ x
(f) x
f (x)
(f) (x) -- and even
(f) $ (x)
在每种情况下,上述表达式都表示相同的内容:“将函数f
应用于参数x
”
那么为什么要使用这些语法呢<代码>($)之所以有用,有两个原因
在第一种情况下,考虑下面的右嵌套函数应用< /P>
f (g (h (i (j x))))
读这篇文章可能有点困难,知道括号的数目是否正确也有点困难。然而,它“只是”一堆应用程序,所以应该使用($)
来表示这个短语。确实有
f $ g $ h $ i $ j $ x
有些人觉得这更容易阅读。更现代的风格还融入了(.)
,以强调这个短语的整个左侧只是一个由函数组成的管道
f . g . h . i . j $ x
lof :: [Int -> Int]
lof = [ (+1), (subtract 1), (*2) ]
正如我们在上面看到的,这个短语与
(f . g . h . i . j) x
有时读起来会更好
有时我们希望能够传递函数应用的思想。例如,如果我们有一个函数列表
f . g . h . i . j $ x
lof :: [Int -> Int]
lof = [ (+1), (subtract 1), (*2) ]
我们可能希望通过它们上面的值来映射应用程序,例如,将数字4
应用于每个函数
> map (\fun -> fun 4) lof
[ 5, 3, 8 ]
但由于这只是一个函数应用程序,我们还可以使用($)
上的节语法来更明确一些
> map ($ 4) lof
[ 5, 3, 8 ]
“$
符号用于简单地替换括号”是非常正确的–但是,它有效地将所有内容都用括号括起来!所以
snd . head . filter (\(k,v) -> key == k) $ xs
有效地
( snd . head . filter (\(k,v) -> key == k) ) ( xs )
当然,xs
周围的参数在这里是不需要的(反正这是一个“原子”),因此在本例中,相关的参数在左侧。事实上,这在哈斯凯尔经常发生,因为一般哲学是尽可能多地将函数视为抽象实体,而不是将函数应用于某个参数时所涉及的特定值。你的定义也可以写出来
let findKey key xs' = let filter (\(k,v) -> key == k) $ xs
x0 = head xs'
v0 = snd x0
in v0
这将是非常明确的,但所有这些中间值都不是很有趣。因此,我们更愿意使用
将函数简单地“无点”链接在一起。这通常会让我们摆脱很多陈词滥调,事实上,根据您的定义,可以做到以下几点:
xs
。这个参数刚刚传递给函数链,所以我们不妨说“findKey
”就是这个链,不管你提供什么参数”
\(k,v)->k
只是fst
函数。然后需要使用键进行后期比较
findKey key = snd . head . filter ((key==) . fst)
键
,我们可以再次摆脱那些使用$
的参数。但是要小心:
"findKey key = snd . head . filter $ (key==) . fst"
是不是对,因为$
会再次将两边都括起来,但是(snd.head.filter)
的类型不好。实际上snd.head
应该只出现在filter
的两个参数之后。进行此类后期合成的一种可能方法是使用函数functor:
…我们可以更进一步,也可以去掉键
变量,但它看起来不太好。我想你已经明白了$
符号不是替换括号的神奇语法。它是一个普通的中缀运算符,在任何方面都像+
这样的运算符
用括号括住单个名称,如(xs)
,总是相当于xs
1。因此,如果$
就是这样做的,那么无论哪种方式,您都会得到相同的错误
试着想象一下,如果您在那里有一些您熟悉的其他操作符,例如+
,会发生什么:
let findKey key xs = snd . head . filter (\(k,v) -> key == k) + xs
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
dotExp2 = snd . dotExp1
in dotExp2 $ xs
忽略+
对数字起作用的事实,因此这没有任何意义,只考虑表达式的结构;哪些术语被识别为函数,哪些术语被作为参数传递给它们
事实上,使用+
确实可以成功地解析和类型检查!(它为您提供了一个具有无意义类型类约束的函数,但如果您满足这些约束,它确实意味着一些东西)。让我们了解一下中缀运算符是如何解析的:
let findKey key xs = snd . head . filter (\(k,v) -> key == k) + xs
优先级最高的总是普通函数应用程序(只需将术语并排写入,不涉及中缀运算符)。这里只有一个例子,filter
应用于lambda定义。这得到了“解决”,并就解析其余运算符而言成为一个子表达式:
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
in snd . head . fileterExp + xs
下一个优先级最高的是
运算符。我们有几个可以从这里选择,都有相同的优先级。
是右关联的,因此我们首先选择最右边的一个(但它实际上不会改变我们选择的结果,
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
in snd . dotExp1 + xs
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
dotExp2 = snd . dotExp1
in dotExp2 + xs
let findKey key xs = snd . head . filter (\(k,v) -> key == k) xs
let findKey key xs
= let filterExp1 = filter (\(k,v) -> key == k)
in snd . head . filterExp1 xs
let findKey key xs
= let filterExp1 = filter (\(k,v) -> key == k)
filterExp2 = filterExp1 xs
in snd . head . filterExp2
let findKey key xs
= let filterExp1 = filter (\(k,v) -> key == k)
filterExp2 = filterExp1 xs
dotExp = head . filterExp2
in snd . dotExp
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
dotExp2 = snd . dotExp1
in dotExp2 $ xs
{-# LANGUAGE RankNTypes #-}
-- A higher rank function
higherRank :: (forall a. a -> a -> a) -> (Int, Char)
higherRank f = (f 3 4, f 'a' 'b')
-- Standard application
test0 :: (Int, Char)
test0 = higherRank const -- evaluates to (3,'a')
-- Application via the ($) operator
test1 :: (Int, Char)
test1 = higherRank $ const -- again (3, 'a')
-- A redefinition of ($)
infixr 0 $$
($$) :: (a -> b) -> a -> b
($$) = ($)
test2 :: (Int, Char)
test2 = higherRank $$ const -- Type error (!)
-- Couldn't match expected type `forall a. a -> a -> a'
-- with actual type `a0 -> b0 -> a0'