Haskell 事始めノート

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

これを見ている。
 

第一章

真理値は True と False。
論理和論理積はそれぞれ || と &&。
a と b が正しくないかは a /= b。(a != b ではない。)

関数呼び出しは引数をカッコで囲まない。また、succ 9 * 10 は 91 ではなくて 100。
div 92 10 は中置関数として呼び出すと 92 `div` 10 と同じ。
関数定義は let doubleUs x y = x * 2 + y * 2 という感じ。関数名の先頭は小文字にする。let は代入

doubleSmallNumber x = if x > 100 then x else x * 2 というような定義も可能。ただし else は必須。Haskell では if は(制御構造ではなく)式。
また、doubleSmallNumber' x = (if x > 100 then x else x * 2) + 1 というような定義も可能。「'」はふつうに関数名として使える。カッコがないと、1 を足すのは x が 100 以下だけのときになってしまうので注意。else の優先順位が低いのである。

リストの定義は let numbers = [1, 4, 5, 3] みたいな感じ。リストの要素の型はすべて同じでなければならない。
リストの連結は ++。[1, 3] ++ [0, 9] は [1, 3, 0, 9] になる。
文字列はじつはリスト。"nyao" は ['n', 'y', 'a', 'o'] と同じ。

リストの先頭への追加は :演算子cons 演算子ともいう)。5:[1, 3, 5] は [5, 1, 3, 5] になる。
リストの比較は辞書順。[3, 4, 1] < [3, 4, 2] は True。
head 関数はリストの先頭を返す。head [5, 1, 4] は 5。last 関数はリストの最後を返す。init 関数は最後を除いたリストを返す。init [1, 9, 3] は [1, 9]。
length 関数はリストの長さ。null 関数はリストが空なら True。reverse 関数はリストを逆順にする。
take 関数は数とリストを引数に取り、リストの先頭から指定された個数だけの要素のリストを返す。take 2 [1, 2, 3, 4, 5] は [1, 2]。drop 関数はリストの先頭から指定された個数だけの要素を削除したリストを返す。drop 2 [1, 2, 3, 4, 5] は [3, 4, 5]。
maximum 関数minimum 関数はそれぞれ最大値と最小値。sum 関数はすべての和、product 関数はすべての積。
elem 関数は要素とリストを引数に取り、リストの中に要素が含まれていれば True。

レンジを使うと、[1..5] は [1, 2, 3, 4, 5] と同じ。
ステップを与えることもできる。[3, 6..10] は [3, 6, 9] と同じ。また [4, 3..1] は [4, 3, 2, 1]。
無限リストは [1..] として作ることができる。これはすべての自然数をならべたリストであり、Haskell遅延評価ゆえに可能になる。実際は take 3 [1..] のように使う。これは [1, 2, 3] を与える。
無限リストを作る関数には cycle, repeat などがある。cycle 関数はリストを受け取ってそれを無限に反復するリストを与える。repeat 関数は 1つの要素を取って無限に反復する。replicate 関数は数と要素を取り、要素を数だの個数だけ反復するそれなので、take 3 (repeat 5) と replicate 3 5 は共に [5, 5, 5] を与える。

リスト内包表記。[x + 2 | x <- [2, 5, 3] ] は [4, 7, 5] を与える。

> let boomBangs xs = [if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]
> boomBangs [7..13]
["BOOM!","BOOM!","BANG!","BANG!"]

みたいなことも可能。odd 関数は奇数が与えられれば True。
また、[x + y | x <- [1, 2, 3], y <- [5, 6, 4] ] は [6, 8, 7] を与える。
length' xs = sum [1 | _ <- xs] は length 関数と同等。「_」は値を捨てるための変数として使っている(ふつうの変数名には使えない)。

タプル。タプルはリストとちがい、要素の型は同じでなくともよい。また、サイズは固定されている。
ちがうサイズのタプルは、別の型とみなされる。サイズ2 のタプルをペア、サイズ3 のタプルをトリプルというが、ペアとトリプルは別の型なのである。
fst 関数はペアを受け取り、最初の要素を返す。snd 関数はあとの要素を返す。
zip 関数は 2つのリストを取り、ペアを作ってリストにする。つまり、

> zip [1, 4, 5] ["nya", "ho"]
[(1, "nya"), (4, "ho")]

である。余った要素は捨てられる。
 
 

第二章

型は REPL の :t コマンドで調べられる。

> :t 'a'
'a' :: Char
> :t True
True :: Bool
> :t "Hello!"
"Hello!" :: [Char]
> :t (False, "ss")
(False, "ss") :: (Bool, [Char])
> :t 4 == 5
4 == 5 :: Bool

という感じ。この「::」は「型である」ことを表す。明示的な型の名前の先頭は必ず大文字である。
例えば「::」で関数の型を宣言することができる。

addTwo :: Int -> Int -> Int
addTwo x y = x + y

という感じ。

型は例えば Int, Integer, Float, Double, Bool, Char など(まだ他にもある)。Int と Integer のちがいは、Integer は有界でないこと(つまり無限大も含む)。

関数の型を REPL で調べてみる。

> :t head
head :: [a] -> a

という感じ。a には何でも入るので、これを型変数と呼ぶ。

型クラスは独特の概念。オブジェクト指向言語のクラスとはちがう。
インスタンスメソッド型クラス制約型注釈などなど、しっかりと理解しよう。
 
 

第三章

パターンマッチはおもしろい!

階乗を求める関数の再帰的な定義。

factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)

case 式を使えば下と同様。ただし case は式なのでどこでも使える。

factorial :: Int -> Int
factorial a = case a of 0 -> 1
                        n -> n * factorial(n - 1)

タプルのパターンマッチ。タプルで表現されたベクトルの和を返す。

addv :: (Double, Double) -> (Double, Double) -> (Double, Double)
addv (x1, y1) (x2 y2) = (x1 + x2, y1 + y2)

リスト内包表記のパターンマッチ。(a, b) <- xs の部分をジェネレータと呼ぶ。

> let xs = [(1, 5), (6, 2), (-5, 3)]
> [a + b | (a, b) <- xs]
[6, 8, -2]

ガード。強力である。
独自の max 関数を定義してみる。与えられた 2つのうち大きい方を返す。

max' :: (Ord a) => a -> a -> a
max' a b
    | a <= b    = b
    | otherwise = a

 
where キーワードを使って fizzbuzz してみる。
fizzbuzz.hs

fizzbuzz :: Int -> String
fizzbuzz n
    | isFizz && isBuzz = "FizzBuzz"
    | isFizz           = "Fizz"
    | isBuzz           = "Buzz"
    | otherwise        = show n
    where isFizz = n `mod` 3 == 0
          isBuzz = n `mod` 5 == 0

REPL で実行。map 関数は第五章で説明される。

> :l fizzbuzz
> map fizzbuzz (take 20 [1..])
["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz","16","17","Fizz","19","Buzz"]

 
let 式。円柱の表面積を求める関数。

cylinder :: Double -> Double -> Double
cylinder r h
    let sideArea = 2 * pi * r * h
        topArea  = pi * r ^ 2
    in  sideArea + 2* topArea

let 式は where キーワードと似ているが、式であるゆえに値をもつ。let 式の内部はローカルスコープであり、「;」で区切ることもできる。
パターンマッチと組み合わせて

> (let (a, b, c) = (1, 2, 3) in a + b + c) * 100
600

ということもできる。