Haskell入门笔记

最近在学习haskell,想要要深入的了解一下函数式编程(functional programming),朋友建议可以通过学习这一门语言来加深理解。所以就尝试一下。
learnyouahaskell.com是一个非常不错的入门教程,适合有一定的编程基础的人阅读。电子版可以免费在线阅读

String

'' 和 "的区别

List

字符串也是List,List的拼接可以使用 ++

1
2
3
4
5
6
ghci> [1,2,3,4] ++ [9,10,11,12]  
[1,2,3,4,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world"
ghci> ['w','o'] ++ ['o','t']
"woot"

高效率的拼接 使用操作符:

1
2
3
4
ghci> 'A':" SMALL CAT"  
"A SMALL CAT"
ghci> 5:[1,2,3,4,5]
[5,1,2,3,4,5]

!! 根据索引值取List元素, 索引值从0开始

1
2
3
4
*Main> "hello world"!!0
'h'
*Main> [1,2,3,4,5,6]!!3
4

取出大于List长度的会报错

1
2
*Main> [1,2,3,4,5,6]!!7
*** Exception: Prelude.(!!): index too large

List的比较, 比较的方式是 lexicographical order

1
2
3
4
5
6
7
8
9
10
ghci> [2,1] > [1,2]
True
ghci> [3,2,1] > [2,10,100]
True
ghci> [3,4,2] > [3,4]
True
ghci> [3,4,2] > [2,4]
True
ghci> [3,4,2] == [3,4,2]
True

List有一系列的函数可以使用

  • tail
  • last
  • init
  • head
  • length 取List的长度 length [5,4,3,2,1] == 5
  • null 检查List是否为空 null [] == True
  • take 获取从头部开始的n个元素 take 3 [1,2,3,4] 获取到 [1,2,3]
  • reverse 翻转数组
  • minimum 最小值
  • maximum 最大值
  • sum
  • elem 检查一个元素是否在List中 4 elem [3,4,5,6] == True

注意:这几个函数用于空List的时候会报错

1
2
ghci> tail []
*** Exception: Prelude.tail: empty list

集合的方式来理解List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ghci > [x*2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]

ghci> [x*2 | x <- [1..10], x*2 >= 12]
[12,14,16,18,20]

ghci> [x*2+1 | x <- [0..10]]
[1,3,5,7,9,11,13,15,17,19,21]

ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]

//排除 3, 4
ghci> [ x | x <- [1..5], x /= 3, x /= 4]
[1,2,5]

//嵌套的处理
ghci> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
ghci> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]

元组 Tuples

元组的元素类型可以不一致, 而List是要求元素是同类型的。 例如

1
('Name', 50)

 

稍微负责一点的元组

1
2
ghci> [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]
[(3,4,5),(6,8,10)]

 

元组同样有一系列的函数,常用的有

fst, 取出元组首个元素

snd 取出元组第二个元素

zip 一个非常有用的函数,将两个List的对应元素合并为元组,并构成一个List

1
2
ghci> zip [1 .. 5] ["one", "two", "three", "four", "five"]
[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]

 

Type And TypeClasses

Type

Haskell是一个强类型的语言,有CharIntegerIntFloat,Double,Bool等基本类型

Typeclasses

类限制 class constraint , => 这个符号代表类限制

1
2
ghci> :t (==)  
(==) :: (Eq a) => a -> a -> Bool

 

the equality function takes any two values that are of the same type and returns a Bool. The type of those two values must be a member of the Eq class (this was the class constraint). 在标准的Haskell中,除了了io和function之外,其他类型都是属于Eq

Haskell的typeclass有 EqOrdShow,Read,EnumBounded

Eq
Ord
Show
Read
1
2
3
4
5
6
7
8
9
10
hci> read 4

<interactive>:94:6:
Could not deduce (Num String) arising from the literal ‘4’
from the context (Read a)
bound by the inferred type of it :: Read a => a
at <interactive>:94:1-6
In the first argument of ‘read’, namely ‘4’
In the expression: read 4
In an equation for ‘it’: it = read 4

需要使用类型声明 (type annotations),Haskell才有办法正确的解析,或者是在表达式中,Haskell自己进行推断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ghci> read "4" :: Int
5

ghci> read "[1,2,3,4]" :: [Int]
[1,2,3,4]

ghci> 10 + read "5"
15

