top page > computer > haskell > web_lecture > for_programmer > iomcn_intro.html
更新日:
文責: 重城良国

IOMcn型: 導入

(工事中 60%)

はじめに

Machine型でユーザからの文字列のうちこみを扱うことを考える。Machine型だと機能的に問題があり、本来なら型エラーとなるはずのエラーが実行時エラーとなってしまうことを説明する。

入力と出力をつなぐ

関数nextはふたつの動作をつなぐ関数だった。動作1と動作2をつないで「動作1のあとに動作2を行う」という機械を作る。今度は動作1の出力と動作2の入力とをつなぐことを考えよう。

(>>>) :: Machine -> Machine -> Machine

のような演算子がありこれはひとつめのMachineが出力する値をふたつめのMachineの入力とするような働きをするものとする。

getLine :: Machine

putLine :: Machine

のようにMachine型の値がふたつある。getLineはユーザのうちこんだ文字列をうけとりそれを出力とする。putLineは入力として文字列をうけとりコンソールに表示する。するとユーザのうちこんだ文字列をコンソールに表示する動作は

getLine >>> putLine

のように書ける。

型エラーのチェック

ここで

getInt :: Machine

がありこれはユーザのうちこんだ文字列を数値に変換し出力とする機械とする。そして

getInt >>> putLine

のようにしたとき機械putLineは入力として文字列を期待しているので、数値をわたされると予測できない動作をする。つまり型システムでチェックされない型エラーが生じるということだ。このようなことはさけたい。Machine型のかわりに機械への入力値と出力値のそれぞれの型を指定する

IOMcn a b

を考えよう。ここでひとつめの型引数aはこの機械への入力値の型を示し、ふたつめの型引数bはこの機械からの出力値の型を示す。Machine型ではなくIOMcn型で型を書きなおすと

(>>>) :: IOMcn a b -> IOMcn b c -> IOMcn a c
getInt :: IOMcn () Int
putLine :: IOMcn String ()

となる。このようにすると

getInt >>> putLine

はgetIntからは型変数bはIntとなるのに対して、putLineからは型変数bがStringであることを期待されるので型エラーとなる。つまり、おかしなつなぎかたは、ちゃんと型エラーとなる。

正しいつなぎかた

型エラーになるようなつなぎかたを見た。それに対して

getLine :: IOMcn () String
putLine :: IOMcn String ()
getLine >>> putLine

のようなつなぎかたはgetLineの出力値の型StringとputLineの入力値の型Stringとが一致するので型エラーは生じない。

何も考えずに

IOMcn.hs

モジュールMachineのときと同じようにIOMcn.hsをカレントディレクトリにダウンロードする。または以下のソースファイルを何も考えずに作成しよう。

module IOMcn (
IOMcn, runIOMcn, (>>>), arr, app,
getLine, getInt, putLine, isEven) where

import Prelude hiding (getLIne)
import qualified Prelude
import Data.Time

newtype IOMcn a b = IOMcn { getIOMcn :: a -> IO b }

runIOMcn :: IOMcn () () -> IO ()
runIOMcn m = getIOMcn m ()

(>>>) :: IOMcn a b -> IOMcn b c -> IOMcn a c
m1 >>> m2 = IOMcn $ \x -> getIOMcn m1 x >>= getIOMcn m2

arr :: (a -> b) -> IOMcn a b
arr f = IOMcn $ return . f

app :: (a -> b) -> IOMcn a b
app = IOMcn $ uncurry getIOMcn

getLine :: IOMcn () String
getLine = IOMcn $ const Prelude.getLine

getInt :: IOMcn () Int
getInt = IOMcn . const $ fmap read Prelude.getLine

putLine :: IOMcn String ()
putLIne = IOMcn putStrLn

isEven :: IOMcn () Bool
isEven = IOMcn . const $ even . floor . utctDayTime <$> getCurrentTime

対話環境で試してみる

対話環境で試してみよう。

% ghci IOMcn.hs
*IOMcn> runIOMcn $ getLine >>> putLine
(適当な文字列を入力し改行ここではhello)hello
hello

キーボードからうちこんだ文字列がgetLineでうけとられその出力となり、putLineの入力となりコンソールに表示される。

*IOMcn> runIOMcn $ getInt >>> putLine
(...エラーメッセージはあとで書く...)

文字列と数値とで一致しない型なので型エラーとなる。

まとめ

機械には入力と出力とがある。機械1の出力と機械2の出力とをつないで新たな機械を合成することを考える。Machine型で考えると合成関数は

(>>>) :: Machine -> Machine -> Machine

のようになる。しかし機械に入力される値や出力される値には型がありその機械で扱えない型の値をわたされると実行時エラーとなってしまう。これは型システムによる型チェックをすりぬけていることになる。きちんと型チェックを働かせるためにはMachine型ではなく

IOMcn a b

のように入力値や出力値の型を指定できるIOMcn型を扱う必要がある。このとき機械をつなぐ関数の型は

(>>>) :: IOMcn a b -> IOMcn b c -> IOMcn a c

となる。このように機械の型を入力値、出力値によって細かく分類することで、型エラーをきちんとチェックすることが可能になる。

「Machine型」へもどる 「IOMcn型: 普通の関数をつなぐ」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics