言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Haskell でヒューメインインタフェース。モナド記法を使わないで IO してみる

誰にも頼まれてないのに Haskell の復習をしています。前から気にくわなかったのが Haskell の do 記法です。例えばコンソールから二つの文字を入力して結果を出すだけのプログラムをこういう風に書きます。

main' = do num1 <- getLine
           num2 <- getLine
           print $ read num1 + read num2

これはかったるい!そう思いませんか?何故次のように書けないのか!これはモナドが悪いのか?

main'' = print ( read getLine + read getLine )

そういうわけで、素人ながらもうちょっとマシに書けるのでは無いかと試してみました。

まず、read getLine と書けない件。getLine はコンソールから文字列を受け取るモナドだけど、read は文字列自体を受け取る関数なのでうまく合わない。例えて言うと、getLine は水が流れ出るホースだけど、read は水をビールに変換する薬なのでくっつかない。必要なのは水をビールに変換する薬が入ったホースなのです。というわけでそういうやつを作ります。

readid :: Read b => IO String -> IO b
readid m = m >>= \s -> return (read s)

次に + です。read は文字列決め打ちの関数でしたが、+ はポリモーフィック関数なのでもうちょっとスマートに書けると思います。IO モナドを Num のインスタンスにすれば良いのです。

instance Eq (IO a)
instance Show (IO a)
instance Num a => Num (IO a) where
    a + b = a >>= \x -> b >>= \y -> return (x + y)

いい加減すぎる。。。これは GHC だと定義が足りないとエラーになります。hugs ならうまく騙されます。本当は instance (Monad m, Num a) => Num (m a) where のようにしたかったのですが、出来ないようでした。さて、同じように print の IO バージョン printio を定義します。

printio :: (Show a) => IO a -> IO ()
printio m = m >>= \v -> print v

これで完成。おお!まるでスクリプティング言語のようだ。可愛らしくて醜い。ゴシックロリータ!

main = printio (readid getLine + readid getLine)

-- Main> main
-- 3
-- 4
-- 7

さらに一声 String も Num のインスタンスにしてしまおうと思ったのですが、String (= [Char]) はインスタンスに出来ないという事が分かってやる気を失いました。Prelude.hs の中で show String なんかはかなーりトリッキーな事をやっています。

本当にやりたかった事を書きます。スプレッドシートで = A1 + A2 のようにした時。一体何が起こっていると思いますか?その場で答えが計算されるに留まらず、A1 や A2 が変化した時にはいつでも新しい答えが計算されるのです。そういう意味で、普通のプログラミング言語の a + b とは全然違う事が行われています。内部的な = A1 + A2 の値とは、A1 からやってくる数字のホースと、A2 からやってくる数字のホースを Y 字型につなげて答えを流し出すジョイントのようなオブジェクトだと言えます。これは Haskellモナドにとてもマッチします。Haskell の美しさを保ったままスプレッドシート的な文法を使うにはどうすれば良いだろうかと言うのが私の興味です。IO String + IO String = IO String みたいな演算子が簡単に定義出きれば良いのになーと思います。