ghci> read "[1,2]" ++ [4]
[1,2,4]

ghci> read "(1, '2')" :: (Int, Char)
(1,'2')

Syntax in Functions

Pattern Matching

1
2
3
lucky :: (Integral a) => a -> String  
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"

如果输入值是7,match到第一个pattern,不是7,那么就会match到第二个pattern。所以,在定义pattern的时候,次序是很重要的,先定义特例pattern,在定义普遍的pattern

再看下面这个例子,

1
2
3
factorial :: (Integral a) => a -> a  
factorial 0 = 1
factorial n = n * factorial (n - 1)

The x:xs pattern

1
2
3
head' :: [a] -> a  
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x

xs@(x:y:ys) @符号 表示 xs 代表的就是 (x:y:ys), 这样可以写起来更加简单一些。

1
2
3
capital :: String -> String  
capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

Gurard

看完下面这个例子,基本就可以理解Guard是怎么工作的。不需要介绍太多。需要的注意的是 where里面定义的变量只对于该函数有作用。

1
2
3
4
5
6
7
8
9
10
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0

还可以类似于下面用3元组的方式

1
2
3
...  
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)

let <bindings> in <expression>

1
2
ghci> 4 * (let a = 9 in a + 1) + 2  
42

introduce functions in a local scope:

1
2
ghci> [let square x = x * x in (square 5, square 3, square 2)]  
[(25,9,4)]

多个表达式用分号隔开

1
2
ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)  
(6000000,"Hey there!")

使用let和List Comprehension来对bmi的例子进行改写, 注意,在List Comprehension中使用时,in是可以省略的,因为名字的可见性已经提前定义好了。

1
2
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]

如果不使用in来限定范围,则是全局可见的

