Bar*_*icz 7 monads haskell applicative
我最近需要head介入两个monadic操作.这是SSCCE:
module Main where
f :: IO [Int]
f = return [1..5]
g :: Int -> IO ()
g = print
main = do
putStrLn "g <$> head <$> f"
g <$> head <$> f
putStrLn "g . head <$> f"
g . head <$> f
putStrLn "head <$> f >>= g"
head <$> f >>= g
Run Code Online (Sandbox Code Playgroud)
该程序格式良好,无需警告即可编译.但是,上述3个版本中只有一个版本可用1.这是为什么?
具体而言,这将是链接的最佳方式f,并g连同head中间?我最终使用了第三个(以do符号的形式),但我并不喜欢它,因为它应该是一个简单的单行2.
1 Spoiler alert:第3个是唯一打印的1; 另外两个是无声的,都下runhaskell和repl.
2我确实意识到那些都是单行的,但操作的顺序在唯一有效的操作中感到非常混乱.
也许最好的写法是:
f >>= g . head
Run Code Online (Sandbox Code Playgroud)
或者以更详细的形式:
f >>= (g . head)
Run Code Online (Sandbox Code Playgroud)
所以我们基本上对fmapfor 的值执行 an f(因此我们获取包含head在 monad 中的值IO),然后我们将 then 传递给g,例如:
(head <$> f) >>= g
Run Code Online (Sandbox Code Playgroud)
语义上是相同的。
但现在如果我们使用会发生什么g <$> head <$> f?我们先来分析一下类型:
f :: IO [Int]
g :: Int -> IO ()
(<$>) :: Functor m => (a -> b) -> m a -> m b
Run Code Online (Sandbox Code Playgroud)
(我m在这里使用是为了避免与f函数混淆)
其规范形式是:
((<$>) ((<$>) g head) f)
Run Code Online (Sandbox Code Playgroud)
第二个(<$>)以g :: Int -> IO ()和head :: [c] -> c作为参数,因此这意味着a ~ Int、b ~ IO ()、 和m ~ (->) [c]。所以结果是:
(<$>) g head :: (->) [c] (IO ())
Run Code Online (Sandbox Code Playgroud)
或者更简洁:
g <$> head :: [c] -> IO ()
Run Code Online (Sandbox Code Playgroud)
因此,第一个函数将、 和(<$>)用作参数,因此这意味着、、、、 因此我们获得类型:g <$> head :: [c] -> IO ()IO [Int]m ~ IOa ~ [Int]c ~ Intb ~ IO ()
(<$>) (g <$> head) f :: IO (IO ())
Run Code Online (Sandbox Code Playgroud)
因此,我们不执行任何实际操作:我们fmap列出[Int]一个IO操作(包含在 中IO)。您可以将其视为return (print 1):您不“评估” print 1,而是return评估包裹在 . 中的内容IO。
您当然可以IO在这里“吸收”外部,然后使用内部IO,例如:
evalIO :: IO (IO f) -> IO f
evalIO res = do
f <- res
f
Run Code Online (Sandbox Code Playgroud)
或更短:
evalIO :: IO (IO f) -> IO f
evalIO res = res >>= id
Run Code Online (Sandbox Code Playgroud)
(这可以推广到所有类型的Monads,但这与这里无关)。
也evalIO称为join :: Monad m => m (m a) -> m a。