すごい Haskell 楽しく学ぼうの1〜5章の内容をまとめました。
参照透過性
を持つ.とりあえず基本演算をやってみる
ghci> -- 四則演算 ghci> 1 + ( 2 * 3 ) / 4 - 5 -2.5 ghci> -- ブール代数 ghci> not (True && False || True) == False True ghci> True /= True False
す,すげー!!(棒)
*
は関数. 特にこの場合中置関数
.succ 8 => 9
max 8 9 => 9
min 8 9 => 8
8 `max` 9 => 9
if
文も使えるがelse
は必須
ghci> let doubleMe x = x + x ghci> doubleMe 10 20 ghci> doubleMe 1.5 3.0 ghci> --if文も使える 例:100より大きいときTrue, そうでなければFalseを返す関数 ghci> let isGraterThan100 x = if x > 100 then True else False ghci> isGraterThan100 100 False ghci> isGraterThan100 100.1 True
++
演算子
:
(cons)演算子で行う.
!!
演算子ghci> let fibonacciNums1 = [1,1,2,3,5,8,13] ghci> let fibonacciNums2 = [21,34,55] ghci> let fibonacciNums = fibonacciNums1 ++ fibonacciNums2 ghci> fibonacciNums [1,1,2,3,5,8,13,21,34,55] ghci> 55 : fibonacciNums [55,1,1,2,3,5,8,13,21,34,55] ghci> fibonacciNums !! 0 55
すべて前置関数
head
tail
last
init
length
null
reverse
take
drop
maximum
minimum
sum
,積を返す product
elem
ghci> tail [1,2,3,4,5] [2,3,4,5] ghci> 3 `elem` [1,2,3,4,5] True ghci> 6 `elem` [1,2,3,4,5] False
※浮動小数点数を使うとおかしな挙動をするので気をつける
[1..20]
と書けばよい.[3,6..99]
と書けるcycle [2,4..]
と書く.repeat
があるreplicate
があるghci> [1..8] [1,2,3,4,5,6,7,8] ghci> [3,6..33] [3,6,9,12,15,18,21,24,27,30,33] ghci> take 10 (cycle[2,4..]) [2,4,6,8,10,12,14,16,18,20] ghci> replicate 4 5 [5,5,5,5]
{∀2*x , x ∈ N, x ≦ 5}
は[2*x | x <- [1,2,3,4,5]]
と書けるフィルタする
という.even
, odd
関数がある.それぞれ偶数か,奇数かを判断するghci> [x + 2*y| x<-[1,2,3] , y <-[1,2,3] , x + 4*y < 10] [3,5,4,5]
重複をなくしたいときは次のようにする.対話モードだと List モジュールが読み込めない...
import List nub [x + 2*y| x<-[1,2,3] , y <-[1,2,3] , x + 4*y < 10]
ヘテロ
であるという(1,"string", 'c',3.14)
fst
があるsnd
があるzip
があるghci> fst (1, 5) 1 ghci> snd (1, 5) 5 ghci> zip [1,2,3,4,5] [2,4,6,8,10] [(1,2),(2,4),(3,6),(4,8),(5,10)] ghci> zip [1..10][100,200..1000] [(1,100),(2,200),(3,300),(4,400),(5,500),(6,600),(7,700),(8,800),(9,900),(10,1000)]
:set +m
で複数行の式が書けるようになる:{
と :}
で囲むと複数行の式が書けるようになる:quit
で GHCi を終了できる:t <expr>
で式の型を得られる:t <expr>
で式の型を得られる
Int
整数型.有界.64 ビット CPU では Mix:-2^63 Max:2^63-1.Integer
整数型.非有界であるがInt
の方が効率的.Float
単精度浮動小数点数Double
倍精度浮動小数点数Bool
真理値型Char
Unicode 文字.文字のリストは文字列になる.a
とかb
とか.詳細は下記コード例を参照.多相的関数
とよぶ.ghci> -- headはあらゆる型のリストに対して機能する ghci> head [1,2,3,4] 1 ghci> head ["abc","def","ghi"] "abc" ghci> ghci> -- headを自分で実装するときは以下のような具合 ghci> let myHead :: [a] -> a; myHead x = x !! 0 ghci> myHead [3,6,7,9] 3 ghci> ghci> -- fstを自分で実装するときは以下のような具合 ghci> let myFst :: (a,b) -> a; myFst (x, y) = x ghci> myFst (10,55) 10
ghci> :t (==) (==) :: (Eq a) => a -> a -> Bool
==
と/=
Prelude> :t (>) (>) :: Ord a => a -> a -> Bool
>
, <
, >=
, <=
をサポートするPrelude> :t show show :: Show a => a -> String
show
が使われるPrelude> :t read read :: Read a => String -> a Prelude> read "True" || False True Prelude> read "3" + 1 4
Prelude> :t [1 .. 10] [1 .. 10] :: (Enum t, Num t) => [t]
succ
, pred
を実装している()
, Bool
, Char
, Ordering
, Int
, Integer
, Float
, Double
などがあるPrelude> :t minBound minBound :: Bounded a => a Prelude> minBound :: Bool False Prelude> maxBound :: Bool True
minBound
とmaxBound
で調べられる.Bounded a => a
となっているが,このような引数を取らない関数を多相定数
というPrelude> :t 20 20 :: Num a => a Prelude> 20 :: Int 20 Prelude> 20 :: Double 20.0 Prelude> 20 :: Float 20.0
Show
とEq
のインスタンスになっている必要があるPrelude> :t read read :: Read a => String -> a
Prelude> :{ Prelude| let { fact :: Int -> Int Prelude| ;fact 0 = 1 Prelude| ;fact n = n * fact(n-1) Prelude| } Prelude| :} Prelude> fact 3 -- fact 3 = 3! = 3 * 2 * 1 = 6 6
Prelude> :{ -- 2次元ベクトルの足し算(タプルのパターンマッチ) Prelude| let { addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) Prelude| ;addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2) Prelude| } Prelude| :} Prelude> addVectors(1.5, 3.0)(2.5, 2.0) (4.0,5.0) Prelude> Prelude>
Prelude> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)] Prelude> [ x*2 | (x,3) <- xs ] [2,8,10]
head関数
空のリストの先頭は取れないため例外を投げていますが, Haskell ではエラーを引き起こす可能性のある関数は, Maybe などの失敗を安全に扱える方法で実装するのが流儀のようです.
Prelude> :{ Prelude| let { head' :: [a] -> a Prelude| ;head' [] = error "can't call head on an empty list" Prelude| ;head' (x:_) = x Prelude| } Prelude| :} Prelude> head' [1,2,3] 1 Prelude> head' ["hoge", "foo", "hage"] "hoge"
Prelude> :{ Prelude| let { firstLetter :: String -> String Prelude| ;firstLetter "" = "empty" Prelude| ;firstLetter all@(x:xs) = "The first letter of " ++ all ++ "is " ++ [x] Prelude| } Prelude| :} Prelude> firstLetter "toushitsuseigen" "The first letter of toushitsuseigenis t"
|
とそれに続く真理値式,その式が真と評価されたときの関数の本体を記述するotherwise
を置くPrelude> -- BMIに対していちゃもんをつける関数 Prelude> :{ Prelude| let { evalBmi :: Double -> String Prelude| ;evalBmi bmi Prelude| | bmi <= 18.5 = "underweight" Prelude| | bmi <= 25.0 = "normal" Prelude| | bmi <= 30.0 = "fat" Prelude| | otherwise = "whale" Prelude| } Prelude| :} Prelude> evalBmi 10 "underweight" Prelude> evalBmi 19.0 "normal" Prelude> evalBmi 50.0 "whale" Prelude> Prelude> -- 独自定義のmax関数 Prelude> :{ Prelude| let { max' :: (Ord a) => a -> a -> a Prelude| ;max' a b Prelude| | a <= b = b Prelude| | otherwise = a Prelude| } Prelude| :} Prelude> max' 10 100 100 Prelude> max' 100 10 100
where句
を用いて変数に格納することができるPrelude> :{ -- BMIにいちゃもんをつける関数の改良型 Prelude| let { evalBmi :: Double -> Double -> String Prelude| ;evalBmi weight height Prelude| | bmi <= 18.5 = "underweight" Prelude| | bmi <= 25.0 = "normal" Prelude| | bmi <= 30.0 = "fat" Prelude| | otherwise = "whale" Prelude| where bmi = weight / height ^ 2 Prelude| } Prelude| :} Prelude> evalBmi 64 177 "underweight" Prelude> Prelude> -- where内でもパターンマッチを用いることができる Prelude> -- イニシャルを返す関数 Prelude> :{ Prelude| let { initials :: String -> String -> String Prelude| ;initials firstname lastname = [f] ++ ". " ++ [l] ++ "." Prelude| where (f:_) = firstname Prelude| (l:_) = lastname Prelude| } Prelude| :} Prelude> initials "Yoshika" "Miyafuji" "Y. M."
let (bindings) in (expression)
という形式をとるPrelude> :{ Prelude| let { cylinder :: Double -> Double -> Double Prelude| ;cylinder r h = Prelude| let sideArea = 2 * pi * r * h Prelude| topArea = pi * r ^ 2 Prelude| in sideArea + 2 * topArea Prelude| } Prelude| :} Prelude>
Prelude> :{ Prelude| let { head' :: [a] -> a Prelude| ;head' xs = case xs of [] -> error "empty list" Prelude| (x:_) -> x Prelude| } Prelude| :} Prelude> head ["cocoa", "chino", "rize", "chiya", "syaro"] "cocoa"
n=0, 1
を基底部としてF(n) = F(n-1) + F(n-2)
という形に再帰的に定義されるPrelude> :{ -- リストをうけとるmax関数 Prelude| let { max' :: (Ord a) => [a] -> a Prelude| ;max' [] = error "empty list" Prelude| ;max' [x] = x Prelude| ;max' (x:xs) = max x (max' xs) Prelude| } Prelude| :} Prelude> max' [1,2,3,4,5,6,10,2] 10 Prelude> Prelude> :{ -- リストを逆順にする関数 Prelude| let { reverse' :: [a] -> [a] Prelude| ;reverse' [] = [] Prelude| ;reverse' (x:xs) = reverse' xs ++ [x] Prelude| } Prelude| :} Prelude> reverse' "hanayamata" "atamayanah"
Prelude> :{ Prelude| let { quicksort :: (Ord a) => [a] -> [a] Prelude| ;quicksort [] = [] Prelude| ;quicksort (x:xs) = Prelude| let smallerOrEqual = [a | a <- xs, a <= x] Prelude| larger = [a | a <- xs, a > x] Prelude| in quicksort smallerOrEqual ++ [x] ++ quicksort larger Prelude| } Prelude| :} Prelude> quicksort [1,4,2,5,7,2,4,1,5,8,3,2,7,1] [1,1,1,2,2,2,3,4,4,5,5,7,7,8] Prelude> quicksort "Is the order a rabbit?" " ?Iaabbdeehiorrrstt"
max:: Int -> Int -> Int
は実際は「Int
を引数に取り,Int -> Int
である関数」を返す関数関数 f: X * Y -> Z, 関数 g: X -> Y -> Z, について gは fのカリー化された関数である ⇔ ∀x∈X, ∀y∈Y, g(x)(y) = f(x,y)
// max(x,y) function max(x, y) { return x > y ? x : y; } // max(x)(y) function max(x) { return function(y){ return x > y ? x : y; } } max(1)(2) //=> 2 max(2)(1) //=> 2
max(x)(y)
に対して,max(5)(y)
とすれば,5より 5 よりも大きければその値を,そうでなければ 5 を返すような関数を得ることができる部分適用
とよぶmax(x)(y)
に対して,x=5
としてmax(5)(y)
という関数を得ることといえる// max(x)(y) function max(x) { return function(y){ return x > y ? x : y; } } var max5 = max(5) // 部分適用をして関数(max(5))(y)を得た max5(1) //=> 5 max5(10) //=> 10
/
について次のように部分適用できるPrelude> let devideByTen = (/10) Prelude> devideByTen 100 10.0
\ [args] -> [body of a function]
という書式で書ける\x -> 2 * x
と書けるaddThree
とaddThree'
は等価になる(関数はデフォルトでカリー化されている点に注意)addThree :: Int -> Int -> Int -> Int addThree x y z = x + y + z addThree':: Int -> Int -> Int -> Int addThree':: \x -> \y -> \z -> x + y + z
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith' _ [] = [] zipWith' _ _ [] = [] zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys zipWith' max [1,5,1,5] [3,1,3,1] -- => [3,5,3,5]
flip' :: (a -> b -> c) -> (b -> a -> c) flip' f = g where g x y = f y x
map' :: (a -> b) -> [a] -> [b] map' _ [] = [] map' f (x:xs) = f x : map' f xs map' (\x -> 2 * x) [1,2,3,4,5] -- => [2,4,6,8,10]
filter' :: (a -> Bool) -> [a] -> [a] filter' _ [] = [] filter' p (x:xs) | p x = x : fileter p xs | otherwise = filter p xs filter' (>10) [1,5,11,21,1,30,100] -- => [11,21,30,100] filter' (==10) [1,5,11,21,1,30,100] -- => []
foldl
を用いるfoldr
を用いるPrelude> foldl (++) "z" ["a","b","c"] "zabc" -- foldl (++) "z" ["a","b","c"] = -- foldl (++) ("z" ++ "a") ["b", "c"] = -- foldl (++) (("z" ++ "a") ++ "b") ["c"] = -- foldl (++) ((("z" ++ "a") ++ "b") ++ "c") [] = -- ((("z" ++ "a") ++ "b") ++ "c") Prelude> foldr (++) "z" ["a","b","c"] "abcz" -- foldr (++) "z" ["a", "b", "c"] = -- "a" ++ (foldr (++) "z" ["b", "c"]) = -- "a" ++ ("b" ++ (foldr (++) "z" ["c"])) = -- "a" ++ ("b" ++ ("c" ++ (foldr (++) "z" []))) = -- "a" ++ ("b" ++ ("c" ++ "z")) -- このとき ++関数より:関数のほうが処理が早い
foldl1
, foldr1
関数がある// 左畳み込み f (f (f (f z 1) 2) 3) 4 // 右畳み込み f 1 (f 2 (f 3 (f 4 z)))
and' :: [Bool] -> Bool and' xs = foldr (&&) True xs Prelude> and' [True, False, True] -- True && (False && (True && True)) False
foldr
関数は,2番目の引数を常に評価するとは限らない 2 引数関数が与えられた場合に,無限リストに対して動作する(&&) :: Bool -> Bool -> Bool True && x = x False && _ = False Prelude> and' (repeat False) -- False && (False && (False && (... False
scanl
, scanr
関数Prelude> scanl (+) 1 [1,2,3,4] [1,2,4,7,11] Prelude> scanr (+) 1 [1,2,3,4] [11,10,8,5,1]
$
は右結合な関数適用を行う関数($) :: (a -> b) -> a -> b f $ x = f x Prelude> sum (map sqrt [1,2,3]) -- これをsum map sqrt [1,2,3]とは書けない 4.146264369941973 Prelude> sum $ map sqrt [1,2,3] 4.146264369941973
.
関数で関数合成が出来る(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (g x) Prelude> :{ Prelude| let { f :: Int -> Int Prelude| ;f x = x * x -- f(x) = x^2 Prelude| Prelude| ;g :: Int -> Int Prelude| ;g x = x + 1 -- g(x) = x + 1 Prelude| } Prelude| :} Prelude> Prelude> (f.g) 1 -- f(g(2)) = ((2 + 1))^2 9 Prelude> Prelude> Prelude> -- 数のリストをすべて負にする処理 Prelude> -- 全ての数の絶対値をとって符号反転する Prelude> map (\x -> negate (abs x)) [1,-3,5,-7,9] [-1,-3,-5,-7,-9] Prelude> Prelude> -- 関数合成を使うともっとスマートに書ける Prelude> map (negate . abs) [1,-3,5,-7,9] [-1,-3,-5,-7,-9]
f(x) = (+) 1 x
のような関数は f = (+) 1
とすれば良いplusOne :: Int -> Int plusOne x = 1 + x -- ポイントフリースタイルで書くと以下のようになる plusOne' :: Int -> Int plusOne' = (+) 1 Prelude> plusOne' 1 2
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです