今天我试图连接两个IO字符串并且无法使其工作。
所以,问题是:假设我们有s1 :: IO String
和s2 :: IO String
。如何实现函数(+++) :: IO String -> IO String -> IO String
,它与(++) :: [a] -> [a] -> [a]
完全一样,但对于IO String?
更一般的问题是如何实现更一般的函数(+++) :: IO a -> IO a -> IO a
?或者甚至更一般?
答案 0 :(得分:17)
您可以使用liftM2
中的Control.Monad
:
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
> :t liftM2 (++)
liftM2 (++) :: Monad m => m [a] -> m [a] -> m [a]
或者,您可以使用do
表示法:
(+++) :: Monad m => m [a] -> m [a] -> m [a]
ms1 +++ ms2 = do
s1 <- ms1
s2 <- ms2
return $ s1 ++ s2
这两者都是等价的。实际上,liftM2
的定义实现为
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
val1 <- m1
val2 <- m2
return $ f val1 val2
很简单!它所做的就是从两个monadic动作中提取值,并为它们应用2个参数的函数。这与函数liftM
一起使用,该函数仅对一个参数的函数执行此操作。或者,正如其他人所指出的,您可以在IO
中使用Applicative
的{{1}}个实例,并使用类似的Control.Applicative
函数。
您可能会注意到泛型liftA2
在某些情况下与通用Applicative
具有相似的行为,其原因是因为它们在数学上非常相似。事实上,对于每个Monad
,您都可以使用Monad
。因此,您还可以在每个Applicative
中生成Functor
。有很多人对Functor-Applicative-Monad proposal已经存在一段时间感到兴奋,并且最终将在即将推出的GHC版本中实施。它们构成了Applicative
的非常自然的层次结构。
答案 1 :(得分:5)
import Control.Applicative (liftA2)
(+++) :: Applicative f => f [a] -> f [a] -> f [a]
(+++) = liftA2 (++)
现在在GHCI
>> getLine +++ getLine
Hello <ENTER>
World!<ENTER>
Hello World!
答案 2 :(得分:4)
(++) <$> pure "stringOne" <*> pure "stringTwo"
答案 3 :(得分:3)
实现函数
(+++)
...与(++) :: [a] -> [a] -> [a]
完全一样,但对于IO String
?
不要这样做,这是个坏主意。连接字符串是一个纯函数操作,没有理由在IO
monad中使用它。除了你需要结果的地方 - 我想在其他IO的中间某个地方。那么,只需使用do
- 表示法将读取的字符串绑定到变量名称,并在它们上使用普通的(++)
!
do
print "Now start obtaining strings..."
somePreliminaryActions
someMoreIOStuff
s1 <- getS1
s2 <- getS2
yetMoreIO
useConcat'dStrings (s1 ++ s2)
print "Done."
通过编写s12 <- liftA2 (++) getS1 getS2
可以使其更加紧凑。但我会这样做,而不是单独定义。
对于较长时间的操作,您当然可能希望定义一个单独的命名操作,但它应该是一个有意义的操作。
您不应将IO String
个对象视为“IO-strings”。它们不是,就像[Int]
不是“列表整数”一样。 IO String
类型的对象是操作,在发生时,可以在String
monad中提供IO
对象。它本身不是一个字符串。