From: Shiro Kawai <shiro / lava.net>
Subject: [haskell-jp:254] Re: 低レベルプログラミングとstricttypesystem
Date: Sat, 01 Nov 2003 15:38:22 -1000 (HST)

> > Double は Prelude で Read クラスのインスタンスであること
> > が宣言されていますので、以下のようになります。
> > (ユーザからの入力が数字じゃなかったときの例外処理は省略しますが、)
> > 
> > foo :: Double -> IO Double
> > foo x | x > 0     = return (log x)
> >       | otherwise = do userInput <- hGetLine stdin 
> >                        foo (read userInput)
> 
>   foo (eval (read userInput))
> 
> ができるかどうか、というのが焦点です。readの返す型はReadに限定できますが、
> 一般にevalの返す型は限定できませんね。
> EvalResultみたいなクラスを定義して、「あらゆる可能な型はEvalResultの
> インスタンスである」とするなら動くのでしょうか。

read の返す型は Read ではありません。
私のあげたプログラムの文脈では Double 型です。

read の型シグネチャーは、
read :: Read a => String -> a

なので、Double が Read のインスタンスなので、Double が要求される
文脈では、read userInput は Double 型です。

ただし、Prelude での Double が Read クラスのインスタンスであるとの宣言
中であたえられている read の定義では userInput の文字列として受け入れられる
のは、Double のリテラル表現になっている文字列だけです。userInput が 
"0.875468737" はOKですが "log 2.4" というのNGです。

{-
すべての型がそのインスタンスであるような Eval クラスという
案は Haskell 98 の仕様のころにはあったよう記憶があるんですが、
それが消えた経緯はよく調べてません。
-}

> もひとつ、現実的なシナリオとして、
>  foo :: Doulbe -> Double
> が既に存在していて、あちこちで使われているのでシグネチャは
> 変更できない、そこに userInput の処理を組み込みたい、
> というケースが考えられますが…

はい、多分これが一番問題です。Haskell では、入出力という「外界の操作」を
含む関数の返り値は必ず、IO モナドでなければなりません。したがって、I/O 
を含まなかった foo を I/O を含む foo にするのは型が違うので、foo を使う
プログラムを全体を書きかえる必要があります。

まあ、反則技 unsafePerformIO :: IO a -> a を使うという手はあるのですが、
unsafePerformIO は型システムが型安全性を保証しないので、プログラマが
型安全性を保証する必要があります。

 I/O という外界とのやりとりを、IO モナドでやろうとするのは、
静的型付けや参照透明性を上手くてなづけているようですが、
プログラムのモジュラリティと自由度をかなりそこなっています。
ここが、Haskell の苦しさですね。

--nobsun

--
ML: haskell-jp / quickml.com
使い方: http://QuickML.com/