🖥️

main :: IO()について(Haskellのアクションを解説)

2022/02/03に公開
2

この記事ではmain :: IO()について解説します。HelloWorldを出力するプログラムの中でも冒頭の部分です。

main :: IO ()
main = do
	putStrLn "HelloWorld"

まず先に結論からです。

main:変数(スタートポイントになる)
「::」: 変数・関数のデータ型を指定
IO():IO()というデータ型を持つ

といった意味となります。

もっとカンタンに言えば、int appleを宣言しているのと仕組みは同じです。

  • IO ()型のデータ型を持つ変数main
  • int型のデータ型を持つ変数apple

と全く同じです。1つずつ解説していきます。

mainとは?

まずはmainです。mainはHaskellでプログラムを実行する際、スタートポイントとなる変数です。Haskellはmainを通してでしかディスプレイの表示やネットワークへの接続などはできません。たった1つのエントリーポイントであり、ここが窓口となって外部世界とのやり取りを行います。

純粋と不純を分けるため

そしてなぜmainがたった1つの窓口となっているかと言えば、純粋な部分・不純な部分を分けるためでした。Haskellは数学の関数を利用してプログラムを組み立てる言語です。数学の純粋さを保つためには「外部とのやり取り」などの不純な部分・もっと言えば「状態を変えてしまうような処理」との分離が必要でした。

::とは?

次に「::」です。「::」は型宣言と呼び、変数・関数のデータ型をこちらで指定したい場合に利用します。今回は変数mainのデータ型を明示的に決めたいので「::」をつけています。

IO型のデータ型

そして最後のIO()というのはデータ型を指定しています。intやcharなどのデータ型と全く同じです。そして注意して頂きたいのが変数mainに対してIO型のデータ型を指定している点です。つまりmainはIO型のデータ型を持つ変数となっています。その意味ではint appleと全く同じです。

しかしIO型のデータ型を持つmainは特別な働きをします。「IO型の変数mainの中に入っているデータが外部との入出力に使われる」という点です。この変数mainの中に格納したデータがディスプレイに表示されたり、ネットワークに接続されます。

IO型を持つデータ=アクション

IO型のデータとは何でしょうか。IO型を持つデータのことをHaskellではアクションと呼びます(正確には型IO aを持つ式のこと)。データなので10や”apple”などの情報と変わるところはありません。しかしIO ()型のデータは「外部へ影響を与える」という点で今までのデータと全く異なります。なので「実行される値」という意味でアクションと呼ばれます。

特別な値:アクション

アクションはHaskellのお約束を破っています。

全ての式が値をもつ
全ての関数が引数・戻り値を持つ
どんな時でも常に同じ値を返す
副作用を禁止する(値を返却する以外はダメ)

でもディスプレイに文字を表示したり、ファイルに読み書きしたいですよね。そこで特別な値をアクションとして用意しています。そしてmainの中に格納されたアクションのデータだけが実行される仕組みとなっています。

アクションの種類

アクションには以下の種類があります。

IO ():コンソールに "hello" という文字列を表示する
IO String:コンソールから入力行を読み取る:
IO Socket:www.google.comに対して80番ポートでネットワーク接続を確立する
IO Int:ターミナルから2行入力を読み込んで、数字として処理し、足し算をして結果を表示する
IO ():マウスの動きを入力として、スクリーンにグラフィックを表示するファーストパーソン・シューティングゲーム

この中から必要な外部入出力機能を選択します。

なぜ()には何もない?

今回は画面に文字を出力するのでIO ()をmainのデータ型に指定します。()というのは特別な記号のように見えるかもしれません。IO Stringは文字列を返却し、IO Intは整数の数値を返却します。ではIO ()とは何でしょうか。

IO ()の()は「結果が何もないこと」を意味します。()は正確にはタプルといって、異なるデータ型を格納する配列のようなモノです。タプルを付けることで少なくともIOを値化します。しかし格納された情報はディスプレイなどへ送るので何もこちらには影響がないですよね。そこでこちらの世界には「何もない」として()内には何も記述しません。

do表記でIOアクションをまとめる

そしてdo表記によって、一連のIOアクションをまとめることができます。これは遅延評価が基本のHaskellで、アクションが実行(評価)される順番を確実に指定できるようにするためでもあります。

まとめ

この記事ではmain :: IO()を読み解きながら、アクションの概念を説明しました。IO型のデータというのは値であり、10や”apple”などと基本的に扱いは同じです。関数の引数・戻り値として渡すこともできます。

ただしIO型のデータが特別なのは、main関数の中に入っていると、それが外部へと実行される点です。Haskellの純粋性を保つためにアクションとして別の仕組みが用意されている点を覚えておきましょう。

main :: IO ()
main = do
	putStrLn "HelloWorld"

main:変数(スタートポイントになる)
「::」: 変数・関数のデータ型を指定
IO():IO()というデータ型を持つ

Discussion

岡本和樹岡本和樹

IO アクションも厳密に言えばこれら↓を守ってます

全ての式が値をもつ
全ての関数が引数・戻り値を持つ
どんな時でも常に同じ値を返す
副作用を禁止する(値を返却する以外はダメ)

例えば mainIO () と型付けされる「値」ですし、main は「常に同じ値」になります。また main評価しても副作用は発生しません。

IO a の値はあえて非厳密な例で言うと「手順書」や「命令書」のようなものです。

Haskell のランタイムがその「手順書」を解釈して実行します。

鳥羽眞嘉鳥羽眞嘉

ご指摘下さりありがとうございます。「mainを評価しても副作用は発生しない」という点で理解が不十分でした。記事の内容も修正させて頂きます。ありがとうございます。