web-dev-qa-db-ja.com

Haskellのモナド前I / O

IOモナドがまだ発明されていなかった時代にHaskellでI/Oがどのように行われたのだろうか。誰もが例を知っている。

編集:I/OはIO現代のHaskellのモナドなしで実行できますか?現代のGHCで動作する例をお勧めします。

51
Tem Pora

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
_
60
sepp2k

@ 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)
0
Tomatosoup