【发布时间】:2017-05-04 11:27:51
【问题描述】:
我很难理解 liftM2 在 haskell 中的工作原理。
我写了以下代码,但它没有输出任何东西。
import Control.Monad
main = liftM2 (\a b -> putStrLn$show$(+) a b) readLn readLn
【问题讨论】:
我很难理解 liftM2 在 haskell 中的工作原理。
我写了以下代码,但它没有输出任何东西。
import Control.Monad
main = liftM2 (\a b -> putStrLn$show$(+) a b) readLn readLn
【问题讨论】:
从liftM2的类型开始会有所帮助:
liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
第一个参数是 2 个参数的函数,例如 (+)。通常,您会像这样使用(+):
> 3 + 5
8
但是,您没有两个 Num a => a 类型的值;您正在使用readLn :: Read a => IO a 获取Num a => IO a 类型的值。如果您尝试直接添加这些值:
:t (+) readLn readLn
(+) readLn readLn :: (Read a, Num (IO a)) => IO a
它需要IO a 才能拥有Num 实例。也就是说,您不想添加readLn 的返回值;您想在这些返回值中添加数字 wrapped。你可以明确地这样做:
do
x <- readLn
y <- readLn
return $ x + y
或者,您可以“修改”(+) 以隐式解包参数,然后将结果打包。这就是 liftM2 所做的:它接受一个 2 参数函数,并将其“提升”到 monad 中,以便它可以处理包装的值。
> :t (+)
(+) :: Num a => a -> a -> a
> :t liftM2 (+)
liftM2 (+) :: (Num r, Monad m) => m r -> m r -> m r
所以虽然(+) 3 5 :: Num a => a,liftM2 (+) $ (return 3) (return 5) :: (Monad m, Num a) => m a。前者计算为8,后者计算为return 8(无论return 对特定的monad 做什么)。一些非IO 的例子:
> (liftM2 (+)) (Just 3) (Just 5)
Just 8
> (liftM2 (+)) [3] [5]
[8]
> (liftM2 (+)) (Right 3) (Right 5)
Right 8
liftM2 与map 非常相似;事实上,liftM(提升 1-argument 函数的版本)只是 map 的另一个名称。
【讨论】:
liftM2 (+) $ (IO 3) (IO 5) == IO 8 行可能会产生误导。
IO 3 和IO 5 实际上不是有效的Haskell 表达式? :)
我不认为编译器可以在 $ 周围没有空格的情况下解析它。然后,这里 main 的类型为 IO (IO ())
如果要对“内部”IO求和,可以使用liftM2 (+),然后打印结果。
例如:
main :: IO ()
main = print =<< liftM2 (+) readLn readLn
或者使用 do 表示法:
main :: IO ()
main = do
s <- liftM2 (+) readLn readLn
print s
【讨论】:
liftM2 读取两个整数并打印它们的总和?
$ 没有空格只有在-XTemplateHaskell 处于活动状态时才会出现问题。但是,是的,总是在它周围留出空间可能是个好主意,这样你就不必担心它,以防你以后需要使用 TH。 $ 无论如何都是一个非常“空间”的运算符,它的优先级很低。