1
2
3
4
5
6
7
ghci> let zoot x y z = x * y + z  
ghci> zoot 3 9 2
29
ghci> let boot x y z = x * y + z in boot 3 4 2
14
ghci> boot
<interactive>:1:0: Not in scope: `boot'

case

1
2
3
4
case expression of pattern -> result  
pattern -> result
pattern -> result
...

这一段表达式其实是case的一个语法糖

1
2
3
head' :: [a] -> a  
head' [] = error "No head for empty lists!"
head' (x:_) = x

如果不使用语法糖,则是

1
2
3
head' :: [a] -> a  
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x

Higher order functions

haskell,函数可以作为参数,也可以作为返回值

Curried functions

之前在js中就比较熟悉这个用法,所以接受起来很简单,不过haskell比javascript的定义方式优雅太多了。以haskell中的max函数为例.

1
2
3
4
ghci> max 4 5  
5
ghci> (max 4) 5
5

 

max函数的定义的定义是 max :: (Ord a) => a -> a -> a

That can also be written as max :: (Ord a) => a -> (a -> a). That could be read as: max takes an a and returns (that’s the ->) a function that takes an a and returns an a. That’s why the return type and the parameters of functions are all simply separated with arrows.

举一个例子来看看这种定义方式的强大之处

1
2
multThree :: (Num a) => a -> a -> a -> a  
multThree x y z = x * y * z

运行一下看看

1
2
3
ghci> let multTwoWith9 = multThree 9
ghci> multTwoWith9 2 3
54

在举一个例子:

1
2
compareWithHundred :: (Num a, Ord a) => a -> Ordering  
compareWithHundred = compare 100

Compare has a type of (Ord a) => a -> (a -> Ordering) and calling it with 100 returns a(Num a, Ord a) => a -> Ordering.

一个我第一次看稍难理解的一个例子

1
2
applyTwice :: (a -> a) -> a -> a  
applyTwice f x = f (f x)

运行

1
2
3
4
5
6
7
8
9
10
ghci> applyTwice (+3) 10  
16
ghci> applyTwice (++ " HAHA") "HEY"
"HEY HAHA HAHA"
ghci> applyTwice ("HAHA " ++) "HEY"
"HAHA HAHA HEY"
ghci> applyTwice (multThree 2 2) 9
144
ghci> applyTwice (3:) [1]
[3,3,1]

zipwith

1
2
3
4
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]  
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
1
2
3
4
5
6
7
8
9
10
ghci> zipWith' (+) [4,2,5,6] [2,6,2,3]  
[6,8,7,9]
ghci> zipWith' max [6,3,2,1] [7,3,1,5]
[7,3,2,5]
ghci> zipWith' (++) ["foo ", "bar ", "baz "] ["fighters", "hoppers", "aldrin"]
["foo fighters","bar hoppers","baz aldrin"]
ghci> zipWith' (*) (replicate 5 2) [1..]
[2,4,6,8,10]
ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]]
[[3,4,6],[9,20,30],[10,12,12]]

filter

1
2
3
4
5
filter :: (a -> Bool) -> [a] -> [a]  
filter _ [] = []
filter p (x:xs)
| p x = x : filter p xs
| otherwise = filter p xs
1
2
3
4
5
6
7
8
9
10
11
12
ghci> filter (>3) [1,5,3,2,1,6,4,3,2,1]  
[5,6,4]
ghci> filter (==3) [1,2,3,4,5]
[3]
ghci> filter even [1..10]
[2,4,6,8,10]
ghci> let notNull x = not (null x) in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]]
[[1,2,3],[3,4,5],[2,2]]
ghci> filter (`elem` ['a'..'z']) "u LaUgH aT mE BeCaUsE I aM diFfeRent"
"uagameasadifeent"
ghci> filter (`elem` ['A'..'Z']) "i lauGh At You BecAuse u r aLL the Same"
"GAYBALLS"
1
ghci> sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

list comprehesion vs filter

1
2
3
4
ghci> [n | n <- [1..10], odd n]
[1,3,5,7,9]
ghci> filter odd [1..10]
[1,3,5,7,9]

chain数列

1
2
3
4
5
6
7
8
9
10
chain :: (Integral a) => a -> [a]
chain 1 = [1]
chain n
| even n = n:chain (n `div` 2)
| odd n = n:chain (n * 3 + 1)


numLongChains :: Int
numLongChains = length (filter isLong (map chain [1..100]))
where isLong xs = length xs > 15

Lambdas

使用符号\来表示,代表这是一个匿名函数,一次性使用。

将前面提及的numLongChains函数进行改写

1
2
numLongChains :: Int  
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))

其中 (\xs -> length xs > 15) 就是lambdas

下面我们看使用 partial 和 lambda的对比

1
2
3
4
5
ghci> map (+3) [1,6,3,2]
[4,9,6,5]
ghci> map (\x -> x +3) [1,6,3,2]
[4,9,6,5]
ghci>

在这个例子中,partial会是一个更好的选择。可读性更高

更多例子

1
2
3
4
ghci> zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]
[153.0,61.5,31.0,15.75,6.6]
ghci> map (\(a,b)-> a+b) [(1,2),(3,4)]
[3,7]

fold up list foldl

Folds can be used to implement any function where you traverse a list once, element by element, and then return something based on that. Whenever you want to traverse a list to return something, chances are you want a fold.

使用foldl对sum函数重新改写

1
2
sum' :: (Num a) => [a] -> a  
sum' xs = foldl (\acc x -> acc + x) 0 xs
1
2
ghci> sum' [3,5,2,1]
11

acc x
0 3
3 5
8 2
10 1
11

更简洁的写法

1
2
sum' :: (Num a) => [a] -> a  
sum' = foldl (+) 0

其中可以改写为这样子的形式有一个原因就是在haskell中 foo a = bar b a 可以简写为 foo = bar b
上例中就是 sum' xs = foldl (+) 0 xs 的简写

另外一个例子,使用 foldl来实现 elem 函数

1
2
elem' :: (Eq a) => a -> [a] -> Bool  
elem' y ys = foldl (\acc x -> if x == y then True else acc) False ys

acc的初始值是False,也就是不存在,如果x等于y就返回 True,不等于就保持acc

fold是非常强大的,书中重新实现了许多基础函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
maximum' :: (Ord a) => [a] -> a  
maximum' = foldr1 (\x acc -> if x > acc then x else acc)

reverse' :: [a] -> [a]
reverse' = foldl (\acc x -> x : acc) []

product' :: (Num a) => [a] -> a
product' = foldr1 (*)

filter' :: (a -> Bool) -> [a] -> [a]
filter' p = foldr (\x acc -> if p x then x : acc else acc) []

head' :: [a] -> a
head' = foldr1 (\x _ -> x)

last' :: [a] -> a
last' = foldl1 (\_ x -> x)

head的实现最好还是之前用的pattern match,这里只是为了举例

scanl 和 scanr 与 foldl 和foldr是相似的,只是scan会输出每一个阶段的累计值

1
2
3
4
5
6
7
8
ghci> scanl (+) 0 [3,5,2,1]  
[0,3,8,10,11]
ghci> scanr (+) 0 [3,5,2,1]
[11,8,3,1,0]
ghci> scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3,7,9,2,1]
[3,4,5,5,7,9,9,9]
ghci> scanl (flip (:)) [] [3,2,1]
[[],[3],[2,3],[1,2,3]]

需要多少个自然数才能使其开方的和大于1000

length (takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))) + 1

如何理解呢?

执行takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))得到的结果是

1
[1.0,2.414213562373095,4.146264369941973,6.146264369941973,8.382332347441762,10.83182209022494,13.47757340128953,16.30600052603572,19.30600052603572,22.4682781862041,25.7849029765595,29.249004591697254,32.854555867161245,36.59621325393519,40.46919660014261,44.46919660014261,48.59230222576027,52.834942912879555,57.19384185642023,61.66597781141981,66.24855350637564,70.93896926619907,75.73480078951178,80.63378027507814,85.63378027507814,90.73279978867092,95.92895221137755,101.22045483350674,106.60561964064124,112.0828452156929,117.65060957852292,123.3074638280153,129.05202647455332,134.88297836939861,140.79905815249822,146.79905815249822,152.88182068279644,159.04623468576543,165.29123268416382,171.6157880045006,178.01891224193344,184.4996529403413,191.0570914646433,197.6903410453541,204.39854497785348,211.18087496097874,218.03652956137978,224.96473279165528,231.96473279165528,239.03580060352076,246.1772290320636,253.38833158299158,260.6684414722721,268.0169107006216,275.43310918771726,282.91642396126514,290.46625839653586,298.0820315023998,305.76317725026837,313.5091439426832,321.31939361858986,329.19340149260165,337.1306554257954,345.1306554257954,353.192913174094,361.3169515787299,369.50230435060234,377.74851560183765,386.0551394647557,394.4217397300965,402.8478895032728,411.3331708775114,419.87717462282893,428.47949988987153,437.1397539277159,445.85755181479726,454.6325162021894,463.4642770685172,472.3524714858328,481.2967433958319,490.2967433958319,499.35212853396933,508.4625621131136,517.6277135030253,526.8472579603182,536.1208764558139,545.4482555089028,554.8290870285497,564.2630681606063,573.7499011411114,583.2892931552809,592.8809562019063,602.5246069628993,612.2199666777319,621.9667610225409,631.7647199936737,641.6135777954698,651.5130727320815,661.4629471031477,671.4629471031477,681.5128227242686,691.6123276626307,701.7612192277229,711.9592582549085,722.2062090208681,732.5018391618551,742.8459195946436,753.2382244400569,763.6785309489675,774.166619430669,784.7022731835218,795.2852784277802,805.9154242405149,816.5925024925461,827.3163077873097,838.0866374015787,848.9032912279707,859.7660717191709,870.6747838338066,881.6292349839099,892.6292349839099,903.6745960010971,914.7651325075066,925.9006612331666,937.0810011206655,948.3059732809874,959.575400950572,970.8891094495567,982.2469261411572,993.6486803921487]

取得 这个数组的长度 + 1之后就是解了。注意,这里不能使用filter,因为filter无法用于无限的数组

Function application with $

理解这个符号$更好的方式是看例子

sqrt (3 + 4 + 9) 与 sqrt 3 + 4 + 9 是不等价的,但是 可以使用 $ 来表示 符号右侧的表达式为左侧的输入值 sqrt $ 3 + 4 + 9

1
2
3
4
5
6
ghci> sqrt $ 3 + 4 + 9
4.0
ghci> sqrt (3 + 4 + 9)
4.0
ghci> sqrt 3 + 4 + 9
14.732050807568877

再复杂一丢丢:sum (filter (> 10) (map (*2) [2..10])) 要怎么改写呢?

首先 filter (> 10) (map (*2) [2..10]) 可以改写为 filter (>10) $ map (*2) [2..10]
继而可以改写为 sum $ filter (> 10) $ map (*2) [2..10]

Function composition

使用符号.来表示函数的组合,f (g (x)) = (f.g) x直接从例子中进行理解 negate . (* 3) 表示的是 乘于 3 让后变为负数

1
2
3
4
ghci> (negate . (* 3)) 4
-12
ghci> map (negate . (* 3)) [1,2,3]
[-3,-6,-9]

而组合不限于两个函数,可以多个组合 f (g (z x)) = (f . g . z) x

1
2
ghci> map (negate . sum . tail) [[1..5],[3..6],[1..7]]
[-14,-15,-27]

上面举的例子都是只有一个参数的,如果是多个参数呢?答案就是 partial

例如 sum (replicate 5 (max 6.7 8.9))可以改写为 (sum . replicate 5 . max 6.7) 8.9 对与这个我目前不是很理解,不过还好作者指出了一个技巧,改写replicate 100 (product (map (*3) (zipWith max [1,2,3,4,5] [4,5,6,7,8]))) 看起来很复杂,但其实很简单 replicate 100 . product . map (*3) . zipWith max [1,2,3,4,5] $ [4,5,6,7,8]

那么这个函数组合到底对实际编程有什么用呢?看下面这个例子

1
2
oddSquareSum :: Integer  
oddSquareSum = sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

可以改写为

1
2
3
oddSquareSum :: Integer  

oddSquareSum = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..]

如果为了更好的可读性, 可以使用let来进行可读性的改善

1
2
3
4
5
oddSquareSum :: Integer  
oddSquareSum =
let oddSquares = filter odd . map (^2) $ [1..]
bellowLimit = takeWhile (<10000) oddSquares
in sum bellowLimit

modules

加载模块

加载模块的语法: import <module name>

1
2
3
4
5
6
7
import Data.List

numUniques :: (Eq a) => [a] -> Int
numUniques = length . nub
-- 与下面两种写法是等价的
-- numUniques xs = length . nub $ xs
-- nu mUniques xs = length (nub xs)

在命令行模式下想要使用模块可以使用 :m + <module name>,如果有多个模块用空格隔开 例如 :m + Data. List

只需要 import 其中的几个函数

1
import Data.List (nub, sort)

import 的时候排除某个函数

1
import Data.List hiding (nub)

如果模块之间出现同名函数,可以通过 qualified来指定

1
2
3
import qualified Data.Map  
-- 或者,命名空间太长,可以使用缩写
import qualified Data.Map as M

如果想要使用 Data.Map的filter函数,就需要通过 Data.Map.filter 或者 M.filter 进行调用,而filter 则会使用我们熟悉的那个 filter,来自于 Prelude 模块,该模块会预加载。
想要查询函数可以使用 hoogle

Data.List

Data.List中有一些很有用的函数
intersperse,intercalateconcat concatMaptakeWhiledropWhile等等,下面列出一些对我有启发的函数:

and 和 or

and $ map (==4) [2,3,4,5,6,1]得到False 和 or $ map (==4) [2,3,4,5,6,1] 得到True

any 和 all

例如 any (==4) [1,2,3,4] 得到True, 而

1
all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"

得到 False

transpose

transpose的用法对于我比较新奇 , 看书中的例子

1
2
3
4
ghci> transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]
[[0,10,8],[3,0,5],[5,0,1],[9,9,-1]]
ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]
[18,8,6,17]

iterate

groupsort

1
2
ghci> map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
[(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]

记得@的用法吗? l@(x:xs) 表示 l 代表 (x:xs)

tails 和 inits

用tails实现字符串的search

1
2
3
4
search :: (Eq a) => [a] -> [a] -> Bool  
search needle haystack =
let nlen = length needle
in foldl (\acc x -> if take nlen x == needle then True else acc) False (tails haystack)

首先 tails 执行得到 一个数组,例如 tails "abcde" 得到

1
["abcde","bcde","cde","de","e",""]

然后使用foldl进行累积。 上面的实现其实就是函数isInfixOf所做的事情, 类似的字符串函数还有 isPrefixOf 和 isSuffixOf

partition , span 和 break

1
2
3
4
5
6
hci>  partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
("BOBMORGAN","sidneyeddy")
ghci> span (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
("BOB","sidneyMORGANeddy")
ghci> break (`elem` ['a'..'z']) "BOBsidneyMORGANeddy"
("BOB","sidneyMORGANeddy")

寻找相似图片算法
posted @ 2017-07-27 17:37  天涯海角路  阅读(991)  评论(0)    收藏  举报