Post

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的复合。

This post is licensed under CC BY 4.0 by the author.