純粋関数的 Haskell I/O プログラミング

HaskellはIOモナドを使えるので命令的Haskellプログラミングが可能になっているのだが,その代償としてプログラム全体の型は自明なIO ()に潰れてしまう.

プログラム全体の型が潰れないような仕組みを考えたのがoiパッケージ.以下は純粋に関数的にプログラムを構成したものである。(もちろん実際には、Haskellのプログラムとして起動するために、main :: IO () が必要であるし、実際の入出力を行うためのプリミティブ関数を構成するにはIOモナドが必要ではある。)

{-# LANGUAGE TypeOperators #-}
module Main where

import Data.OI
import Control.Parallel
import System.Environment

pmain ::  (FilePath,FilePath)
      ->  (FilePath,FilePath)
      ->  ((String, [String], ()), (String, [String], ()))
      :-> ()
pmain (kbd1,scr1) (kbd2,scr2)
  = uncurry par . (talk "Alice" (kbd1,scr1) |><| talk "Bob" (kbd2,scr2))

talk :: String                   -- 名前
     -> (FilePath,FilePath)      -- (キーボード, 端末スクリーン)
     -> [String]                 -- 相手からのメッセージ列
     -> OI (String,[String],())  -- 神託 (キーボード入力列,マージされたメッセージ列,プロセス結果)
     -> ([String],())            -- (相手へのメッセージ列,プロセス結果)
talk name (kbd,scr) msg r = (ins,showscr scr (mergeOI ins msg os) us)
  where
    (is,os,us) = deTriple r
    ins = map ((name ++ ": ")++) $ lines $ readkbd kbd is

----------

main :: IO ()
main = do { kbd1:scr1:kbd2:scr2:_ <- getArgs
          ; run $ pmain (kbd1,scr1) (kbd2,scr2)
          }

readkbd :: FilePath -> String :-> String
readkbd = iooi . readFile

showscr :: FilePath -> [String] -> () :-> ()
showscr s = iooi . writeFile s . unlines