この記事はHaskell初心者が躓くところピックアップし、Rubyのコードで解説したものです。
基本的には、これらの記事の二番煎じネタですのであしからず。
"Rubyistに贈るHaskell入門"がちょっと微妙だったので補足記事その1
対象
・Rubyのコードがある程度分かる人(proc, curry化等も出てきます。)
・HaskellでHello Worldしたことある人
・Haskellの独特な文法の理解に苦しんでいる人
Haskell勉強してよかったこと(期待していること)
ここらへんの記事が理解できるようになります。(前は読めなかった)
副作用のないコードを書けるようになります。(期待)
抽象的なロジックが組めるようになります。(期待)
学習手順
この記事を見てHaskellをセットアップ(超簡単!)
Stack を使って Haskell をインストールする
このサイトを一周しました。(手っ取り早く学べる)
ウォークスルーHaskell
目次
セクションごとに独立しているのでどこから呼んでも構いません。
私もHaskell初心者ですので誤った解釈をしている可能性もあります。
・関数定義
・局所定義
・高階関数
・ラムダ抽象
・部分適用
・合成関数
・パターンマッチング
・リスト
・型クラス制約
・data宣言
・class宣言
関数定義
Rubyではメソッドですが、Haskellでは関数と呼びます。
メソッドは状態に依存しますが、関数は状態に依存しません。
つまり、関数は返り値が引数に一意に決まるという性質を持っています。
haskell
関数の型を定義できる。
変数のように定義する。
add :: Int -> Int -> Int
add x y = x * y
add 1 2 -- > 3
ruby
メソッドですが、おなじみ。
def add(x, y)
x + y
end
add(1,2)
局所定義
局所で評価されない関数プログラムの定義の仕方。
-> 関数内で関数を定義することです。
haskell
haskellだとlet, where等を使うことが出来ます。
let {宣言1 ; 宣言2 ; ... 宣言n } in 式
circle :: Int -> Int
circle r = let square x = x * x
in 3.14 * square
circle 3 -- > 28.26
where {宣言1 ; 宣言2 ; ... 宣言n }
circle :: Int -> Int
circle r = 3.14 * square r
where square x = x * x
circle 3 -- > 28.26
ruby
rubyではproc, lambda等を使うしかありません。
def circle(r)
square = lambda { |x| x * x }
3.14 * square.call(r)
end
高階関数
関数を引数にとったり、関数を返り値にする関数。
-> 実は全然難しくない。
関数を引数にとる高階関数
haskell
haskellのmapです。rubyでももちろん書けますね。
double :: Int -> Int
double x = 2 * x
map double [1,2,3,4,5] -- > [2,4,6,8,10]
ruby
おなじみのmapメソッド。
class Integer
def double
2 * self
end
end
[1,2,3,4,5].map(&:double)
関数を返り値に返す関数
haskell
-- Intを受け取ってIntを返す関数を受け取り、Intを受け取ってIntを受け取る関数を返す。
return_function :: (Int -> Int) -> (Int -> Int)
return_function f = \ x -> f(x)
double x = x * 2
main = print $ (return_function double) 3
ruby
rubyは遅延評価ではないので、カリー化を明示的に宣言する必要があります。。
あんまりピンとこないですが、こんな感じでしょうかね。
関数を返して、最後に引数を渡します。
def double
proc { |x| 2 * x }
end
def return_func(f)
f.curry
end
return_func(double)[3]
ラムダ抽象
関数を式で記述したもの。
匿名関数。
Rubyでも無名関数等とよばれるものがありますね。
haskell
let hoge = (\i -> i * 2)
こんな感じで使います。
map (\i -> i * 2) [1,2,3]
ruby
proc, lambda等
hoge = proc { |i| i * 2 }
部分適用
関数に対して全ての引数を一度に渡さず、一部の引数だけを渡すことが出来る仕組み。
後で説明するカリー化とは違う。
・関数を引数一つづつに分割してネストさせることをカリー化
・一部の引数を固定化して新しい関数を作り出すことを部分適用
haskell
multiply :: Int -> Int -> Int
-- 二つの引数が必要
multiply x y = x * y
double :: Int -> Int
double = 2 * multiply 2
--- 後からmultiplyの第二引数を渡している。
double 3 -- > 12
ruby
高階関数はcurry化ばかりですね。
def multiply
proc { |x, y| x * y }
end
def double(x)
2 * multiply.curry[2][x]
end
double(3)
関数合成
合成関数について復讐
連立方程式
z = g(y)\\
y = f(x)\\
z = g(f(x))
と変換することが出来、fとgの合成関数と呼びます。
また、以下のように表現することが出来ます。
z = g ○ f
例えば
z = 2y \\
y = x + 1 \\
z = 2(x + 1) \\
x = 3の時\\
z = 8 \\
です。
haskell
haskellは数学知っていれば読めますね。
g y = 2 * y
f x = x + 1
z x = (g . f) x
main = print $ z 3 -- > 8
ruby
rubyでも関数の合成を行うことが出来ます。
g = proc { |y| 2 * y }
f = proc { |x| x + 1 }
z = proc{ |x| g.call(f.call(x)) }
z.call(3)
# もう少し格好良く書くと
z = proc { |x| g.(f.(x)) }
z.(3)
パターンマッチング
疲れました。rubyのコードはここからはありません。
ワイルドカード
なんでも良い時です。
return_three :: Int -> Int
return_three _ = 3
return_three 1 -- > 3
return_three 2 -- > 3
return_three 3 -- > 3
return_three 1000 -- > 3
ガード
定義を適用する条件を論理式によって記述できます。
power :: Int -> Int -> Int
power 0 _ = 1
power x y | x > 0 = x * y
power x y | otherwise = power (-x) y
power 3 4 -- > 12
power (-3) 4 -- > 12
case式
case 式 of { 選択肢1 ; 選択肢2 ; ... 選択肢n }
ガードで書いたのをcase式で書いてみます。
power :: Int -> Int -> Int
power x y = case (x, y) of
(0, _) -> 1
(x, y) | x > 0 -> x * y
| otherwise -> power (-x) y
リスト
-- [1, 2, 3]
[1, 2, 3]
1 : (2 : (3 : []))
1 : 2 : 3 : []
-- get element by index
arr = [1, 2, 3]
arr !! 0 -- > 1
arr !! 1 -- > 2
arr !! 2 -- > 3
-- range
[1..5] -- [1, 2, 3, 4, 5]
[1, 3..6] -- [1, 3, 4, 5, 6]
-- リスト内包
[(x, y) | x <- [1, 2], y <- [3, 4]] -- [(1, 3), (1, 4), (2, 3), (2, 4)]
[x | x <- [1..10], odd x] -- [1, 3, 5, 7, 9]
[(x, y) | x <- [1, 2, 3], let y = 4] -- [(1,4), (2,4), (3,4)]
型クラス制約
リストの総和を求める関数sum
sum :: [a] -> a
これだと、広すぎる。
sum :: [Int] -> Int
これだと、浮動小数点には使えない。
そこで、
sum :: Num a => [a] -> a
このように書くことで、Int, Integer, Float, Double等のクラスにおいても使える。
data宣言
型を宣言する事ができます。
data Point = Pt Double Double
norm :: Point -> Double
norm (Pt x y) = sqrt (x * x + y * y)
norm (Pt 3 4) -- > 5
class宣言
・引数の型によって動作を変えるような関数を集めたもの
・Javaのインターフェースっぽい感じのもの
あまり使い方は分かっていない。
class Hoge a where
foo::a -> String -- fooは何かを受け取ってStringとして返す
d = D "AAA"
dd = DD "BBB" "CCC"
instance Hoge D where
foo(D x) = "> " ++ x
instance Hoge DD where
foo(DD x y) = "> " ++ x ++ y
main = do {
print(foo d);
print(foo dd);
}
--- "> AAA"
--- "> BBBCCC"