Haskellメモ
入門Haskellプログラミング LESSON 15まで。続きはUNIT 3から。
コンパイル
ghc -o 実行ファイル名 haskellファイル.hs
main = do
print "Hello World"
関数定義
関数名 引数 = 本文
の書式で定義できる。
add a b = a + b
bmi w h = w / (h^2)
where句
関数内で利用できる変数を定義できる。
-- 健康体重との差を計算する
diff_healthy_weight w h =
w - healthy_weight
where
square = h^2
healthy_weight = square * healthy_weight_bmi
healthy_weight_bmi = 22
他の言語の変数のように思えるが、定義順序に意味はない。他の言語的に考えるならローカルなメソッドや関数を定義していると考えても良い。
# Rubyコード
def diff_healthy_weight(w, h)
w - healthy_weight(h)
end
def square(h)
h ** 2
end
def healthy_weight(h)
square(h) * healthy_weight_bmi
end
def healthy_weight_bmi
22
end
let式
他の言語的にいえばブロックに相当するもの。新しいスコープを作る。
let 変数定義 in 本文
という構文。
x a =
(let
a = 2
in
a) * a
-- 6が返る
x 3
上記の例の場合、let
式によってa
の返す値がlet
式の中だけ2になる。let
式はin a
となっているのでlet
式内でのa
の値、つまり2を返す。let
式の外ではa
は元の値になるのでx 3
は2 * 3
ということになる。
条件式
if
式とcase
式がある。
x a =
if odd a then
"odd"
else
"even"
end
y a =
case a of
1 -> "1"
2 -> "2"
_ -> "other"
パターンマッチ
y a =
case a of
1 -> "1"
2 -> "2"
_ -> "other"
上記のようなcase
式を使ったコードはパターンマッチで書き換えることができる。
y 1 = "1"
y 2 = "2"
y a = "other"
さらに、リストやタプルの場合、以下のように複雑なマッチングも可能。
my_sum [] = 0
-- my_sum [1,2,3] なら x は1、xs は[2, 3]
my_sum (x:xs) =
x + (my_sum xs)
-- tuple_sum (1, 2) なら a は1、b は2
tuple_sum (a, b) = a + b
map tuple_sum (zip [1..5] [1..5]) -- [2,4,6,8,10]
ラムダ関数
\引数 -> 本文
の書式で定義できる。即時利用する場合は(\引数 -> 本文) 実引数
のようにする。
-- 二人のbmiの差を求める
diff_bmi aw ah bw bh =
(bmi aw ah) - (bmi bw bh)
where
bmi = \w h -> w / (h^2)
ファーストクラス関数
関数は引数として渡すことも、戻り値として戻すこともできる。
x f a =
if f a then
odd
else
even
上記の関数でx odd 4
とした場合、if f a then
はif odd 4 then
として評価される。4は偶数でodd 4
はTrue
なのでodd
がx関数の戻り値として返る。(x odd 4) 5
を実行した場合、odd 5
になるのでFalse
になる。
高階関数
別の関数を受け取る関数のこと。map
関数など。
x f a b = f a b
x (+) 1 2 -- 1 + 2 が実行される
クロージャ
func a = \x -> a * x
func2 a =
(x3 a) + (x4 a)
where
x3 = func 3 -- \x -> 3 * x のラムダ関数
x4 = func 4 -- \x -> 4 * x のラムダ関数
func2 3 -- 21が返る
func
関数はラムダ関数を返す。ラムダ関数の本文にはfunc
関数のa
引数が使われており、func
関数を読んだ時の引数が束縛されたままになる。結果としてfunc 3
を呼び出せば\x -> 3 *x
のラムダ関数が返る。
部分適用
Haskellでは引数の一部だけ指定済の関数を作ることができる。
bmi h w = w / (h^2)
-- 身長180cm
bmi_h180 = bmi 1.8
-- 身長180cm 体重60kgのbmiを計算
bmi_h180 60
リスト操作
リストの要素は全て同じ型でなければならない。
list = [1, 2, 3]
head list -- 1
tail list -- [2, 3]
0 : list -- [0, 1, 2, 3]
[0] ++ list -- [0, 1, 2, 3]
[1..3] -- [1, 2, 3]
[1,3..5] -- [1, 3, 5]
[1,1.5..3] -- [1.0, 1.5, 2.0, 2.5, 3.0]
[1,0.5 .. 0] -- [1.0, 0.5, 0.0]
[1..] -- 1から始まる無限リスト
[1..] !! 2 -- 3
length [1..3] -- 3
reverse [1..3] -- [3, 2, 1]
-- ruby: (1..3).include?(2)
elem 2 [1..3] -- True
take 2 [1..5] -- [1, 2]
drop 2 [1..5] -- [3, 4, 5]
zip [1,3..5] [2,4..6] -- [(1,2),(3,4),(5,6)]
cycle [1..3] -- [1, 2, 3, 1, 2, 3 ...] のように引数のリストを無限に繰り返す無限リスト
高階関数のリスト操作
map (2 *) [1..3] -- [2, 4, 6]
filter (3 <) [1,3..5] -- [5]
-- ruby: (1..3).inject(0, :+)
foldl (+) 0 [1..3] -- 6
遅延評価
Haskellでは必要になるまでコードの評価は行われない。
func x =
if x == 0 then
x
else
func (x - 1)
result = func 1000000
上記のようなコードの場合、result
の値を参照しない限りfunc 1000000
は実行されない。適当なファイルに保存してGHCiで:l ファイル
をした上で、result
を指定すると多少時間がかかって0
が返ってくるのがわかる。これは、list = [1..]
のような無限リストの場合でも同じであり、head(tail [1..])
のようなコードを実行しても、無限リスト全体が評価されることなくすぐに2
が返ってくる。
型
変数の書式は変数名 :: 型
になる。関数の書式は関数名 :: 引数の型 -> 戻り値の型
になる。
x :: Integer
x = 100
func :: Integer -> Integer
func x = x^2
-- 引数が複数の場合
-- 関数名 :: 第一引数の型 -> 第二引数の型 -> 戻り値の型
func2 :: Integer -> Integer -> Integer
func2 x y = x * y
-- 高階関数の場合
-- 関数名 :: ( 引数の型 -> 戻り値の型 ) -> 引数の型 -> 戻り値の型
func3 :: ( Integer -> Integer ) -> Integer -> Integer
func3 f a = f (a * 2)
func3 (\x -> x * 3) 2
型変数
特定の型に限定したくない場合や任意の型だが同じ型に制限したい場合に型変数が使える。
-- func関数の第一引数の関数の引数とfunc関数の第二引数は同じ型
-- func関数の第一引数の関数の戻り値とfunc関数の戻り値は同じ型
func :: ( a -> b ) -> a -> b
func f a = f a
func (\x -> (fromIntegral x) / 2) 100 -- 50.0
型シノニム
型にエイリアスを作る。type シノニム名 = 元の型
の書式。
type Height = Integer
type Width = Integer
type Area = Integer
area :: Height -> Width -> Area
area h w = h * w
ただし、シノニム名と一致しているかはチェックされないため、以下のようなコードでもエラーにならない。
-- Height型の変数
h = 10 :: Height
area h h -- 100が返る
新しい型を作る
data Code = Code1 | Code2 | Code3
func :: Code -> String
func Code1 = "code1"
func Code2 = "code2"
func Code3 = "code3"
func Code1 -- "code1"を返す
上記の例の場合、Code
が新しい型で、Code1
やCode2
はデータコンストラクタという。
以下のように別の型を利用して新しい型を作ることができる。
-- data Code の Code は型名
-- Code Int String の Code はデータコンストラクタ名
data Code = Code Int String
func :: Code -> String
-- (Code i s)の部分はパターンマッチ
func (Code i s) = foldl (++) [] (take i (cycle [s]))
-- (Code 3 "abc") の部分は
-- Code がデータコンストラクタで
-- 3 が Int に
-- "abc" が String に対応する
func (Code 3 "abc") -- "abcabcabc"を返す
レコード構文
オブジェクト指向言語でいうところのgetter、setter的なものを定義できる。
name (Code _ name) = name
のように関数を定義することもできるが、レコード構文を使うと記述が簡略化できる。
data Code = Code {
no :: Int,
name :: String
}
code = Code 200 "OK"
no code -- 200
name code -- "OK"
code2 = code { name = "OK!" }
-- 元の変数は変わっていない
name code -- "OK"
-- 新しい変数code2のnameだけ変わっている
no code2 -- 200
name code2 -- "OK!"
型クラス
一連のメソッド(型クラスの関数はメソッドという)を定義して、その関数に渡せる型と実装を定義できる。
-- 型クラス Foo を定義 a は型変数
class Foo a where
-- 型クラス Foo のメソッド
hoge :: a -> String
piyo :: a -> a
fuga :: a -> String
-- デフォルト実装
fuga n = hoge (piyo n)
-- 型クラス Foo のインスタンス
instance Foo Int where
-- メソッドの実装
hoge 1 = "1!"
hoge 2 = "2!"
hoge n = "many!"
piyo n = n * 2
-- fuga はデフォルト実装があるので実装しない
hoge (1::Int) -- "1!"
hoge (3::Int) -- "many!"
piyo (3::Int) -- 6
fuga (1::Int) -- "2!"
fuga (2::Int) -- "many!"
fuga (1.1::Float) -- Foo Floatのインスタンスがないのでエラー
型クラスはJavaなどのインターフェースと比較されることが多いようだが、インターフェースは「型に関数が所属する(ないしクラスにインターフェースが所属する)」が型クラスの場合は「関数に型が所属する(ないし型クラスに型が所属する)」と考えた方が良さそうである。
インターフェースの場合はすでに定義済のクラスに後付けでインターフェースを追加することはできないが、型クラスの場合はそのような制限はない。
型クラスを関数定義で使う
関数の引数などが型クラスのインスタンスの型であることを明示したい場合、以下のようにする。
-- 引数が型クラス Foo と Bar のインスタンスの型であることを強制している
baz :: (Foo a, Bar a) => a -> String
baz a = fuga (bar n)
型クラスを型クラスで使う
関数定義で型クラスを利用したように型クラスでも利用することができる。
-- 型変数 a は型クラス Eq のインスタンスの型でなければならない
class Eq a => Foo a where
-- ...
型クラスの派生
新しい型も型クラスのインスタンスにすることができる。
data Code = Code1 | Code2 | Code3
-- Show は組込の型クラス
instance Show Code where
-- showメソッドは文字列を引数に変換して返す
show Code1 = "Code1"
show Code2 = "Code2"
show Code3 = "Code3"
show Code1 -- "Code1"
ただ、デフォルト実装でよければインスタンスの宣言を省略できる記法がある。
data Code = Code1 | Code2 | Code3 deriving (Show)