9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「Rubyistに贈るHaskell入門」 again

Last updated at Posted at 2016-09-14

この記事はHaskell初心者が躓くところピックアップし、Rubyのコードで解説したものです。

基本的には、これらの記事の二番煎じネタですのであしからず。

Rubyistに贈るHaskell入門

"Rubyistに贈るHaskell入門"がちょっと微妙だったので補足記事その1

対象

・Rubyのコードがある程度分かる人(proc, curry化等も出てきます。)
・HaskellでHello Worldしたことある人
・Haskellの独特な文法の理解に苦しんでいる人

Haskell勉強してよかったこと(期待していること)

「関数型Rubyという病」

ここらへんの記事が理解できるようになります。(前は読めなかった)
副作用のないコードを書けるようになります。(期待)
抽象的なロジックが組めるようになります。(期待)

学習手順

この記事を見て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"
9
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?