LoginSignup
32

More than 5 years have passed since last update.

Haskell入門

Posted at
1 / 36

Haskell入門


対象

「Scala モナド」などでググったけどHaskellでの説明が出てきて、Scalaっていうたやん!とキレたことのある人。

目標

Haskellで書かれた式が読める


モナド則

  1. return x >>= f == f x
  2. m >>= return == m
  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

※注

僕はまだこれしか読んでません。


Haskellとは?

  • 強い静的型付け
  • 関数型言語
  • 遅延評価
  • ロゴがかっこいい

基本

> 1 + 2
3
> True && False
False
> 1 == 1
True
> "Hello" /= "hello"
False
> 1 : 2 : 3 : []
[1,2,3]

関数


普通の関数(前置記法)

関数名の後にスペース区切りで引数を続ける

> min 4 10
10
> max 100 (10 * 50)
500

中置記法

引数を2つ取る関数は `で囲む事で2つの引数の真ん中に置くことが出来る。
関数の名前に記号も使える。(
!#$%&+./<=>?@^|-~)
などの演算子も関数で、記号のみの関数はデフォルトで中置記法になる
中置記法がデフォルトの関数を前置記法で使いたい場合は
()`で括る

> div 5 2
2
> 5 `div` 2
2
> (+) 5 10
15

関数定義

関数名 引数 = 式 とするとことで定義可能。
引数が複数ある場合はスペース区切りで渡す。
記号のみの関数名は()で括る

> sum5 x = x + 5
> sum5 10
15
> (@~@!) x y = x + y
> 10 @~@! 10
20

無名関数

いわゆるラムダ式

\から始まる式が無名関数となる

> (\x -> x + 100) 1
101
> (\x y -> x + y) 1 2
3
> map (\x -> x + 100) [1..10]
[101,102,103,104,105,106,107,108,109,110]

セクション

中置演算子を()を使って部分適用したものをセクションと呼び、よく使う。
さっきの\x -> x + 100はセクションの(+100)と等しい

> map (+100) [1..10]
[101,102,103,104,105,106,107,108,109,110]
> map (*2) [1..5]
[2,4,6,8,10]


型定義

関数定義の場合に型推論が可能なら型定義は省略できる。
型定義は 関数名 :: 型 という形で行う。
->で複数の型が繋がっている場合、一番最後の型が戻り値の型で、それ以外は引数の型

cuboid :: Float -> Float -> Float -> Float
cuboid width height length = width * height * length

> cuboid 3.4 2.5 4
34.0

なぜ引数と返り値の方が同列に定義されるのか?

全ての関数はカリー化されているため。
cuboid に width を渡すと (height と length を取り直方体の体積を返す関数) が得られる
同様にして↑の関数に height を渡すと…

つまり型定義は全て引数 -> 戻り値となっており、

Float -> Float -> Float -> Float

Float -> (Float -> (Float -> Float))
と同じ


型変数

リストの関数のように色々な型に対して動作するものには、型定義に型変数を用いる。
ジェネリクスのようなもの。

> head [1,2,3]
1
> :t head
head :: [a] -> a

型クラスと型クラス制約

型クラスとは振る舞いを定義するインターフェース。
例えばある型に対してEq型クラスのメソッド==を実装していれば等値性比較の振る舞いが定義できる。

> :t (==)
(==) :: Eq a => a -> a -> Bool

↑の=>記号が型クラス制約で、
その名の通り型aEqの型クラスに属しているという制約を表している。
(つまりEq型クラスに属しているので 型aの値 == 型aの値 が定義されているという事)


パターンマッチ

checkList :: (Show a) => [a] -> String
checkList (x : y : []) = "Two elements " ++ show x ++ " and " ++ show y
checkList (x : []) = "Just one element " ++ show x
checkList [] = "Empty!"
checkList xs = "Many elements " ++ show xs

> checkList [1,2,3,4]
"Many elements [1,2,3,4]"
> checkList ["hello"]
"Just one element \"hello\""

Show型クラスは文字列に変換できるインターフェース。
++ は文字列(リスト)の結合演算が行える関数


データ型定義、型コンストラクタ

新しい型は data 型名 = 型 (| 型2 (| 型3 ...)) という様に定義できる。
|で複数の型が繋がっている場合はいずれかの型がそのdataの型となりうる事を表している。
またこの時型名を型コンストラクタと呼ぶ。

data Point = Pt2d Double Double | Pt3d Double Double Double
distance :: Point -> Point -> Double
distance (Pt2d x1 y1) (Pt2d x2 y2) = sqrt((x1 - x2)**2 + (y1 - y2)**2)
distance (Pt3d x1 y1 z1) (Pt3d x2 y2 z2) = sqrt((distance (Pt2d x1 y1) (Pt2d x2 y2))**2 + (z1 -z2)**2)

