是否可以使用类型类将“ReaderT (IO a) IO a”更改为“ReaderT (i a) IO a”?
Is it possible to use typeclasses to change `ReaderT (IO a) IO a` into `ReaderT (i a) IO a`?
我正在学习 Haskell,感谢 的帮助,我得到了以下代码,它只是一个 echo
程序。它工作得很好,但我想对其进行一些改进并且 运行 遇到了麻烦。
userInput :: MonadIO m => ReaderT (IO String) m String
userInput = ask >>= liftIO -- this liftIO eliminates your need for join
echo :: MonadIO m => ReaderT (IO String) m ()
echo = userInput >>= liftIO . putStrLn -- this liftIO is just so you can use putStrLn in ReaderT
main :: IO ()
main = runReaderT echo getLine
我想做的是将 ReaderT (IO String)
更改为 ReaderT (i String)
并使其更通用,以便我可以将其换出以进行单元测试。问题是,因为我们在 userInput
中使用 liftIO
,它有点 ties i
和 IO
。有什么方法可以用其他东西替换 liftIO
来使下面的代码工作吗?
class Monad i => MonadHttp i where
hole :: MonadIO m => i a -> ReaderT (i a) m a
instance MonadHttp IO where
hole = liftIO
newtype MockServer m a = MockServer
{ server :: ReaderT (String) m a }
deriving (Applicative, Functor, Monad, MonadTrans)
instance MonadIO m => MonadHttp (MockServer m) where
-- MockServer m a -> ReaderT (MockServer m a) m1 a
hole s = s -- What goes here?
userInput :: (MonadHttp i, MonadIO m) => ReaderT (i String) m String
userInput = ask >>= hole
echo :: (MonadHttp i, MonadIO m) => ReaderT (i String) m ()
echo = userInput >>= \input ->
((I.liftIO . putStrLn) input)
main = runReaderT echo (return "hello" :: MockServer IO String)
请记住,ReaderT r m a
是 r -> m a
的 newtype
包装器。具体来说,MonadIO m => ReaderT (IO a) m b
等同于 MonadIO m => IO a -> m b
。那么让我重新表述一下你的问题:
Can you convert MonadIO m => IO a -> m b
to MonadIO m => m a -> m b
?
答案是否,因为IO a
作为函数类型的输入出现。 (有时你会看到人们说 "in negative position",这与 "input" 的意思大致相同。)这里重要的是,转换函数输入与转换函数输出的方向相反。
让我们退后一步,考虑一个更一般的情况。如果你有一个函数 a -> b
并且你想转换它的输出以获得函数 a -> c
,你需要能够将 b
s 转换成 c
s。如果你能给我一个将 b
s 转换为 c
s 的函数,我可以在它们从 a -> b
函数出来后将其应用于值。
convertOutput :: (b -> c) -- the converter function
-> (a -> b) -- the function to convert
-> (a -> c) -- the resulting converted function
convertOutput f g = \x -> f (g x)
convertOutput
更广为人知的是 (.)
.
转换函数的输入以相反的方式进行。如果要将函数 b -> a
转换为函数 c -> a
,则必须将 c
转换为 b
。如果你能给我一个将 c
s 转换为 b
s 的函数,我可以在它们进入 b -> a
函数之前将其应用于值。
convertInput :: (c -> b) -- the converter function
-> (b -> a) -- the function to convert
-> (c -> a) -- the resulting converted function
convertInput f g = \x -> g (f x)
(偶尔你会听到 covariance 和 contravariance 这两个词与转换类型的想法有关。他们指的是这个想法该转换器函数可以沿两个方向之一进行。函数在其输出参数方面是协变的,在其输入方面是逆变的。)
回到问题,
Can you convert MonadIO m => IO a -> m b
to MonadIO m => m a -> m b
?
希望你能看到这个问题实际上是在寻求一种将 m a
变成 IO a
的方法。 (您必须将 m a
转换为 IO a
才能将其提供给原始函数。)MonadIO
包含一个方法 liftIO :: IO a -> m a
,它嵌入了一个 IO
计算到 "bigger" monad 中,它可能包含其他效果,但这与我们需要的完全相反。没有别的路可走。
也不应该有。 m a
这是一个可以执行各种未知效果的单子计算。在不知道效果是什么的情况下,您不能将任意单子值转换为 IO
。许多(大多数)单子效应无法直接转换为 IO
计算; 运行 例如,State
计算需要状态的起始值。
我正在学习 Haskell,感谢 echo
程序。它工作得很好,但我想对其进行一些改进并且 运行 遇到了麻烦。
userInput :: MonadIO m => ReaderT (IO String) m String
userInput = ask >>= liftIO -- this liftIO eliminates your need for join
echo :: MonadIO m => ReaderT (IO String) m ()
echo = userInput >>= liftIO . putStrLn -- this liftIO is just so you can use putStrLn in ReaderT
main :: IO ()
main = runReaderT echo getLine
我想做的是将 ReaderT (IO String)
更改为 ReaderT (i String)
并使其更通用,以便我可以将其换出以进行单元测试。问题是,因为我们在 userInput
中使用 liftIO
,它有点 ties i
和 IO
。有什么方法可以用其他东西替换 liftIO
来使下面的代码工作吗?
class Monad i => MonadHttp i where
hole :: MonadIO m => i a -> ReaderT (i a) m a
instance MonadHttp IO where
hole = liftIO
newtype MockServer m a = MockServer
{ server :: ReaderT (String) m a }
deriving (Applicative, Functor, Monad, MonadTrans)
instance MonadIO m => MonadHttp (MockServer m) where
-- MockServer m a -> ReaderT (MockServer m a) m1 a
hole s = s -- What goes here?
userInput :: (MonadHttp i, MonadIO m) => ReaderT (i String) m String
userInput = ask >>= hole
echo :: (MonadHttp i, MonadIO m) => ReaderT (i String) m ()
echo = userInput >>= \input ->
((I.liftIO . putStrLn) input)
main = runReaderT echo (return "hello" :: MockServer IO String)
请记住,ReaderT r m a
是 r -> m a
的 newtype
包装器。具体来说,MonadIO m => ReaderT (IO a) m b
等同于 MonadIO m => IO a -> m b
。那么让我重新表述一下你的问题:
Can you convert
MonadIO m => IO a -> m b
toMonadIO m => m a -> m b
?
答案是否,因为IO a
作为函数类型的输入出现。 (有时你会看到人们说 "in negative position",这与 "input" 的意思大致相同。)这里重要的是,转换函数输入与转换函数输出的方向相反。
让我们退后一步,考虑一个更一般的情况。如果你有一个函数 a -> b
并且你想转换它的输出以获得函数 a -> c
,你需要能够将 b
s 转换成 c
s。如果你能给我一个将 b
s 转换为 c
s 的函数,我可以在它们从 a -> b
函数出来后将其应用于值。
convertOutput :: (b -> c) -- the converter function
-> (a -> b) -- the function to convert
-> (a -> c) -- the resulting converted function
convertOutput f g = \x -> f (g x)
convertOutput
更广为人知的是 (.)
.
转换函数的输入以相反的方式进行。如果要将函数 b -> a
转换为函数 c -> a
,则必须将 c
转换为 b
。如果你能给我一个将 c
s 转换为 b
s 的函数,我可以在它们进入 b -> a
函数之前将其应用于值。
convertInput :: (c -> b) -- the converter function
-> (b -> a) -- the function to convert
-> (c -> a) -- the resulting converted function
convertInput f g = \x -> g (f x)
(偶尔你会听到 covariance 和 contravariance 这两个词与转换类型的想法有关。他们指的是这个想法该转换器函数可以沿两个方向之一进行。函数在其输出参数方面是协变的,在其输入方面是逆变的。)
回到问题,
Can you convert
MonadIO m => IO a -> m b
toMonadIO m => m a -> m b
?
希望你能看到这个问题实际上是在寻求一种将 m a
变成 IO a
的方法。 (您必须将 m a
转换为 IO a
才能将其提供给原始函数。)MonadIO
包含一个方法 liftIO :: IO a -> m a
,它嵌入了一个 IO
计算到 "bigger" monad 中,它可能包含其他效果,但这与我们需要的完全相反。没有别的路可走。
也不应该有。 m a
这是一个可以执行各种未知效果的单子计算。在不知道效果是什么的情况下,您不能将任意单子值转换为 IO
。许多(大多数)单子效应无法直接转换为 IO
计算; 运行 例如,State
计算需要状态的起始值。