(´ワ`)

主に技術系備忘録、たまに日記。

ゴイスーなえっち本を読んだのでHaskellについてまとめた

Haskellメモ

すごいH本を読んだのでHaskellに関してメモしたことをそのまんまここに残しておく。違うところもあるかも...

基本的なこと(データ型など)については省略

すばらしい機能

パターンマッチ

  • 与えられた引数に対してパターンマッチを行える

例) フィボナッチ数を求める(パターンマッチver.)

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib x = fib (x - 1) + fib (x - 2)

ガード

  • 与えられた引数に対して条件式を行える

例) フィボナッチ数(ガード文ver.)

fibG :: Int -> Int
fibG x
  | n == 0    -> 1
  | n == 1    -> 1
  | otherwise -> fibG (n - 1) + fibG (n - 2)

case .. of ...

  • 与えられた引数に対してパターンマッチと条件式を行える

例) フィボナッチ数(case of ver.)

fibC :: Int -> Int
fibC n = case n of
  0         -> 1
  1         -> 1
  _ | n > 1 -> fibC (n - 1) + fibC (n - 2)  {-- case ofは上から評価されるので本来ガード文はいらない? --}

Haskellはこのように文法は違うものの、同じ機能を持たせることができる。使い方は見やすさなどを考慮して使い分ける

特殊な式について

do

Monad値をつくるための式

let .. in

letで関数を、inで式を定義する式 どこにでも定義できる

例) リスト内包式

[let inc x = x + 1 in (inc 1)]  {-- [2] (xを引数にとり+1して返す関数に対して1を引数に適用する式をListに内包して返す) --}

where

変数や関数を定義された式に対して束縛することができる 使い方はlet .. inとさほど変わらないので関数の定義によって見やすさのために使い分けるべき

return

Monadを返す。CやJavaのようなプログラミング言語とは異なって結果の返り値や処理の抜け出しとしての使用法ではなく、正しく返されるべきMonadにラップされて値が返される

例) mainはIO ()が返るべき

main :: IO ()
main = do
  let a = return "hello"  {-- aの値はMonadである --}
  let b = "world"         {-- bの値はStringである --}
  a >>= print             {-- `>>=`の左辺が受け付ける引数はMonadのみ。print関数を呼び出したのでaはIOモナドとして関数に適用される --}
  print $ b               {-- print関数は文字として表せるもののみ引数にとる(Monadは引数にとれない) --}
  print $ a               {-- printはMonadを引数に取れないので構文エラーとなる --}

Functorとは?Applicativeとは?Monoid?Monad?ん?

これらをざっくりいうと、型が型を引数にもつ(型引数)もの。つまり型を持つことのできる型クラス

すごいH本がいうところの文脈付きの型

またそれらがもつ値をあらゆる形に変更・加工することのできるもの

例) Maybe型やList型([])はこれらすべてのインスタンスである Maybe IntInt型を持つMaybeという型 [Int]Int型を持つ[]という型

このような型を引数(型引数)に新しい型を作る型(型コンストラクタ)は下に示した機能をもつことができる

前提のお話

  • 文脈付き型は関数を適用する際に中身を直接触ることが出来ない(Maybe aに対してaを直接触れることができない)

  • 同様に、文脈付き関数もまた直接関数を使用することが出来ない(Maybe (a -> b)という関数はa -> bを直接適用することが出来ない)

Functor

文脈付き型に対して関数を適用することのできるもの Haskellではfmap(<$>)がFunctorに属する

例)

fmap (\x -> x + 1) (Just 1)  {-- Just 2 --}

(+1) <$> (Just 1)            {-- Just 2 --}

(+2) <$> Nothing             {-- Nothing(Maybeの仕様より、値のない`Nothing`が返る) --}

Applicative

文脈を持つ値と関数同士を適用することができるもの Haskellでは中置関数で<*>がある

例)

(Just (+3)) <*> (Just 4)  {-- Just 7 (与えられた引数に対して+3をする関数に4を与える) --}

Nothing <*> (Just 4)      {-- Nothing(適用される関数がないので`Nothing`) --}

(Just (+3)) <*> Nothing   {-- 逆も然り --}

Monoid

文脈付き型を結合することができるもの

例)

[1, 2, 3] `mappend` [4, 5, 6]             {-- [1, 2, 3, 4, 5, 6] --}

Just [1, 2, 3] `mappend` Just [4, 5, 6]   {-- Just [1, 2, 3, 4, 5, 6] (Maybeのインスタンスである場合、中身を結合することができる) --}

Monad

文脈付き型に対して文脈付き型を返すことができるもの

  • これまでのFunctor、Applicative、Monoidは振る舞いが違うものの、全て文脈付き型を授かり文脈付き型を返すことに関しては全部一緒である

    • つまりMonadはFunctorにもApplicativeにもMonoidにもなり得るもの??

Monadreturn>>=>>という関数を持つとすごいH本には書いてある

例)

return $ Just 3                   {-- Just 3 (returnはClangやJavaの`return文`とは異なり、Monadをそのまま返す`関数`である) --}

Just 3 >>= \x -> return $ x + 3   {-- Just 6 (`>>=`は左辺のモナドから結果だけを取り出し右辺の関数に適用した結果を新しいMonadとして返す) --}

Nothing >>= \x -> return $ x + 3  {-- Nothing (MaybeはMonadなので`>>=`を適用できる) --}

Just 3 >> Just 4                  {-- Just 4 (両方がJustの場合、右を評価する) --}

Nothing >> Just 4                 {-- Nothing (言わずもがな) --}

↑たちのルール(XX則)

Functor則

  • idでファンクター値を写した場合、ファンクター値が変化してはいけない

例)

fmap id (Just 3)  {-- Just 3 --}

fmap id [1 .. 5]  {-- [1, 2, 3, 4, 5] --}

fmap id Nothing   {-- Nothing --}
  • 「fとgの関数合成でファンクター値を写したもの」と「g、fと順番にファンクター値を写したもの」が等しいこと

    • fmap (f. g) = fmap f . fmap gであること

Applicative則

以下の式は等価である

(pureは純粋にその関数を返す関数)

  • pure f <*> x = fmap f x

  • pure id <*> v = v

  • pure (.) <*> u <*> v <*> w = u <*> (v <*> u)

  • pure f <*> pure x = pure (f x)

  • u <*> pure y = pure ($ y) <*> u

Monoid則

以下の式は等価である

  • mempty mappend x = x

    • 空のモノイド値に対してモノイド値を結合するとき、モノイド値が評価されること
  • x mappend mempty = x

    • モノイド値に対して空のモノイド値を結合するとき、モノイド値が評価されること
  • (x mappend y) mappend z = x mappend (y mappend z)

    • 3つのモノイド値を結合するとき、それぞれの結合する順番に関係なく結果が等しくあること

Monad

  • 左恒等則

    • returnを使ってデフォルトの値をデフォルトの文脈に入れたものを>>=を使って関数に食わせた結果は、単にその値にその関数を適用した結果と等しくあること

      • return x >>= ff xは等価である
  • 右恒等則

    • >>=を使ってモナド値を食わせた結果は、元のモナド値と不変である

      • Just 123 >>= returnJust 123
  • 結合法則

    • >>=を使ったモナド関数適用の連鎖があるときに、どの順序で評価しても結果は同じであるべき

    • (m >>= f) >>= gm >>= (\x -> f x >>= g)が同じである

      • fを先に評価しgを後に評価するような関数をモナド値に適用した結果とgを先に評価しfを後に評価する関数をモナド値に適用した結果は等価である

モナディック関数

モナディック関数とは

モナド値を操作したり、モナド値を返したりする関数(両方も可!)

すごいH本より

Listは文脈付き型であるためMonadであり、Monad値を操作できる関数(map、filter...)はモナディック関数である

関数の紹介

MonadはApplicative Functorであり、Applicative FunctorはFunctorである

Monad > Applicative > Functror

  • liftM

    • fmapの強化版?

      • fmap -> 関数をFuntorに適用できる関数である

      • liftM -> 関数をMonadに適用できる関数である

        • 上の定義に伴ってMonadのほうが扱える範囲が広い
  • ap

    • <*>の強化版?

      • <*> -> Applicativeの関数と値同士を適用できる

      • ap -> Monadの関数と値同士を適用できる

  • liftA2

    • 関数を2つのアプリカティブ値に適用できる
  • liftM2, liftM3 ...

  • join

    • モナド値が入れ子になっている部分を潰して平らにする

    • Scalaでいうところのflatten, flatMapに若干似ている

以上だぁ
なんとなくモナドについての理解が深まったのでcats on Scalaでより理解を深めたいっすね