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…呢,也或许会太监了呢。