这是两个非常简单的函数f
和g
。
{-# LANGUAGE ScopedTypeVariables #-}
module Test where
import Control.Applicative
f :: IO ()
f = do
y <- (<*>) (pure (show . (*10))) (read <$> readFile "data")
writeFile "out" y
g :: IO ()
g = do
y <- (readFile "data" >>= return . show . (*10) . read)
writeFile "out" y
阅读的文件和*10
中的f
是以pure
和(<*>)
的应用样式编写的。读取的文件和*10
中的g
是以monadic样式编写的,>>=
。 (我故意避免在liftM
中使用g
来强调下面的问题。)
f
和g
之间的语义差异是什么?或者在这种情况下,它只是一种风格选择吗?
答案 0 :(得分:8)
show . (*10) . read
是一个“非monadic”函数 - 我的意思是,它在IO monad中没有任何作用,正如你可以从它的类型中看到的那样。
>>= return .
可以缩短为
`liftM`
但是
`liftM`
应始终等同于
`fmap`
fmap
既不需要monad类型类也不需要应用类型类,它只需要仿函数类型类。
现在把注意力转向应用版本,这个:
(<*>) (pure ...
相当于<$>
,只是fmap
。
因此,在这两种情况下,我们“真的”只是在编写器之间进行仿函数操作,尽管你以稍微不同的方式组合了函数(我们需要应用一个或多个“法则”)将两个版本互相翻译),语义是 - 或应该 - 相同。无论如何,他们肯定是IO monad。
答案 1 :(得分:1)
是的,选择纯粹是风格,但两者都可以整理成更具惯用性:
应用函数最好写成运算符,并且无点:
f :: IO ()
f = show . (*10) . read <$> readFile "data" >>= writeFile "out"
在monadic风格中,如果你更全心全意地不是无点的,那么看起来更整洁,避免操作员:
g :: IO ()
g = do
y <- readFile "data"
let x = show . (*10) . read $ y
writeFile "out" x