$ brew install ghc
$ brew install --env=std haskell-platform
$ ghci
ghci>
変数とか代入とかないです…。 代わりに束縛があります。
NOTE 束縛=何かへの参照
ghci> let hoge = 1
ghci> hoge
1
型推論は指定された値から型を導く。
ghci> :t hoge
hoge :: Integer
letで束縛した値をin句で利用することができる
ghci> :{
ghci| let a = 1
ghci| b = 2
ghci| in
ghci| a + b
ghci| :}
3
::
以降は型を表すアノテーションInt -> Int
ではInt
型引数を1個とり、Int
型戻り値を返す型を表す。- つまり一番最後が戻り値の型で、それ以外は引数の型となる。
hoge :: Int
hoge = 1
hoge :: Int -> Int
hoge fuga = fuga
(Show a) =>
は型制約。
この場合はaはShowという型である必要がある。Showにはshow関数があり文字列に変換することができる。
hoge :: (Show a) => a
hoge x = show x
NOTE letではなく関数に変数を束縛する方法もある
ちょっと癖がある
ghci> let hoge :: Int -> Int; hoge fuga = fuga
ghci> hoge 1
1
引数がない場合
hoge :: Int
hoge = 1
:t で型情報を調べることができる。
ghci> :t hoge
hoge :: Int
引数が1個の場合
hoge :: Int -> Int
hoge x = x
ghci> :t hoge
hoge :: Int -> Int
ghci> :t hoge 1
hoge 1 :: Int
引数が2つの場合
fuga :: Int -> Int -> Int
fuga x y = x + y
ghci> :t fuga
fuga :: Int -> Int -> Int
ghci> :t fuga 1
fuga 1 :: Int -> Int
ghci> :t fuga 1 2
fuga 1 2 :: Int
ghci> fuga 1 2
3
ghci> (fuga(1) 2)
3
引数は左側に結合します。(左結合)
ということでこんなことができます。
ghci> let fuga_1 = fuga 1
ghci> fuga_1 2
3
あれ?fuga(1)
は関数? はい。そうです。
例えば、2つの引数を持つ関数は、1つの引数をとり、「一つの引数をとる関数」を返す関数と同義です。関数を返すことですべての関数を一つの引数の関数として表現することをカリー化という。
(fuga(1) 2)
本来より少ない引数で呼び出した時に部分適用された関数が得られます。その関数を使って残りの引数を渡せば関数の処理が可能です。
fuga(1)
add :: Int -> Int -> Int
add x y = x + y
multi :: Int -> Int -> Int
multi x y = x * 2
ghci> multi 1 (add 2 1)
は
ghci> multi 1 $ add 2 1
と書くことができます。$は次のとおりになっています。
($) :: (a -> b) -> a -> b
f $ x = f x
Char | 文字です。シングルクオートで囲みます。 |
Int | 範囲が決まった整数です。少なくとも (- 229) --- (229 - 1) をカバーします。 |
Integer | 任意精度の整数 |
Float | IEEE 単精度 |
Double | IEEE 倍精度の浮動小数点 |
Bool | 真偽値です。True と False があります。 |
NOTE Stringは[Char]のエイリアス
ghci> [1,2,3,4,5]
[1,2,3,4,5]
レンジ指定
ghci> [1..5]
[1,2,3,4,5]
ステップ指定
ghci> [1,3..10]
[1,3,5,7,9]
ghci> [1, 2, 3, 4] ++ [9, 10, 11, 12]
[1,2,3,4,5,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world!"
ghci> ['w','o'] ++ ['o','t']
"woot"
ghci> [1, 2, 3] !! 2
3
ghci> head [5, 4, 3, 2, 1]
5
ghci> tail [5, 4, 3, 2, 1]
[4,3,2,1]
ghci> last [5, 4, 3, 2, 1]
1
ghci> init [5, 4, 3, 2, 1]
[5,4,3,2]
headとtailを結合できる
ghci> 'A' : " B C"
"A B C"
ghci> 1 : [2,3,4,5]
[1,2,3,4,5]
ghci> length [5, 4, 3, 2, 1]
5
ghci> null []
True
ghci> null [1, 2, 3]
False
ghci> reverse [5, 4, 3, 2, 1]
[1,2,3,4,5]
ghci> elem 4 [5, 4, 3, 2, 1]
True
中置記法での表現
ghci> 4 `elem` [5, 4, 3, 2, 1]
True
ghci> 4 `elem` [3, 2, 1]
False
記号は``で囲まなくても中置記法できます。
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 | x <- [50..100], x `mod` 7 == 3]
[52,59,66,73,80,87,94]
ghci> boomBangs xs = [if x < 10 then "BOOM" else "BANG!" | x <- xs, odd x]
ghci> boomBangs [7..13]
["BOOM!", "BOOM!", "BANG!", "BANG!"]
複数のリストを利用する
ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]
ghci> [ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50]
[55,80,100,110]
ghci> (1,2)
(1,2)
ghci> (1,2,3)
(1,2,3)
ghci> fst (1, 2)
1
ghci> snd (2, 3)
2
マップはData.Mapを使う。
import Data.Map
let map = fromList [("Taro", 20), ("Jiro", 17), ("Saburo", 15)]
mapM_ putStrLn [key ++ " => " ++ (show value) | (key, value) <- toList map]
lookupを使えばキーから値を取得できる。値はMaybeでラップされている。
import Prelude hiding (lookup)
import Data.Map
let map = fromList [("Taro", 20), ("Jiro", 17), ("Saburo", 15)]
let value = lookup "Jiro" map -- Just 17
ghci> import Data.Ratio
ghci> 1 % 4 + 2 % 3
11 % 12
ghci> import Data.Complex
ghci> (3 :+ 1) + (1 :+ 2)
4.0 :+ 3.0
- if式は値を返します
- elseが必須です( ー`дー´)キリッ
doubleSmallNumber x = if x > 100
then x
else x * 2
引数のパターンにマッチさせて戻り値を返す。
getPosition :: Int -> Maybe String
getPosition 0 = Nothing
getPosition 1 = Just "平社員"
getPosition 2 = Just "係長"
getPosition 3 = Just "課長"
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
@
を用いて、パターンに名前をつけることができる
firstChar :: String -> String
firstChar "" = ""
firstChar all@(x:xs) = all ++ "'s first char is " ++ [x]
_
がワイルドカード。
head :: [a] -> a
head (x:_) = x
tail :: [a] -> [a]
tail (_:xs) = xs
evalBmi :: Double -> String
evalBmi bmi
| bmi <= 18.5 = "低体重(痩せ型)"
| bmi <= 25.0 = "普通"
| bmi <= 30.0 = "肥満(1度)"
| bmi <= 35.0 = "肥満(2度)"
| bmi <= 40.0 = "肥満(3度)"
| otherwise = "肥満(4度)"
evalBmi :: Double -> Double -> String
evalBmi weight height
| bmi <= 18.5 = "低体重(痩せ型)"
| bmi <= 25.0 = "普通"
| bmi <= 30.0 = "肥満(1度)"
| bmi <= 35.0 = "肥満(2度)"
| bmi <= 40.0 = "肥満(3度)"
| otherwise = "肥満(4度)"
where
bmi = weight / height ^ 2
take m ys = case (m,ys) of
(0,_) -> []
(_,[]) -> []
(n,x:xs) -> x : take (n-1) xs
再帰で行います。
数値を持つリストを単一の文字列に結合する場合
ghci> listToString [1, 2, 3, 4, 5]
"12345"
これでもよいが、スタックオーバーフローする場合がある…。
listToString :: (Show a) => [a] -> String
listToString [] = ""
listToString (x:xs) = show x ++ listToString xs
末尾再帰版その1
listToString :: (Show a) => [a] -> String
listToString xs = listToString0 xs ""
listToString0 :: (Show a) => [a] -> String -> String
listToString0 [] r = r
listToString0 (x:xs) r = listToString0 xs $ r ++ show x
末尾再帰版その2
関数定義がまとまっていて見やすい。
listToString :: (Show a) => [a] -> String
listToString xs = f xs ""
where
f [] r = r
f (y:ys) r = f ys $ r ++ show y
foldr(畳込み関数)を使う方法
ghci> foldr (\a b -> a ++ b) "" $ map show [1, 2, 3]
"123"
ghci> foldr (++) "" $ map show [1, 2, 3]
"123"
通常の関数定義は次のとおり。
add :: Int -> Int
add x = x + 1
これをラムダ式を使って、無名関数として定義する。そして引数を適用する。
ghci> (\x -> x + 1) 1
2
省略形で記述。
ghci> (+1) 1
2
add :: Int -> Int
add = (\x -> x + 1)
add :: Int -> Int
add = (+1)
ghci> add 1
2
hoge = let add = \x -> x + 1
in add 1
REPLの場合
let add :: Int -> Int ; add = (\x -> x + 1)
数学における関数合成がHaskellにも定義されています。
ghci> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
f (g (x))
が (f . g)(x)
で書くことができます。
利用例は次のとおり。
(putStrLn . fizzbuzz)
は 関数合成。(\n -> putStrLn $ fizzbuzz n)
と等価です。
fizzbuzz :: Int -> String
fizzbuzz n
| n `mod` 15 == 0 = "fizzbuzz"
| n `mod` 3 == 0 = "fizz"
| n `mod` 5 == 0 = "buzz"
| otherwise = show n -- showはStringに変換する関数
main = mapM (putStrLn . fizzbuzz) [1..100]
Haskellはデフォルトで遅延評価を採用している。他の命令型言語は先行評価だが、Haskellの場合は式が必要になるまで評価されない。Haskellの関数は数学的な関数であり、このような関数はいつ呼び出しても引数に対して戻り値の結果が変わらない特性を持っている。なので必要になるまで評価しないのは、実行時の性能を最適化するのに良い方法といえる。
answer = const 42 (1 `div` 0)
constは常に第一引数を返すので、(1 `div` 0)
は評価されないのでエラーにならない。
Haskellには構造体や列挙型の性質を兼ね備えたデータ構造を簡単に定義することができる。
data Person
のPersonは型コンストラクタという。= Person ...
のPersonは値コンストラクタという。
ghci> data Person = Person String String deriving (Show)
ghci> Person "Junichi" "Kato"
Person "Junichi" "Kato"
ghci> data Color = Red | Blue | Yellow deriving (Show)
ghci> :t Red
Red :: Color
ghci> data Shape = Circle Int | Rectangle Int Int Int Int
ghci> data Maybe a = Nothing | Just a
ghci> :t Just "abc"
Just "abc" :: Maybe [Char]
data Person = Person String String
firstName :: Person -> String
firstName (Person f _) = f
lastName :: Person -> String
lastName (Person _ l) = l
ghci> firstName $ Person "Junichi" "Kato"
"Junichi"
これは正直うざいので、こういうときはレコード構文を使う。
data Person = Person { firstName :: String, lastName :: String } deriving (Show)
ghci> firstName Person { firstName = "Junichi", lastName = "Kato" }
"Junichi"
ghci> firstName $ Person "Junichi" "Kato"
"Junichi"
head関数はaのリストを引数にとり、a型の戻り値を返す。aのことを型変数と呼ぶ。
ghci> :t head
head :: [a] -> a
型に対して、何らかの振る舞いを定義するインターフェイスです。ある型に対して適用する関数の集合を定義したもの。
Eq型クラスは次のように定義されています。classと定義されているけど、Javaのinterface宣言ように読むと良い。実装も書くことができます。Scalaのtraitのようだがmixinができるわけではない。
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
==
演算子は、任意の型を2つとり、等値性を評価し結果をBoolで返します。ただし、aはEq型クラスのインスタンスでなければなりません。(Eq a) =>
は型クラス制約という。
ghci> :t (==)
(==) :: (Eq a) => a -> a -> Bool
Int型に対応した型クラスが定義されているので次の操作は可能。
ghci> 1 == 1
True
ghci> 1 /= 1
False
Int型に対応するEq型クラスの実装は次のとおり。
Eq a
のaの部分がInt
なのでxとyはInt型ということになる。
instance Eq Int where
x (==) y = x == y
x (/=) y = x /= y
Eq型クラスはいろいろな型に対応している。便利!
ghci> 'a' == 'a'
True
ghci> 3.14 == 3.14
True
値を比較するための型クラス。
ghci> :t (>)
(>) :: (Ord a) => a -> a -> Bool
Eq型クラスと同様にいろいろな型にて適用できる。
ghci> 2 > 1
True
ghci> 1 > 2
False
ghci> "Abrakadabra" < "Zebra"
True
Showは文字列に変換するための型クラス。
ghci> :t show
show :: Show a => a -> String
いろいろな型に対応している。
ghci> show 1
"1"
ghci> show 3.14
"3.14"
ghci> show "a"
"\"a"\"
文字列を読み込んである型に変換するための型クラス。
ghci> :t read
read :: Read a => String -> a
型推論ができない場合は型アノテーションを付与する。
ghci> read "1" + 1
2
ghci> read "1" :: Int
1
ghci> read "8.2" + 3.8
12.0
ghci> read "5" - 2
3
derivingは、derive = 自動導出の動名詞。キーワードの後ろで指定した型クラスを自動的に作ります。
data Person = Person { firstName :: String, lastName :: String } deriving (Show,Eq,Ord)
ShowはREPLでインスタンスが表示される時にも利用される。
ghci> Person "Junichi" "Kato"
Person "Junichi" "Kato"
Showが指定されていないとREPLでインスタンスの内容を表示する際にエラーになる。
ghci> data Person = Person String String
ghci> Person "Junichi" "Kato"
<interactive>:14:1:
No instance for (Show Person)
arising from a use of `print'
Possible fix: add an instance declaration for (Show Person)
In a stmt of an interactive GHCi command: print it
Eq,Ordの例は次のとおり。
ghci> Person "Junichi" "Kato" == Person "Junichi" "Kato"
True
ghci> Person "Junichi" "Kato" == Person "junichi" "kato"
False
ghci> Person "Junichi" "Kato" > Person "" ""
True
ghci> Person "Junichi" "Kato" > Person "Junichi" "Kato"
False