[TOC]
0, Why
我们用几分钟来想象一下自己是一名伐木工人,手里有林场里最好的斧子,因此你是工作效率最高的。突然有一天场里来了个推销的,他把一种新的砍树工具一一链锯给夸到了天上去。这人很有说服力,所以你也买了一把,不过你不懂得怎么用。你估摸着按照自己原来擅长的砍树方法,把链锯大力地挥向树干一一你不知道要先发动它。“链锯不过是时髦的样子货罢了”,没砍几下你就得出了这样的结论,于是把它丢到一边重新捡起用惯了的斧子。就在这个时候,有人在你面前把链锯给发动了…
学习一种全新的编程范式,困难并不在于掌握新的语言。
毕竟学到函数式编程语言的程序员,学过的编程语言少说也有一箩筐一一语法不过是些小细节了。
真正考验人的,是怎么学会用另一种方式去思考。
——《Functional Thinking》 Neal Ford
1,编程语言的类型
- 强类型:偏向于不容忍隐式类型转换。比如说haskell的int就不能转换为double。
- 弱类型:偏向于容忍隐式类型转换。比如说C语言的int可以转换为double。
- 静态类型:编译的时候就知道每一个变量的类型,类型声明/使用的错误属于编译期的错误,这意味着编译阶段就能发现所有类型错误。
- 动态类型:编译的时候不知道每一个变量的类型,类型声明/使用的错误属于运行时的错误,这意味着要到程序运行阶段才能发现类型错误。
如图所示,常见编程语言的分类:

2,Haskell是什么
- 纯函数式编程语言
- Lazy(缓求值)
- 静态类型
- Type inference
2.1,什么是函数式编程语言:
函数式编程关心数据的映射,命令式编程关心解决问题的步骤。
这里的映射就是数学上「函数」的概念——一种东西和另一种东西之间的对应关系。
在纯粹函数式编程语言中, 不是像命令式语言那样命令电脑「要做什么」,而是通过用函数来描述出问题「是什么」。
举个例子,二叉树的翻转:
## 命令式编程思维的实现,描述了从旧树到新树应该怎么做来实现翻转:
def invertTree(root):
if root is None:
return None
root.left, root.right = invertTree(root.right), invertTree(root.left)
## 函数式编程思维的实现,描述了一个旧树到新树的映射,犹如一个数学公式:
def intert(node):
if node is None:
return None
else:
return Tree(node.value, invert(node.right), invert(node.right))
2.2, 函数式语言的特点:
- 函数是”第一等公民”
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值
-
只用”表达式”,不用”语句”
“表达式”(expression)是一个单纯的运算过程,总是有返回值;”语句”(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
-
没有”副作用”
意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值
-
不修改状态
函数式编程只是返回新的值,不修改系统变量
-
引用透明
指的是函数的运行不依赖于外部变量或”状态”,只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的
函数式语言的优点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 更方便的代码管理,UT
- 易于”并发编程”
- 代码的热升级 Erlang
面向对象編程通过封装不确定因素来使代码能被人理解;函式编程通过尽量减少不确定因素来使代码能被人理解。
3,Show me the code
首先安装GHC
然后命令行ghci
加载haskell代码: :load ~/Desktop/xxx.hs
-- 定义函数,声明(参数a是Num/Ord类型,返回bool)+实现
biggerThanthree :: (Num a,Ord a) => a -> Bool
biggerThanthree a
| a > 3 = True
|otherwise = False
capital :: String -> String
capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
lucky' :: (Integral a) => a -> String
lucky' 7 = "LUCKY NUMBER SEVEN!"
lucky' x = "Sorry, you're out of luck, pal!"
-- 实现阶乘,递归
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
initials :: String -> String -> String
initials firstname lastname = [f]
where (f:_) = firstname
-- 取list中的最大值
maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximum' xs
-- 多个重复数字组成的列表
replicate' :: (Num i,Ord i) => i -> a ->[a]
replicate' n x
| n <= 0 = []
| otherwise = x:some
where some = replicate' (n-1) x
-- replicate 4 3 输出[3,3,3,3]
-- List中取前n个数
take' :: (Num i,Ord i) => i ->[a] -> [a]
take' n []
| n <= 0 = []
take' _ [] = []
take' n (x:xs) = x:take'(n-1) xs
-- 列表反转,递归
reverse' ::[a] ->[a]
reverse' [] = []
reverse' (x:xs) = reverse' xs ++ [x]
repeat' :: a -> [a]
repeat' x = x:repeat' x
-- 实现zip函数 输入两个列表,输出一个合并列表
zip' :: [a] -> [b] -> [(a,b)]
zip' [] _ = []
zip' _ [] = []
zip' (x:xs) (y:ys) = [(x,y)] ++ zip' xs ys
-- zip' [1,2,3,4] [2,2,2,2]
-- 输出[(1,2),(2,2),(3,2),(4,2)]
-- exist
elem' :: (Eq a) => a -> [a] -> Bool
elem' _ [] = False
elem' a (x:xs) = a == x || elem' a xs
elem'' :: (Eq a) => a -> [a] -> Bool
elem'' a [] = False
elem'' a (x:xs)
| a == x = True
| otherwise = a `elem''` xs
-- 快速排序,简洁之美
quicksort' :: (Ord a) => [a] -> [a]
quicksort' [] = []
quicksort' (x:xs) = sm ++ [x] ++ bg
where sm = quicksort'[a | a <- xs ,a <= x]
bg = quicksort'[a | a <- xs ,a > x]
-- 调用两次函数 (a -> a)定义了函数
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
-- applyTwice (+3) 10
-- 输出: 16
-- zip两个列表然后函数映射上去
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
-- zipWith' (+) [1,1,1] [2,2,2]
-- 输出: [3,3,3]
flip' :: (a -> b -> c) -> (b -> a -> c)
flip' f y x = f x y
-- 递归实现map
map' :: (a -> b) -> [a] -> [b]
map' _ [] = []
map' f (x:xs) = f x : map' f xs
-- 递归实现filter
filter' :: (a -> Bool) -> [a] -> [a]
filter' _ [] = []
filter' p (x:xs)
| p x = x : filter' p xs
| otherwise = filter' p xs
largestDivisible :: (Integral a) => a
largestDivisible = head[x | x <- [100000,99999.. ] , x `mod` 3829 ==0]
largestDivisible' :: (Integral a) => a
largestDivisible' = head (filter p [100000,99999..])
where p x = x `mod` 3829 == 0
nini :: (Integral a) => a
nini = sum [x^2 | x <- [10000,9999..1], odd x]
chain :: (Integral a) => a -> [a]
chain 1 = [1]
chain n
| even n = n:chain (n `div` 2)
| odd n = n:chain (n*3 + 1)
sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs
sum'' :: (Num a) => [a] -> a
sum'' = foldl1 (\acc x -> acc + x)
elem''' :: (Eq a) => a -> [a] -> Bool
elem''' y ys = foldl (\acc x -> if x == y then True else acc) False ys
maximum''' :: (Ord a) => [a] -> a
maximum''' = foldl1(\acc x -> if x > acc then x else acc )
reverse'' :: [a] -> [a]
reverse'' = foldl (\acc x -> x : acc) []
4,一个感叹
接触了很多的语言,但也许永远学不会Haskell, 他太独特了,宛若一个艺术品。
在公司分享的时候有同事问写API能不能用Haskell,答案是当然可以,但这就好像开保时捷去开滴滴一样,不适合,那是丰田大众们(Java PHP)干的事情。
对于95%的工程师而言,掌握一门语言,做出不错的产品,实现商业价值,才是正途。不应该花时间纠结编程语言的优劣,那是Geeker们干的事情。