読者です 読者をやめる 読者になる 読者になる

by shigemk2

当面は技術的なことしか書かない

今日やったこと #ikebin

勉強会

モナドの話

アクションの話

qiita.com

アクションとラムダの話

qiita.com

doの実体はbind.hs

doで何行も書かれているのは、bindで一行でまとめることが出来る。

main =
    print "hello" >>= \_ ->
        print "world"
main =
    print "abc" >>= \_ ->
        print "def" >>= \_ ->
            print "ghi" >>= \_ ->
                print "jkl" >>= \_ ->
                    print "mno"

最後のは捨てることは出来ない。

printは引数で、副作用でコンソール出力している。

なお、ポイントフリースタイル的なことは出来る。

main =
    return "hello" >>=
    print

doを使わなくても同じ処理を書くことができます。ってことが言えます。

ラムダ式やセクションを使って、式の一部を省略出来る。

import System.Random
import Data.IORef

main = do
    a <- newIORef =<< (getStdRandom $ randomR (0, 9) :: IO Int)
    -- a' <- readIORef a
    -- writeIORef a (a' + 1)
    -- 上2行の代わり
    modifyIORef a (+ 1)
    print =<< readIORef a

IOモナド

今まで出てきたモナドはIOモナドで、IOモナドによるアクションはIOアクションという。

qiita.com

IOモナドの中に参照透過性が保証されない特殊な関数が隠されている。 で、この特殊な関数は取り出すことが出来る。

処理系依存なので、以下のプログラムは通常は使わない。

import GHC.Base

hello1 = unIO $ return 1

IOモナドは処理系依存の特殊関数を利用して値を格納している。関数や値を取り出したり格納するのにunIOとかIOとかreturnとか<-とかが使える。

IOモナドの中に関数が入っていることはプログラマは意識しなくていい。 こういう手法はオブジェクト指向のデザインパターン、ファクトリメソッドに似ている。

アンボックス化タプル

  • タプルと違って計算結果が含まれる。式を入れるとそこから計算をしないといけないから効率が悪い。

vs 非ボックス化 人によってはアンボックスだったり非ボックスだったりする。

  • 戻り値は2要素のアンボックス化タプル
  • 第1要素は引数を素通ししたもの
  • 第2要素は今まで「アクションから取り出した値」と言っていた値
  • アンボックス化タプルを使うことで、タプルに格納する際に式が評価される

アンボックス化タプルが使用された瞬間に計算が終わっている。

IOモナド内の関数

s→s1→s2→s3で、世界が次々と変わっていく。状態変化。

{-# LANGUAGE UnboxedTuples, MagicHash #-}

import GHC.Base

main' :: State# RealWorld -> (# State# RealWorld, () #)
main' s =
    let (# s1, _ #) = unIO (print "hello") s
        (# s2, _ #) = unIO (print "world") s1
        (# s3, r #) = unIO (print "!!") s2
    in  (# s3, r #)

main :: IO ()
main =  IO main'

なお、この書き方はIOモナドのみの書き方。 状態の受け渡しをモナドでかくしている。

リストとIOUArrayは副作用の点で違うものですよ、っていう話。 qiita.com

IOUArrayは状態が変化している。 通常、過去の状態を弄ることは出来ない。

qiita.com

qiita.com

Clean

Clean

Cleanで関数プログラミング 【総目次】

Haskellと似ている言語で、副作用の表現方法がHaskellではモナドなのに対し、Cleanでは一意型を使う。

  • 一意型とは:一意性を持つ型
  • 一意性とは:一度しか使われないこと

(一度しか使ってはいけない型のことを一意性の型)

Cleanはググラビリティが低い…

github.com

qiita.com

qiita.com

{-# LANGUAGE UnboxedTuples #-}

import GHC.Base

test1 = do
    a <- return 1
    print a

test2 =
    return 2 >>= \a ->
    print a

test3 = IO $ \s ->
    let (# s1, a #) = unIO (return 3) s
        (# s2, r #) = unIO (print  a) s1
    in  (# s2, r #)

main = test1 >> test2 >> test3
  • IOモナドの中には関数
  • 関数は状態を受け取って、更新された状態と値を返す
  • 状態の受け渡しを隠せば、IOモナドから値だけを取り出しているように見える
  • 状態をバケツリレーすることでコンテキストが構成されます。
  • doブロックは独自のコンテキストを持つと見なせます。
  • このモデルで入出力(I/O)による状態変化を取り扱えるため、IOモナドと呼ばれます。

IOモナドと関数をbindし、さらにそれを関数にbindすると別のIOモナドが出来上がる。

普通はプログラマにその詳細は見えないようになっている。オブジェクト指向でのカプセル化に近い。

結果をアンボックス化タプルに入れることで、順番に評価されることが保証される。

詳細

IO inside - HaskellWiki

リストモナド

qiita.com

bind(>>=)とreturnで操作できる対象をモナドと呼ぶ(ただしモナド則に則る)

  • 中に値が入っていること
  • bindでモナドと関数をつなぎ、新しいモナドを作れる
  • returnでモナドを作れる

これが共通点。

リストモナドはIOモナドと違って、中に直接値が入っている。

printはIOモナドを返すため、リストモナドとはbindできない。

まとめ

  • bind(>>=)とreturnで操作できる対象をモナドと呼びます(ただしモナド則に則る)
  • リストはモナドの一種です
  • IOモナドは中に値を生成する関数がありますが、リストは値が直接入っています(すべてのモナドが一緒なわけではない)
  • 副作用を扱うのはIOモナド特有で、モナド共通の特徴ではありません(ここでいう副作用は状態の変化のことで、外部に影響を与えるもののこと)
  • doブロックは共通の見た目で記述できますが、モナドによって動きが異なります(1つのdoで使えるモナドは1つだけ)
  • IOモナドと違ってリストモナドは複数の値が入る
  • bindはモナドによって動きが違う
  • リスト内包表記はdoの糖衣構文です

ココ重要。

モナド則

モナドを使うにあたってのルールだが、ルールを完璧に覚えていなければ使えないというわけでもない。

qiita.com

Prelude> :t 1
1 :: Num a => a
Prelude> :t return 1
return 1 :: (Monad m, Num a) => m a

モナド則は順番は決まっていないが、全部を満たさなければならない。難しい…

線型写像 - Wikipedia

The monad laws

圏論を真面目にやると1 2 3のどれかが満たされないやつが出てくるようになるかもしれない。

qiita.com

そういえば、絵は分かりやすいけど、なんで箱からモノを出し入れしないといけないのかがあんまり良くわからないかもしれない。

パーサー

プログラミングHaskell

プログラミングHaskell

  • 関数型パーサー
  • yaccだとしんどい
  • 逆アセンブラの実装は楽だけど、アセンブラの実装はしんどい
  • ,やスペースをひとつひとつ解析しないといけないのが理由

パーサーは状態を持つのでモナドを使っている。

https://www.shido.info/hs/haskell2.html

あと、サポートページからソースコードをダウンロードできるけど、ダウンロードされるファイルは.lhsなので、気をつけておこう。

https://www.shido.info/hs/haskell2.html