mainでputStrをmapする方法

前回分からなかった件、簡単に書くとこんな感じ。

main = mapM putStrLn ["hoge", "piyo", "fuga"]

mapでも良さそうな気がしてしまうが駄目。mapMでもmapM_でも問題なく動くようなのだが。理由を自分なりにまとめた。

putStrLn

Prelude> :t putStrLn
putStrLn :: String -> IO ()

putStrLnは「文字列を引数に取り、()(空のタプル)を結果とするI/Oアクションを返す」。

アクションとは

Haskellの関数は全て参照透過性(同じ引数に対して常に同じ値を返す)がある。アクションは参照透過性がないものを扱う。例えば乱数を返すアクション、コンソールからの入力を返すアクション等。

I/Oアクションとは

実行されると、入力を読んだり画面やファイルに何かを書き出したりする動作をして、結果を返すアクション。

putStrLnが「()を結果とするI/Oアクション」を返すのは、文字列を端末に表示するアクションには意味のある返り値がないので、ダミーの値として()を使うため。*1

main変数

Mainモジュールに関係がある。

モジュールとは

エンティティ(変数、型コンストラクタ、データコンストラクタ、フィールドラベル、型クラス、クラスメソッド)をパッケージ化したもので、モジュールごとに名前空間が分かれている。

importで別モジュールをインポートできる。

import System.Random

モジュールを定義するには以下のように書く。

module MyFileUtils (tmpPath) where

MyFileUtilsモジュールを定義し、tmpPathエンティティを外部にエクスポートする宣言となる。

ファイル内でmodule宣言を省略すると、以下のmodules宣言が勝手に追加される。

module Main (main) where

このmainがプログラムエントリーポイントとなり実行される。 mainはI/Oアクションが束縛された変数でなければならない。

map、mapM

ここまで、main :: IO aputStrLn :: String -> IO ()となることを説明した。つまり

main = putStrLn "hoge"

これは型が合致し問題なく動くということだ。では間にmapを挟むにはどうしたらいいか。
sequence関数というのがある。I/Oアクションのリストを引数にし、それらを順に実行するI/Oアクション(シーケンス)を返す。これをつかって以下のように書ける。

main = sequence $ map putStrLn ["hoge", "piyo", "fuga"]

mapが[putStrLn "hoge", putStrLn "piyo", putStrLn "fuga"]を返すのでsequnceがI/Oアクションにまとめる訳だ。
mapM関数はシーケンスにまとめる処理を内部で行ってくれる。

main = mapM putStrLn ["hoge", "piyo", "fuga"]

結論として、mapMは、リストに対してI/Oアクションを返す関数をマップし、単一のI/Oアクションにまとめてくれるから、mainに束縛できるということだ。
最後に残ったmapM_、動作はmapMとほぼ同じで、違いはmapM_はI/Oアクションの結果を捨てる。

ちなみにsequenceとmapMのシグネチャは相変わらず読めない。

Prelude> :t sequence
sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
Prelude> :t mapM
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)

参考にした。
http://jutememo.blogspot.jp/2010/03/haskell-mapm-foldr.html
すごいHaskellたのしく学ぼう!

*1:voidなんて無いんだね、と思ったらある?