关于haskell随便写点什么1

LYAH字典查询函数理解指南

for what

最近在看Haskell,并且极度想要给好基友安利这一个语言。So,今天看 《Learn You a Haskell for Great Good》恰巧基友被我安利烦了,问我haskell有什么好的,我刚看到§7.4 Data.Map这章,就发了看到的第一个函数给他,不过感觉他会看不懂,就想写个讲解给他,写长了之后就想成文了。

那个函数

这是一个从字典中找值的函数

findKey :: (Eq k) => k -> [(k,v)] -> v
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs

举个栗子

这个例子同样是从LYAH中搞出来的:

phoneBook = [(“betty”,“555-2938”) ,       (“bonnie”,“452-2928”) ,
      (“patsy”,“493-2928”) ,
      (“lucille”,“205-2928”) ,
      (“wendy”,“939-8282”) ,
      (“penny”,“853-2492”) ]

函数申明

findKey :: (Eq k) => k -> [(k,v)] -> v

在函数中,首先第一行是函数申明,他和其他语言一样表明了输入输出。
在haskell中函数仅支持一个返回值,支持多个入参。在上面的申明中 findKey 是函数名,以 “::“来标明函数,后面一共有四个东西,

(Eq k) => k -> [(k,v)] -> v

首先可以仅理解后三个:

k -> [(k,v)] -> v

这是函数参数和返回值,这个函数中一共有两个参数和一个返回值,函数申明中的最后一个箭头”->“是一定指向返回值的,当然”k v“这两个字母一定会让人觉得很疑惑,在haskell中函数声明中的字母其实只代表一个变量类型哦 sorry,haskell中没有变量,他只有常量,所有都是静态参数,所以这个字母代表了一种模糊的参数类型,这仅表示上面两个k需要是同样的类型,而两个v也需要是同样的类型,随便用abcd也可以。所以:

k” “[(k,v)]” 是两个入参,第一个参数是k类型的参数,第二个参数是一个list,list中是一个有两个类型为k v为成员的tuple

v” 是函数的返回值,它的类型需要和入参list中tuple第二个元素一样。

然后我们来说一说”(Eq k) =>", “=>“是对后面参数的约束条件,"(Eq k)“说明后面的 “k” 类型的参数必须是一个Eq的派生类型,这种类型是可以比较的,比如int,char。

在事例中我们可以按照

findKey “betty” phoneBook

的方式进行调用,其中phoneBook可扩展成的[(“betty”,“555-2938”)…],看起来形式就差不多了。

函数实体

函数调用

findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs

这是函数体,在等号左边的是函数调用的方式,在这个函数中,调用方式是:

findKey key xs
example:findKey “betty” phoneBook

当然也可以是中序,这个就另讲了。这个函数调用方式也是一种模式匹配,这边的”key"、"xs“匹配了函数的两个入参”k"、"[(k,v)]

filter

接下来对这两个入参进行操作(先忽略”$“符号):

filter (\(k,v) -> key == k) xs

首先是filter,这个是对一个list的过滤器,是”filter f list” 这样的模式,对list的每一个元素使用函数f进行匹配。在此例中

(\(k,v) -> key == k)

就是filter中的 f ,haskell中使用 \做lambda,lambda就是一个隐式函数。
在lambda中”->“是用来区分参数和lambda函数本体的,在”->“左边是函数参数,可做模式匹配。在此lambda函数中,用”(k,v)“来匹配list成员tuple的成员。第一个”(k,v)“就是:

(“betty”,“555-2938”)

此lambda用来匹配 xs 这个list取出来的每一个元组,并且判断此元组中的键值 k 是否能匹配入参的 key。并且filter会将符合条件的元素==重新组合==成一个新的list,在此例中,filter结果就是

[(“betty”,“555-2938”)]

后面键值不为***“betty”***的元组就会被舍弃,这是仅有一个元素的list。

snd head

接下来这个是head,这个函数用来取list的头部,取出来就是第一个元素,例如

***head [3,2,5,3,6,3,4,3,7]***的结果就是3

在此例中结果大致是:

(“betty”,“555-2938”)

是的,你会发现,这仅仅是去掉了中括号,这代表着这不再是一个list了。

接下来snd用来取tuple中的第二个元素,就是字典中的value,也就是

“555-2938”

就能得出函数返回值了。这边需要注意的是,snd的参数只能是二元tuple。

函数组合

snd . head . filter 这个是函数组合,相当于一下子调用了三个函数。这个可以用数学公式来表示:

(f.g)(x) = f(g(x))

在此处

snd.head.filter f $ list = snd(head(filter f list)))

在haskell中,函数组合是一个让代码简洁明了的手段,但是他也有它的局限性,就是说他的参数只能唯一,函数组合仅能包含一个参数。但是此处柯里化使得组合函数使用多参成为可能。

柯里化(Currying)

柯里化是现代化语言中包含的一个非常有趣的特性之一,在js、swift中也支持柯里化(非常遗憾swift仅在Language Reference中提到了一点)。
Currying是指函数可以不完全调用,他可以将多参函数转化为单参函数来求解。例如”+“函数(对,在haskell中”+“也是一个函数)

5+4 =======> 9

其可以转化为:

let plusFive = (+5)

这样一来我们就多了一个新的函数叫做plusFive,我们来调用它:

plusFive 4 =======> 9
plusFive 167 =======> 172

ok,Currying就是这样一个好玩的特性,我自己认为,这个和惰性有关,因为Curried Function切切实实要在有完整入参时才会调用。

对于本例来说,我们将

filter (\(k,v) -> key == k) xs Currying成 ***(filter (\(k,v) -> key == k)) xs***

在组合函数中其实是组合成了

***(snd . head . filter (\(k,v) -> key == k)) xs***

在原函数中,少了”()",多了”$":

snd . head . filter (\(k,v) -> key == k) $ xs

这边用到了的”$",如果缺少”$

snd . head . filter (\(k,v) -> key == k) xs 会被理解成
snd . head . (filter (\(k,v) -> key == k) xs)

而这绝对是会被编译器理解不能的句子,会报错,"$“改变了filter函数的匹配方式,在haskell中”$“是拥有最低优先级的函数调用符,相当于一个函数,所以会变成

snd . head . filter (\(k,v) -> key == k) ($ xs)

函数组合的优先级大于$ xs的优先级,就会前面线组合起来。"$“是一个非常节约括号的函数调用符.

函数结论

findKey “betty” phoneBook
***“555-2938”***
***findKey “patsy” phoneBook***
***“493-2928”***

就此结束吧,心血来潮谢谢,先打上个标签1,说不定还有2,3,4…呢,也或许会太监了呢。

reads

Avatar
MorningTZH

喵?

下一页
上一页