上一篇我光是在扯 Haskell 多 nb 却没说学 Haskell 到底有什么好处或者用处... 我想了很久,列出几点又被自己反驳了...
增进人与人之间的沟通和理解?学 C 可以理解 Go 语言的发明者对前半生的深深遗憾 (误), 学 C++ 可以理解 D 语言的发明者对 C++ 爱是有多深 (大误), 学 Python 可以窥见豆瓣改版的某些因素 (超大误)... 学 Java ... 如果你的同事用了 ThreadPoolExecutor 去改进性能,结果程序就挂了,学了 Java 就会拍拍同事的肩,叹一口气,说"王架构早告诉你别在线程池里创建线程池了"... 吾在玩了片轮少女后,非常喜欢里面盲眼的 Lily, 心想以后万一真的遇到这样的少女肿么办,至少得会用布莱叶写"好き"吧... 但用函数式语言的人很少了,用 Haskell 的人实在是更少,不是 Haskell 程序员却和 Haskell 程序员同事的人就更少了,这无穷小量何处去找 (下次杭州 Ruby Tuesday?).
利用 Haskell 处理并发并行的优势,玩转 Actor (轻量级并发) 和 STM (软件事务性内存), 拥抱多核的时代?老实说几千核的 GPU 已经普及了但 100 核的 CPU 还很少见... 赶紧研究显卡和 OpenCL 才对吧...
以子之矛攻子之盾,学习函数式编程,给论坛上那些鼓吹 Haskell 的人狠狠的打脸?我想起 Jeremy Weirich, 这么老的一个 Ruby 程序员,在 Ruby Kaigi 2012 上分享在 Ruby 中写 Y-combinator 的过程,比起在 haskell 中写个 y f = f y f
就说 "实现了" 更有挑战性...
我们每天在杀猪,为什么还要研究龙的空气动力学, 为什么还要训练对龙格斗术? 所以还不如反过来,在这里摘录一些 wiki books 上的屠龙宝典的介绍,揉合一些杂七杂八的内容,把 没必要学 Haskell 的理由补充充分...
站在 Ruby 程序员的角度,Haskell 很多特性都是处于反面:
-W2
可以提示某些地方缩进与代码块不一致的地方。Haskell 的缩进是语法的一部分,标示着块的开端。写了 do
就不用写 end
了-- 函数入口是 main
main = do putStr "hello"
putStrLn "world"
缩进风格上 Haskell 和各种 lisp 一样,习惯和上一行的第二个词对齐,比 Ruby 要多产生很多空格,所以上面的代码会变成这样:
{-
Haskell 缩进风格:
putStrLn 和上一行的 putStr 对齐
-}
main = do putStr "hello"
putStrLn "world"
命名风格上 Ruby 是蛇行习惯,下划线看起来像空格。Haskell 是驼峰习惯,少敲一个键,除了函数/参数以小写字母开头外,其它东西 (包名,数据类型,type class,constructor...) 都用大写字母开头。
函数调用风格上 Haskell 是 "反正你都要写那个空格,干脆逗号也不要算了".
在 Haskell 里
f = 3
其实在 Ruby 里相当于
def f
3
end
=
并不是赋值运算符,而是定义的意思,要念出来的话就是 is
.
例如在 Ruby 里声明一个 lambda
lambda {|x| x != 5}
在 Haskell 里就变成
\x -> x /= 5
在书里就显示成
λ x → x ≠ 5
后来 Ruby 也稍微向 Haskell 靠拢了点
-> x {x + 5}
另外一些象形文字还有
<-<
>->
((.)$(.))
不是开玩笑!你和 Haskell 程序员说 right fish
他应该明白就是指 >->
!
但也有不太明显的符号,记住下面这个词汇表,基本就能高声朗诵 Haskell 代码了:
:: has type (类型声明)
: cons (列表构造)
=> implies (类型约束)
. compose (函数复合)
<- drawn from (从...中拖出来)
-< arrow
&&& both
||| either
++ append
>>= bind
>> then
<*> applied over
! index
() unit
(,) tuple (2元组)
(,,) triple (3元组)
(,,,) quadruple (4元组)
(,,,,) quintuple / pentuple (5元组)
(,,,,,) sextuple / hextuple (6元组)
(,,,,,,) septuple (7元组)
(,,,,,,,) octuple (8元组)
(,,,,,,,,) nonuple (9元组)
(,,,,,,,,,) decuple (10元组)
(,,,,,,,,,,) hendecuple / undecuple (11元组)
(,,,,,,,,,,,) duodecuple (12元组)
(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,) centuple (百元组)
-- 其实... n元组念 n-tuple 就可以了 XD
构造一个 List:
[] -- 一个空 List
1 : [] -- [1]
[1] ++ [2] -- [1,2]
常见 List 处理函数的山寨实现
{-
head [] -- error
head [1,2,3] -- 1
-}
head x:xs = x
{-
tail [] -- []
tail [1..3] -- [2,3]
-}
tail x:xs = xs
{-
init [1..3] = [1,2]
-}
init (x:_:[]) = [x]
init (x:xs) = x:init xs
{-
last [1..3] = 3
-}
last (x:[]) = x
last (_:xs) = last xs
{-
take 3 [1..] -- [1,2,3]
-}
take 0 _ = []
take n (x:xs) = x:take (n - 1) xs
{-
drop 3 [1..4] -- [4]
-}
drop 0 xs = xs
drop n (x:xs) = drop (n - 1) xs
{-
判断是否为空
null [] -- True
null [1..3] -- False
-}
null [] = True
null _ = False
{-
length [1..3] -- 3
-}
length [] = 0
length (x:xs) = 1 + length xs
说到函数式,在 ruby 中我们有 select, map, inject, find, 在 haskell 中我们有 filter, map, foldl/foldr, find. 其实像 ruby 那样把 lambda 放后面是比较好写的,block 可以换到下一行,像 haskell 那样把 lambda 放前面就略不好写,但是 haskell 的做法又能享受 curry 的好处:因为把 map 等函数和一个 lambda 绑定住多次使用,比和一个 list 绑定住的情况要多得多。
{-
map (*2) [1..4] -- [2,4,6,8]
-}
map f [] = []
map f (x:xs) = f x:map f xs
{-
filter (>3) [0..6] -- [4,5,6]
-}
filter f [] = []
filter f (x:xs) = if f x then x:filter f xs else filter f xs
{-
find (>3) [0..6] -- Just 4
-}
find f [] = Nothing
find f (x:xs) = if f x then Just x else find f xs
reduce 在 haskell 中分为 foldl 和 foldr 两种
foldl (+) 0 [1..5] == ((((0+1)+2)+3)+4)+5
foldr (+) 0 [1..5] == 1+(2+(3+(4+(5+0))))
-- 作为体会 Haskell 无用性的练习, 就留给读者实现了
当然 Haskell 中内建大量的 List 操作函数, 某些函数还有 strict 或者特殊用途的版本。在代码中 import Data.List
或者在 ghci 中 :m Data.List
就能使用了。讨厌学习的人完全可以不 import, 自己实现一套。
Haskell 的字符串类型其实是 [Char]
, 也就是 Char
的 List, API 统一了也可以少学好多东西...
first "hello" -- 'h'
主动报学历或者主动造学历也是程序员应聘面试技巧之一... 在 ghci 看类型用 :t
命令
Prelude> :t 1
1 :: Num a => a
输出按照上面的密码词典翻译,就是 1 has type 'a', 'a' implies Num
, Num
是 class, 下面会继续解释。
Haskell 的常见类型有 Void, Unit, Int(小整数), Integer(任意长整数), Float(单精度浮点), Double(双精度浮点), Char, String, Bool 等。
Void 是这么一个类型:任何东西都不属于 Void, 它是一个没有值的类型... 如果把类型看作集合,属于一种类型的东西是集合元素,Void 就是空集。真要问 Void 是什么东西,答案就是它的定义就是 "平时我们不会遇到也不会用到的东西"... 在类型运算中 Void
是零元,就和 0 在四则运算中的地位一样。
Unit 是这么一个类型:它只有一个值 ()
, 它和其它 tuple 一样,是类型的笛卡尔积,但它是 0 个类型的积,也就是乘法单位元,和 1 在四则运算的地位一样。(关于 Void 和 Unit 以后要专门发一篇帖子讨论代数数据类型..). Scala 中也有 Unit
类型,其实和 C / Java 里返回 void
是一样的。
类型声明可以在源代码中用,但不能在 ghci 中用 (我会告诉你因为 ghci 其实是个 Monad 吗??). 例如你可以在 main 上面加个类型 main :: IO ()
, 念法就是 main 的类型是 IO 没有返回值
...
main :: IO ()
main = do printLn "hello world"
class 这个坑爹的词,但是说白了就是实现函数重载的工具。因为用参数类型来匹配函数重载不够高端 (java 的 interface 可以看作用类型匹配函数重载), 所以 Haskell 用参数的 type 的 class 来匹配。
挖了一个弥天大坑... 下面是待完工的部分... 务求一定要将不用学 Haskell 的理由写得充分完整...
其实,在静态语言里,List 里每个元素类型要一样,这不是略蛋疼的一件事,而是非常非常蛋疼的一件事,在一个静态语言里处理 json 有让人想死的冲动...
Ruby 是语法上 OO 的,Haskell 是语义上 OO 的,前面说了此 class 非彼 class 你又来说 OO ? master foo (无名师) 说过,闭包和对象是一体...
Ruby 的函数调用是 strict 的,也就是参数在调用前要求值,而且一调用就要出货,Haskell 的函数调用是 lazy 的,参数在调用后也不需要求值,到了 IO 时才真的去计算 (是不是想起那个可以用 O(1) 的时间解决任何问题,只是没有 IO 的编程语言?). 所以 haskell 的程序往往会多耗一些内存 (保存未求值的调用) 但少做一些计算。
Ruby 程序的正确,我们用直觉和测试去保证 (我觉得我的程序是对的), Haskell 程序的正确,我们用证明去保证 (你能证明你的程序是对的吗?)...
Ruby 把各种不纯的东西混在里面,Perl + Smalltalk + Lisp + ... 连 awk 的写法都无耻的拿来用了,是编程经验的积累,实用得很。Haskell 是纯纯的函数式语言,内置 Hindley Milner 推导系统的加强版,是类型化 lambda 代数历史的沉淀,不实用得很... 但有句话叫做 "愚者从经验中学习,痔者从力屎中学习"...
打开类型运算参数后,Haskell 的类型运算自身已经构成图灵完备的语言 ...
{-# LANGUAGE EmptyDataDecls, TypeOperators #-}
利用 type deducer 是可以做些事情的,例如实现一个 quickCheck (need citation here)