Learning Haskell Note - 01
这学期上了一门《程序语义分析与验证》的课,老实说我对其中的数学证明不太感冒(对我来说,有点“过于”严谨了),但是其中介绍的函数式编程语言非常吸引我,打算趁这个机会玩一玩。
这个系列不会用来记录繁杂的知识点,而是写下一些偶尔的想法。
函数的柯里化
在编写”根据key查找map中数据”的时候,刚开始我编写了基础版本的程序:
1
2
3
4
book = [("a", 12), ("b", 13), ("c", 14), ("d", 15)]
findKey k m =
snd (head (filter (\x -> fst x == k) m))
然而尝试引入 .的时候,遇到了一些错误:
1
2
findKey k m =
snd . head (filter (\x -> fst x == k) m)
• Couldn’t match expected type: (a1, b0) with actual type: a2 -> (a3, c) • Probable cause: ‘x’ is applied to too few arguments
问题出在结合顺序上,对于.运算符,需要结合两个函数,但是head和后面的参数结合后,结果不再是函数,而是一个tuple.
解决方法:
1
2
3
4
5
findKey k m =
(snd . head) (filter (\x -> fst x == k) m)
-- 或者下面的方法
findKey k m =
snd . head $ (filter (\x -> fst x == k) m)
$运算符能够将左侧和右侧“阻断”,从而将左侧解释为一个复合函数,右侧是一个list
如果想要进一步使用.来结合,下面的方法会报错:
1
2
findKey k m =
snd . head . filter (\x -> fst x == k) m
• Couldn’t match expected type: a2 -> [(a0, c)] with actual type: [(a1, b)] • Possible cause: ‘filter’ is applied to too many arguments
理解错误很容易,可是要如何修改呢?如果仿照上面的例子改成:
1
2
findKey k m =
snd . head . filter $ (\x -> fst x == k) m
还是有问题,仔细想想:要结合 f . g,必须要求g的返回值类型和f的参数类型相符合。 如果把filter理解成“接受两个参数,然会一个list”的函数,肯定会想:没有什么问题啊?这不就是head所需要的吗?
然而,在haskell中,所有的函数其实本质上都是“接受一个参数,返回一个结果”,对于filter,本质上是“接受一个函数(用来判定),返回一个函数B”,其中函数B进一步才有”接受一个list, 返回一个list”, 这也是为什么我们习惯了用“多参数”的视角看待问题后,很容易出现结合顺序的问题。对于f x y,实际上相当于(f x) y, 因此有时候写f -1会导致( f (-) ) 1而不是f (-1),进而语法报错。
理解了这一点后,就可以找到一种解决方法:
1
2
findKey k m =
snd . head . filter (\x -> fst x == k) $ m
这里filter优先以后面的lambda表达式作为参数,返回一个“函数B”,而这个函数B会“接受一个list, 返回一个list”,接着我们用这个函数B参与和snd . head的复合。