gpt4 book ai didi

haskell 将管道字符串输入 IO

转载 作者:行者123 更新时间:2023-12-02 12:07:50 25 4
gpt4 key购买 nike

抱歉,如果这是一个常见问题。我有这个简单的 IO() 函数:

greeter :: IO()
greeter = do
putStr "What's your name? "
name <- getLine
putStrLn $ "Hi, " ++ name

现在我想调用greeter,同时指定一个参数来预填充getLine,这样我实际上不需要交互。我想象像一个函数

IOwithinputs :: [String] -> IO() -> IO()

那我就这么做

IOwithinputs ["Buddy"] greeter

这将产生一个不需要用户输入的 IO 操作,如下所示:

What's your name?
Hi, Buddy

我想在不修改原始IO()函数greeter的情况下执行此操作。我也不想编译greeter并从命令行通过管道输入。我在 Hoogle 中没有看到类似 IOwithinputs 的东西。 (withArgs 的输入和命名都很诱人,但根本不是我想要的。)有没有一种简单的方法可以做到这一点?或者由于某种原因这是不可能的?这就是 Pipes 的用途吗?

最佳答案

正如其他人所指出的,如果您已经在使用 getLineputStrLn 之类的东西,就没有干净的方法来“模拟”IO。您必须修改greeter。您可以使用 hGetLinehPutStr 版本,并使用假的 Handle 模拟 IO,或者您可以使用Purify Code with Free Monads方法。

它要复杂得多,但也更通用,通常很适合这种模拟,特别是当它变得更复杂时。我将在下面简要解释它,尽管细节有些复杂。

这个想法是,您将创建自己的“假 IO”单子(monad),它可以通过多种方式“解释”。主要解释是仅使用常规IO。模拟解释用一些假行替换 getLine 并将所有内容回显到 stdout

我们将使用免费包。第一步是使用仿函数描述您的接口(interface)。基本概念是每个命令都是仿函数数据类型的一个分支,并且仿函数的“槽”代表“下一个操作”。

{-# LANGUAGE DeriveFunctor #-}

import Control.Monad.Trans.Free

data FakeIOF next = PutStr String next
| GetLine (String -> next)
deriving Functor

如果忽略下一步操作,从构建 FakeIOF 的人的角度来看,这些构造函数几乎就像常规的 IO 函数一样。如果我们想要PutStr,我们必须提供一个String。如果我们想要GetLine,我们提供一个函数,仅在给定String时给出下一个操作。

现在我们需要一些令人困惑的样板文件。我们使用 liftF 函数将仿函数转换为 FreeT monad。请注意,我们提供 () 作为 PutStr 上的下一个操作,并提供 id 作为我们的 String -> next 函数。事实证明,如果我们考虑 FakeIO Monad 的行为方式,这些就会为我们提供正确的“返回值”。

-- Our FakeIO monad
type FakeIO a = FreeT FakeIOF IO a

fPutStr :: String -> FakeIO ()
fPutStr s = liftF (PutStr s ())

fGetLine :: FakeIO String
fGetLine = liftF (GetLine id)

使用这些我们可以构建我们喜欢的任何功能,并以极小的更改重写greeter

fPutStrLn :: String -> FakeIO ()
fPutStrLn s = fPutStr (s ++ "\n")

greeter :: FakeIO ()
greeter = do
fPutStr "What's your name? "
name <- fGetLine
fPutStrLn $ "Hi, " ++ name

这可能看起来有点神奇——我们使用 do 表示法,而不定义 Monad 实例。诀窍在于,FreeT f m 是任何 Monad mFunctorMonad f`。

这完成了我们的“模拟”greeter 函数。现在我们必须以某种方式解释它,因为到目前为止我们几乎没有实现任何功能。为了编写解释器,我们使用 Control.Monad.Trans.Free 中的 iterT 函数。其完全通用的类型如下

iterT
:: (Monad m, Functor f) => (f (m a) -> m a) -> FreeT f m a -> m a

但是当我们将它应用到我们的 FakeIO monad 时,它看起来

iterT
:: (FakeIOF (IO a) -> IO a) -> FakeIO a -> IO a

这要好得多。我们为它提供了一个函数,该函数将“下一个操作”位置(这就是它的名称)中填充有 IO Action 的 FakeIOF 仿函数转换为普通的 IO 操作和 iterT 将发挥将 FakeIO 变成真正的 IO 的魔力。

对于我们的默认解释器来说,这非常简单。

interpretNormally :: FakeIO a -> IO a
interpretNormally = iterT go where
go (PutStr s next) = putStr s >> next -- next :: IO a
go (GetLine doNext) = getLine >>= doNext -- doNext :: String -> IO a

但我们也可以制作一个模拟解释器。我们将使用 IO 功能来存储某些状态,特别是虚假响应的循环队列。

newQ :: [a] -> IO (IORef [a])
newQ = newIORef . cycle

popQ :: IORef [a] -> IO a
popQ ref = atomicModifyIORef ref (\(a:as) -> (as, a))

interpretMocked :: [String] -> FakeIO a -> IO a
interpretMocked greetings fakeIO = do
queue <- newQ greetings
iterT (go queue) fakeIO
where
go _ (PutStr s next) = putStr s >> next
go q (GetLine getNext) = do
greeting <- popQ q -- first we pop a fresh greeting
putStrLn greeting -- then we print it
getNext greeting -- finally we pass it to the next IO action

现在我们可以测试这些函数

λ> interpretNormally greeter
What's your name? Joseph
Hi, Joseph.

λ> interpretMocked ["Jabberwocky", "Frumious"] (greeter >> greeter >> greeter)
What's your name?
Jabberwocky
Hi, Jabberwocky
What's your name?
Frumious
Hi, Frumious
What's your name?
Jabberwocky
Hi, Jabberwocky

关于haskell 将管道字符串输入 IO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19953612/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com