> distance (Pt2d 0 0) (Pt2d 3 4)
5.0


data Maybe a = Just a | Nothing
Maybeは Just aNothingのどちらかを取るデータ型で、Maybeがその型コンストラクタ


型クラス定義

型クラスはclassキーワードを使って行う。

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)

whereはブロック{}のようなもので、Eq aの定義の為にスコープを作ると思ってくれれば良い。
(==)(/=)がその型クラスに属する型が実装すべきメソッド
x == yx /= yはその実装を行っている。実装は行わなくても型クラスの定義は可能。
Eq型クラスでは==/=が相補的に実装されているので、実際にはどちらかのメソッドを実装するだけで良い。


型クラスのインスタンス作成

ある型をある型クラスに属させるのにはinstanceキーワードを使って行い、これをインスタンス化と呼ぶ。

instance Eq Point where
    Pt2d x1 y1 == Pt2d x2 y2 = x1 == x2 && y1 == y2
    Pt3d x1 y1 z1 == Pt3d x2 y2 z2 = x1 == x2 && y1 == y2 && z1 == z2
    _ == _ = False

> Pt2d 1 1 == Pt2d 1 1
True
> Pt2d 1 1 == Pt3d 1 2 3
False

モナド則

  1. return x >>= f == f x
  2. m >>= return == m
  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)
> :t f
f :: Monad m => a -> m b
> :t g
g :: Monad m => b -> m c
> :t return 
return :: Monad m => a -> m a
> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b



Functor型クラス

Monadのベースのベース


Functor型クラスは 全体を写せる 振る舞いを持ったインターフェース。

定義は

> :i Functor 
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
  {-# MINIMAL fmap #-}
    -- Defined in ‘GHC.Base’

となっており、最低限fmapを実装していればFunctor型クラスのインスタンスになることが出来る。
(Functor (f :: * -> *)fは型を取って型を返す型コンストラクタ という意味)


fmap

改めてfmapの型定義を見るとfmap :: (a -> b) -> f a -> f b となっている。

型コンストラクタfはコンテナ型のようなものなので、その文脈で考えると「ある型に包まれた値a」と「値aを値bに変換できる関数」を引数に取って「ある型に包まれた値b」を返すような定義をしている。
実際にリストではinstance Functor [] where fmap = mapと定義されているため、map関数をイメージすると解りやすい。
ただし、リストのように値が連続している必要はなく、実際の値がある型で包まれていればFunctor型クラスになりうるというのが重要。


fmap使用感

> fmap (*2) [1..3]
[2,4,6]
> fmap (++ "I'm alice") (Just "Hi bob.")
Just "Hi bob.I'm alice"
> fmap (++ "I'm alice") Nothing
Nothing

fが型コンストラクタなので、取りうる型(MaybeならJust|Nothing)についてfmapが問題なく使える。


Applicative

Monadのベース, Functorの強化版


Applicativeは強化版Functorの様な型クラス
定義は

> :i Applicative 
class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  (*>) :: f a -> f b -> f b
  (<*) :: f a -> f b -> f a
  {-# MINIMAL pure, (<*>) #-}
    -- Defined in ‘GHC.Base

重要なのはpure<*>の2つ。(最低限の実装は<*>だけで良い)


pure

pure :: a -> f a

定義から、値aをアプリカティブ値として包む事がわかる。
リストならpure 1[1]、Maybeなら1をJust 1の様になる。


<*>

(<*>) :: f (a -> b) -> f a -> f b

<*>は「aをbに変換する関数のアプリカティブ値」と「aのアプリカティブ値」を取って「bのアプリカティブ値」を返す。
また、記号のみの関数なのでデフォルトで中置記法での使用になる (f (a->b) <*> faと使う)

ここでfmapの定義はfmap :: (a -> b) -> f a -> f bなので、これと見比べるとa -> bがアプリカティブ値に包まれているだけと分かる。
つまりfmapの変換関数がJustなどで包まれている場合でもfmapの代わりに<*>を使えば同じことが出来る。


<*>使用感

> Just (\x -> x + 10) <*> Just 5
Just 15
> Just (\x -> x + 10) <*> Nothing
Nothing
> Nothing <*> Just 5
Nothing
> pure (\x -> x + 10) <*> Just 5
Just 15
> Just (\x -> x + 10) <*> pure 5
Just 15

\x -> ...はxを引数に取る無名関数を表す。...の部分が実装


Monad

Applicativeの強化版


> :i Monad
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b
  return :: a -> m a
  fail :: String -> m a
  {-# MINIMAL (>>=) #-}
    -- Defined in ‘GHC.Base’

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
32