IOモナドがまだ発明されていなかった時代にHaskellでI/Oがどのように行われたのだろうか。誰もが例を知っている。
編集:I/OはIO現代のHaskellのモナドなしで実行できますか?現代のGHCで動作する例をお勧めします。
IOモナドが導入される前は、main
はタイプ_[Response] -> [Request]
_の関数でした。Request
は、書き込みのようなI/Oアクションを表します。チャネルまたはファイル、入力の読み取り、環境変数の読み取りなど。Response
は、そのようなアクションの結果です。たとえば、ReadChan
またはReadFile
リクエストの場合、対応するResponse
は_Str str
_になります。ここで、str
は読み取り入力を含むString
になります。AppendChan
を実行する場合、 AppendFile
またはWriteFile
リクエストの場合、応答は単にSuccess
になります(もちろん、すべての場合において、指定されたアクションが実際に成功したと仮定します)。
したがって、Haskellプログラムは、Request
値のリストを作成し、main
に与えられたリストから対応する応答を読み取ることによって機能します。たとえば、ユーザーから数値を読み取るプログラムは次のようになります(簡単にするためにエラー処理は省略します)。
_main :: [Response] -> [Request]
main responses =
[
AppendChan "stdout" "Please enter a Number\n",
ReadChan "stdin",
AppendChan "stdout" . show $ enteredNumber * 2
]
where (Str input) = responses !! 1
firstLine = head . lines $ input
enteredNumber = read firstLine
_
Stephen Tetleyがコメントですでに指摘しているように、このモデルの詳細な仕様は、 1.2 Haskell Report の第7章に記載されています。
現代のHaskellではIO MonadなしでI/Oを実行できますか?
いいえ。HaskellはResponse
/Request
のIOを直接行う方法をサポートしなくなり、main
のタイプはIO ()
なので、IO
を含まないHaskellプログラムを書くことはできません。できたとしても、I/Oを行う別の方法はありません。
ただし、できることは、古いスタイルのメイン関数を取り、それをIOアクションに変換する関数を作成することです。その後、古いスタイルを使用してすべてを作成し、使用するだけで済みます。 IO in main
ここでは、実際のメイン関数で変換関数を呼び出すだけです。そうすることは、IO
モナドを使用するよりもほぼ確実に面倒です。 (そして、あなたのコードを読んでいる現代のハスケラーの地獄を混乱させるでしょう)、それで私は絶対にお勧めしません。しかしそれは可能です。そのような変換関数は次のようになります。
_import System.IO.Unsafe
-- Since the Request and Response types no longer exist, we have to redefine
-- them here ourselves. To support more I/O operations, we'd need to expand
-- these types
data Request =
ReadChan String
| AppendChan String String
data Response =
Success
| Str String
deriving Show
-- Execute a request using the IO monad and return the corresponding Response.
executeRequest :: Request -> IO Response
executeRequest (AppendChan "stdout" message) = do
putStr message
return Success
executeRequest (AppendChan chan _) =
error ("Output channel " ++ chan ++ " not supported")
executeRequest (ReadChan "stdin") = do
input <- getContents
return $ Str input
executeRequest (ReadChan chan) =
error ("Input channel " ++ chan ++ " not supported")
-- Take an old style main function and turn it into an IO action
executeOldStyleMain :: ([Response] -> [Request]) -> IO ()
executeOldStyleMain oldStyleMain = do
-- I'm really sorry for this.
-- I don't think it is possible to write this function without unsafePerformIO
let responses = map (unsafePerformIO . executeRequest) . oldStyleMain $ responses
-- Make sure that all responses are evaluated (so that the I/O actually takes
-- place) and then return ()
foldr seq (return ()) responses
_
次に、この関数を次のように使用できます。
_-- In an old-style Haskell application to double a number, this would be the
-- main function
doubleUserInput :: [Response] -> [Request]
doubleUserInput responses =
[
AppendChan "stdout" "Please enter a Number\n",
ReadChan "stdin",
AppendChan "stdout" . show $ enteredNumber * 2
]
where (Str input) = responses !! 1
firstLine = head . lines $ input
enteredNumber = read firstLine
main :: IO ()
main = executeOldStyleMain doubleUserInput
_
@ sepp2kはこれがどのように機能するかをすでに明確にしていますが、私はいくつかの単語を追加したいと思いました
本当にごめんなさい。 unsafePerformIOなしでこの関数を書くことは不可能だと思います
もちろん可能ですが、unsafePerformIOを使用することはほとんどありません http://chrisdone.com/posts/haskellers
わずかに異なるRequest
型コンストラクターを使用しているため、@ sepp2kのようにチャネルバージョン(stdin
/stdout
コード)。これに対する私の解決策は次のとおりです。
(注:getFirstReq
は空のリストでは機能しません。そのためのケースを追加する必要がありますが、それは些細なことです)
data Request = Readline
| PutStrLn String
data Response = Success
| Str String
type Dialog = [Response] -> [Request]
execRequest :: Request -> IO Response
execRequest Readline = getLine >>= \s -> return (Str s)
execRequest (PutStrLn s) = putStrLn s >> return Success
dialogToIOMonad :: Dialog -> IO ()
dialogToIOMonad dialog =
let getFirstReq :: Dialog -> Request
getFirstReq dialog = let (req:_) = dialog [] in req
getTailReqs :: Dialog -> Response -> Dialog
getTailReqs dialog resp =
\resps -> let (_:reqs) = dialog (resp:resps) in reqs
in do
let req = getFirstReq dialog
resp <- execRequest req
dialogToIOMonad (getTailReqs dialog resp)