2

我正在学习 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)使其更通用,以便我可以将其换出以进行单元测试。问题是,因为我们liftIOuserInput其中使用了 . 有什么方法可以用其他东西代替来使下面的代码工作吗?iIOliftIO

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)
4

1 回答 1

2

请记住,这ReaderT r m a是. 具体来说,等价于。所以让我重新表述你的问题:newtyper -> m aMonadIO m => ReaderT (IO a) m bMonadIO m => IO a -> m b

你能转换MonadIO m => IO a -> m bMonadIO m => m a -> m b?

答案是否定的,因为它IO a作为函数类型的输入出现。(有时你会看到人们说“innegative position”,这与“input”的意思大致相同。)这里重要的是,转换函数输入的工作方向与转换函数输出的方向相反。


让我们退后一步,考虑一个更一般的情况。如果你有一个函数a -> b,并且你想将它的输出转换为一个函数a -> c,你需要能够将bs转换为cs。如果你能给我一个将bs转换为cs 的函数,我可以在值从函数中出来后将其应用于值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,则必须将cs转换为bs。如果你能给我一个将cs转换为bs 的函数,我可以在值进入函数之前将其应用于值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)

(有时您会听到与转换类型的想法相关的协变逆变这两个词。它们指的是转换器函数可以朝两个方向之一发展的想法。函数的输出参数是协变的,输入是逆变的。 )


回到问题,

你能转换MonadIO m => IO a -> m bMonadIO m => m a -> m b?

希望您能看到这个问题实际上是在寻求一种将 anm a变成IO a. (您必须将m a转换为 anIO a才能将其提供给原始函数。)MonadIO包含一个方法,liftIO :: IO a -> m a,它将IO计算嵌入到可能包含其他效果的“更大” monad 中,但这与我们需要的完全相反. 没有别的办法。

也不应该有。m a这是一个可以执行各种未知效果的单子计算。IO在不知道效果是什么的情况下,您无法将任意一元值转换为。许多(大多数)单子效应并没有直接转化为IO计算;例如,运行State计算需要状态的起始值。

于 2018-12-30T04:16:44.507 回答