Yet Another Haskell 3章 (5)
3.5 Functions
関数に関して
関数の定義
関数の定義は、関数名 引数 = 式 の形で書く。
例えば、ある数を2乗する関数は次のように書ける。
square x = x * x
if then else
条件分岐はif then elseを使って書ける。
例えば、
signum x = if x < 0 then -1 else if x > 0 then 1 else 0
case of
C言語等にあるswitch文に相当するものはcase ofを使って書ける。
f x = case x of 0 -> 1 1 -> 5 2 -> 2 _ -> -1
ここで、関数fは引数xが、0のときは1を、1のときは5を、2のときは2を、それ以外の場合は、-1を返す関数となる。
_はワイルドカードである。
インデント
case ofの例では、インデントが重要である。
Haskellでは、Pythonのように、インデントを使うことで{}を省略することができる。
一般的には、where, let, do, ofの後に、{が挿入され、次のコマンドの現れる位置が記憶される。
その後、同じインデントの行が現れる度に、;が挿入され、インデントが少なくなった位置で、}が挿入される。
一般的な書き方をしている限り、この規則は特に覚えなくてもよい。
関数fの定義は次と同等になる。
f x = case x of { 0 -> 1 ; 1 -> 5 ; 2 -> 2 ; _ -> 1 }
関数の分割定義
関数は、分割して定義できる。関数fは次のようにも定義できる。
f 0 = 1 f 1 = 5 f 2 = 2 f _ = -1
f _ = -1が最後にあることは重要である。この行を最初にもってくると、任意の値に対してfは-1を返すようになってします。
分割して定義された関数はcase ofを使ったものと同等であり、分割して定義されたものはcase ofを使ったものに置き換えられる。
関数の合成
2つの関数を.を使うことで合成できる。
square (f 2)
と
(square . f) 2
と書ける。
(.の前後のスペースは無くても大丈夫だった。)
幾つかの関数
id: 恒等関数 id x = x
null: リストが空ならTrueを返す。
==: 2つの値が等しければTrue
/=: 2つの値が異なればTrue
Yet Another Haskell 3章 (4)
3.4 Source Code Files
プログラムの実行に関して
Test.hsという名前のファイルを用意し、以下を記述する。
module Test
where
x = 5
y = (6, "Hello")
z = x * fst y
これでプログラムができる。
module TestのTestの部分は、ファイル名と同じにした方が良いらしい。(詳しくは6章で)
コマンドラインで、
ghci Test.hs
とすると、このモジュールがロードされる。
>ghci Test.hs
GHCi, version 7.4.2: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Ok, modules loaded: Test.
Prelude Test>
ghci起動後であれば、:lを使ってロードできる。
Prelude> :l Test.hs
Ok, modules loaded: Test.
Prelude Test>
ロード後はモジュール内で定義されている識別子にアクセスできる。
Prelude Test> x
5
Prelude Test> y
(6,"Hello")
Prelude Test> z
30
実行ファイル形式にコンパイルするには、
module名をMainにし、main関数を追加する。
main = putStrLn "Hello World"
そして、コマンドラインで、
ghc --make Test.hs -o test
とすると、testという実行ファイルが生成される。
Yet Another Haskell 3章 (3.2)
3.3.2 Simple List Functions
代表的なリスト操作関数について
Haskellプログラムの殆どはリストの操作である。
3つのプリミティブなリスト処理関数がmap, filter, foldr(とfoldl)である。
map
mapはリストのそれぞれの要素に対して、与えた関数を適用し、その結果のリストを返す。
Prelude> map Data.Char.toUpper "hello"
"HELLO"
(GHCiでもWinGHCiでも、原文のChar.toUpperでは駄目だった)
map toUpper "hello"は
map toUpper ['h','e','e','l','l','o']と同じ
そして結果は
[toUpper 'h', toUpper 'e', toUpper 'l', toUpper 'l', toUpper 'o']と同じと。
Prelude> [Data.Char.toUpper 'h', Data.Char.toUpper 'e', Data.Char.toUpper 'l', Data.Char.toUpper 'l', Data.Char.toUpper 'o']
"HELLO"
filter
filterはリストのそれぞれの要素に対し、与えた関数を適用し、その結果がFalseになった要素を取り除いたリストを返す。
Prelude> filter Data.Char.isLower "Hello World"
"elloorld"
はい。
foldr (foldl)
foldr(とfoldl)は関数(2つの引数を取り、同じ型の結果を返すもの)、初期値、リストの3を引数に取る。
foldr (+) 0 [3, 8, 12, 5]としたとき、foldrは以下のような計算をする
(3 + (8 + (12 + (5 + 0))))
一方、foldlは
((((0 + 3) + 8) + 12) + 5)
与える関数が+だと結果は変わらない。
Prelude> foldr (+) 0 [3, 8, 12, 5]
28
Prelude> foldl (+) 0 [3, 8, 12, 5]
28
これは、+が結合法則を満たすからですね。
(+に括弧が付いているのは、確か+を関数名として扱うためだったと思う。)
-だと結果が変わる。
(3 - (8 - (12 - (5 - 0)))) = 2だから、
Prelude> foldr (-) 0 [3, 8, 12, 5]
2
((((0 - 3) - 8) - 12) - 5) = -28だから、
Prelude> foldl (-) 0 [3, 8, 12, 5]
-28
附則
foldlはfoldrより効率が良い。
一方、foldlは無限リストを扱えないが、foldrは扱える。
詳細は7.8で扱う。
Excecise 3.3
Prelude> map Data.Char.isLower "aBCde"
[True,False,False,True,True]
Excecise 3.4
Prelude> length (filter Data.Char.isLower "aBCde")
3
Excecise 3.5
Prelude> foldl max 0 [5,10,2,8,1]
10
Excecise 3.6
Prelude> fst (head (tail [(5,'b'),(1,'c'),(6,'a')]))
1
今日はここまで。
Yet Another Haskell 3章 (3.1)
3.3.1 Strings
文字列に関して
Haskellでは文字列はString型で表され、その実体は文字型Charのリストである。
Prelude> 'H':'e':'l':'l':'o':[]
"Hello"
リストは++オペレータを使って結合できる。
Prelude> [1,2] ++ [3,4,5]
[1,2,3,4,5]
文字列もリストなので、もちろん++が使える。
Prelude> "Hello " ++ "World"
"Hello World"
show関数を使って、文字列でない値を文字列に変換できる。
Prelude> show 1
"1"
Prelude> show (1,2)
"(1,2)"
Prelude> show [1,2,3]
"[1,2,3]"
Prelude> show "1"
"\"1\""
最後の結果は意外。
Prelude> show (show "1")
"\"\\\"1\\\"\""
文字列に対してshowを適用すると、元の文字列の両端に"を付けて、さらにそれをエスケープするのかな?
Prelude> show show
<interactive>:50:1:
No instance for (Show (a0 -> String))
arising from a use of `show'
Possible fix: add an instance declaration for (Show (a0 -> String))
In the expression: show show
In an equation for `it': it = show show
関数はshowできないと。
read関数を使って文字列を文字列でない値に変換できる。
Prelude> read "5" + 3
8
なお、
Prelude> read "5"
とすると下のエラーがでる。
<interactive>:58:1:
Ambiguous type variable `a0' in the constraint:
(Read a0) arising from a use of `read'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: read "5"
In an equation for `it': it = read "5"
おそらく、read "5" + 3では、 + 3からreadが返すべき型(この場合はNumber?)が推論できるのだけど、read "5"だけだと、型の推論ができないから駄目なんだろう。
Yet Another Haskell 3章 (3)
3.3 Lists
リスト(list)に関して
タプルは要素の数が固定されているため、予め要素の数がわからない場合には使えない。
そういうときにはリストを使う。
リストはで囲むことで表す。
ex. [1,2], [1,2,3]
リストの要素は1つでもいいし、空でもいい
ex. [ ], [1]
リストの要素は(タプルと違い)全て同じ型でなければならない。
:(コロン)を使うことで、リストの先頭に新たな要素を追加したリストを生成することができる。
Prelude> 0:[1,2]
[0,1,2]
:はconsオペレータと呼ばれ、consオペレータを使って要素を追加することをconsingと言う。
空のリスト([ ])とconsオペレータを使うことで、任意のリストを生成することができる。
Prelude> 5:1:2:3:4:[ ]
[5,1,2,3,4]
(consは右結合っぽい)
また、[5,1,2,3,4]という文は、実際には5:1:2:3:4:[ ]という文の糖衣構文である。
headとtail関数が定義されており、headはリストの先頭の要素を、tailはリストから先頭の要素を除いた残りのリストを返す。
Prelude> head [1,2,3,4]
1
Prelude> tail [1,2,3,4]
[2,3,4]
Prelude> head [1]
1
Prelude> tail [1]
[ ]
なお、空のリストに対して使うと例外が飛ぶ。
Prelude> head [ ]
*** Exception: Prelude.head: empty list
Prelude> tail
*** Exception: Prelude.tail: empty list
length関数を使うことで、リストの長さを取得できる
Prelude> length [1,1,1,1,1]
5
Yet Another Haskell 3章 (2)
3.2 Pairs, Triples and More
タプル(tuple)に関して
Haskellでは、タプルが使える。
タプルのそれぞれの要素は同じ型でなくても良い。
ex. (5, "hello"), (1, 2, 3, 4)
関数fstとsndが定義されていて、これを使って、pair(2-tuple)の要素を取り出すことができる。
Prelude> fst (5, "hello")
5
Prelude> snd (5, "hello")
"hello"
fstとsndはpairのみにしか使えない。
Prelude> fst (1,2,3)
<interactive>:8:5:
Couldn't match expected type `(a0, b0)'
with actual type `(t0, t1, t2)'
In the first argument of `fst', namely `(1, 2, 3)'
In the expression: fst (1, 2, 3)
In an equation for `it': it = fst (1, 2, 3)
Exercise 3.2
Prelude> snd (fst ( (1, 'a'), "foo"))
'a'