Nie*_*and 17 haskell functional-programming
我开始学习Haskell,遇到了一个我无法理解的问题.我有一个方法用于从键值列表列表中查找值(来自此页面):
let findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
Run Code Online (Sandbox Code Playgroud)
我试着摆弄一下,决定以这种方式摆脱$ sign:
let findKey key xs = snd . head . filter (\(k,v) -> key == k) ( xs )
Run Code Online (Sandbox Code Playgroud)
但是,它甚至没有解析(过滤器应用于太多的争论错误).我已经读过$ sign用于简单地替换括号,我无法弄清楚为什么这个简单的代码更改很糟糕.有人可以向我解释一下吗?
J. *_*son 29
中缀运算符($)只是"函数应用程序".换一种说法
f x -- and
f $ x
Run Code Online (Sandbox Code Playgroud)
是相同的.因为在Haskell中,括号仅用于消除优先级(对于元组符号和其他一些小的位置,请参阅注释)我们也可以用其他几种方式编写上述内容
f x
f $ x
(f) x
f (x)
(f) (x) -- and even
(f) $ (x)
Run Code Online (Sandbox Code Playgroud)
在每种情况下,上面的表达式都表示相同的事情:"将函数f应用于参数x".
那么为什么要使用这些语法呢?($)有用有两个原因
在第一种情况下,请考虑以下深度右嵌套函数应用程序
f (g (h (i (j x))))
Run Code Online (Sandbox Code Playgroud)
阅读本文可能有点困难,有点难以知道你有正确数量的括号.然而,它只是"一堆"应用程序所以应该使用这个短语的表示($).的确有
f $ g $ h $ i $ j $ x
Run Code Online (Sandbox Code Playgroud)
有些人发现这更容易阅读.更加现代的风格也融合在一起(.),以强调这个短语的整个左侧只是一个组合的功能管道
f . g . h . i . j $ x
Run Code Online (Sandbox Code Playgroud)
正如我们上面所看到的,这句话与...相同
(f . g . h . i . j) x
Run Code Online (Sandbox Code Playgroud)
这有时更好阅读.
有时我们希望能够传递功能应用的想法.例如,如果我们有一个功能列表
lof :: [Int -> Int]
lof = [ (+1), (subtract 1), (*2) ]
Run Code Online (Sandbox Code Playgroud)
我们可能希望通过它们的值映射应用程序,例如将数字4应用于每个函数
> map (\fun -> fun 4) lof
[ 5, 3, 8 ]
Run Code Online (Sandbox Code Playgroud)
但由于这只是函数应用程序,我们也可以使用section语法($)来更明确一些
> map ($ 4) lof
[ 5, 3, 8 ]
Run Code Online (Sandbox Code Playgroud)
ber*_*eal 17
运营商$的优先级最低,因此
snd . head . filter (\(k,v) -> key == k) $ xs
Run Code Online (Sandbox Code Playgroud)
被读为
(snd . head . filter (\(k,v) -> key == k)) xs
Run Code Online (Sandbox Code Playgroud)
而你的第二个表达是相当的
snd . head . ( filter (\(k,v) -> key == k) xs )
Run Code Online (Sandbox Code Playgroud)
该$标志是不是魔术语法替换括号.它是一个普通的中缀运营商,在各方面都是运营商所喜欢+的.
把周围像一个名字括号( xs )是始终只相当于xs1.所以如果这就是做了什么$,那么你会得到同样的错误.
试着想象如果你有一些你熟悉的其他操作员会发生什么,例如+:
let findKey key xs = snd . head . filter (\(k,v) -> key == k) + xs
Run Code Online (Sandbox Code Playgroud)
忽略+对数字起作用的事实,这是没有意义的,只要考虑表达式的结构; 哪些术语被识别为函数,哪些术语作为参数传递给它们.
事实上,使用+那里确实成功解析和typecheck!(它为您提供了一个具有无意义类型类约束的函数,但如果您实现它们,它确实意味着什么).让我们来看看中缀运算符是如何解决的:
let findKey key xs = snd . head . filter (\(k,v) -> key == k) + xs
Run Code Online (Sandbox Code Playgroud)
最高优先级始终是正常的函数应用程序(只是编写彼此相邻的术语,不涉及中缀运算符).这里只有一个例子,filter应用于lambda定义.这得到"解决",并且就解析其余运算符而言变成单个子表达式:
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
in snd . head . fileterExp + xs
Run Code Online (Sandbox Code Playgroud)
下一个最高优先级是.运营商.我们有几个可供选择,所有都具有相同的优先权.该.是对的关联,因此,我们采取了最右边的一个第一(但不会实际改变哪一个我们选择的结果,因为意义的.是一个关联操作,但是解析器不知道的样子):
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
in snd . dotExp1 + xs
Run Code Online (Sandbox Code Playgroud)
请注意,立即向左和右.抓住条款.这就是优先权如此重要的原因.还有一个左边,它的优先级仍然高于,所以它接下来:.+
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
dotExp2 = snd . dotExp1
in dotExp2 + xs
Run Code Online (Sandbox Code Playgroud)
我们完成了!+这里的运算符的优先级最低,所以它最后得到它的参数,并最终成为整个表达式中最顶层的调用.请注意,+优先级较低会阻止xs左侧任何较高优先级的应用程序"声明"为参数.如果他们中的任何一个优先级较低,他们最终会把整个表达dotExp2 + xs作为一个论点,所以他们仍然无法做到xs; 在之前放置一个中缀运算符xs(任何中缀运算符)可以防止它被左边的任何东西声称为参数.
事实上,这与在此表达式中解析的方式完全相同$,因为.它$恰好具有相同的优先级.并且+具有相同的优先级.$被设计为具有极低的优先级,因此它将以这种方式与左右相关的几乎任何其他运算符一起工作.
如果我们不在filter调用之间放置一个中缀运算符xs,那么这就是:
let findKey key xs = snd . head . filter (\(k,v) -> key == k) xs
Run Code Online (Sandbox Code Playgroud)
正常功能应用程序首先.在这里,我们已经得到了3项只是彼此相邻:filter,(\(k,v) -> key == k),和xs.函数应用程序是左关联的,所以我们先取最左边的一对:
let findKey key xs
= let filterExp1 = filter (\(k,v) -> key == k)
in snd . head . filterExp1 xs
Run Code Online (Sandbox Code Playgroud)
还剩下另一个正常的应用程序,它的优先级仍然高于.,所以我们这样做:
let findKey key xs
= let filterExp1 = filter (\(k,v) -> key == k)
filterExp2 = filterExp1 xs
in snd . head . filterExp2
Run Code Online (Sandbox Code Playgroud)
现在第一个点:
let findKey key xs
= let filterExp1 = filter (\(k,v) -> key == k)
filterExp2 = filterExp1 xs
dotExp = head . filterExp2
in snd . dotExp
Run Code Online (Sandbox Code Playgroud)
我们已经完成了,这次整个表达式中最顶级的调用是最左边的.运算符.这一次xs被吸入作为第二个论点filter; 这是有点,我们所希望的,因为filter确实需要两个参数,但它留下的结果的filter一个函数组合链,并filter应用到两个参数不能返回的功能.我们想要的是将它应用于一个参数以给出一个函数,让该函数成为函数组合链的一部分,然后将整个函数应用于xs.
在$那里,最终形式反映了我们使用时+:
let findKey key xs
= let filterExp = filter (\(k,v) -> key == k)
dotExp1 = head . filterExp
dotExp2 = snd . dotExp1
in dotExp2 $ xs
Run Code Online (Sandbox Code Playgroud)
它的解析方式与我们的方式完全相同+,所以唯一的区别在于,+"将我的左参数添加到我的右参数" $意味着"将我的左参数作为函数应用于我的正确参数".这就是我们想要发生的事情!好哇!
TLDR:坏消息是$只用括号括起来就行不通; 它比那更复杂.好消息是,如果您了解Haskell如何解析涉及中缀运算符的表达式,那么您就能理解它是如何$工作的.就语言而言,没有什么特别之处; 它是一个普通的运算符,你可以定义自己,如果它不存在.
1对运算符进行括号(+)也只是为了给出完全相同的函数+,但现在它没有特殊的中缀语法,所以这确实会影响在这种情况下解析事物的方式.不是这样( xs ),它只是里面的名字.