fugafuga.write

日々のログ



すごいH本 part57

入出力

前回はただのポエムになってしまったので真面目にやっていく。

出力するのに使った関数 putStrLn

*Main Lib> :t putStrLn
putStrLn :: String -> IO ()
*Main Lib> :t putStrLn "hello, world"
putStrLn "hello, world" :: IO ()

putStrLn の型は、文字列を引数に取り、() 空のタプルを結果とする I/O アクションを返す。 I/O アクションとは、実行されると副作用を含む動作をして返すような何か。 ここでいう副作用とは、入力を読んだり画面やファイルに何かを書き出したりすること。 文字列を端末に表示するアクションには実際には意味のある返り値がないので、 ダミーとして () を使っている。

このことを I/O アクションが結果を生成する。という。 I/O アクションは、main という名前をつけてプログラムを起動すると実行される。

putStrLn "hello, world"は評価されると、hello, worldと出力するのではなく、 hello, world を表示しろ という命令を返している。

I/O アクション同士をまとめる

複数の I/O アクションを1つにするのに do 構文を使う。

main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!!!")

命令型のプログラムのように実行ステップを連ねて書ける。 実行ステップそれぞれが I/O アクションとなっている。

getLineを見る

*Main> :t getLine
getLine :: IO String

String を生成する I/O アクションだとわかる。 name <- getLline で入力された何かを name に束縛している。

I/O アクションは小さな足がついた箱のようなもの。 実世界に出ていって何かを行い、何か値を持って帰ってくる。 箱がデータを取ってきたとき、それを明けて中のデータを入手する唯一の方法が<-となる。

このようにして、Haskell は純粋なものと不純なものを分類している。

getLine は純粋ではない。 なぜなら、2回実行したときに同じ結果を返す保証が無いから。

次のコードは動作しない。

nameTag = "Helllo, my name is " ++ getLline

++の2つめの引数はString型を要求するが、

*Main> :t (++)
(++) :: [a] -> [a] -> [a]

getLine は、IO String型になるため型が一致しない。 動作させるためには、どこか別の I/O アクションの中で name <- getLine するしかない。

純粋でないデータを扱いたい場合は、純粋でない環境の中で行う必要がある。 不純による汚染を防ぐために、コード中のI/Oの部分を可能な限り小さくするべき。

  • I/O アクションから値を取り出すには <- を使って名前に束縛するしかない
  • I/O アクションが実行される時
    • main という名前を与えられた時
    • do ブロックで作った別の大きな I/O アクションの中にある時
    • GHCi に I/O アクションを入力してEnterを押した時
  • I/O アクションを do でまとめ、そのまとめられた I/O アクションを他の do で使える

所感

純粋な世界と不純な世界がはっきりと分けて考えられている。 この考え方は今までなかったのでとても新鮮だし、それを強制されるという部分が興味深い。

I/O アクションちゃんはあっちの世界とこっちの世界を行ったり来たりしてて働き者でかわいい。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る