为什么这个简单的组合不起作用?

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; 另外两个是无声的,都下runhaskellrepl.

2我确实意识到那些都是单行的,但操作的顺序在唯一有效的操作中感到非常混乱.

Wil*_*sem 4

也许最好的写法是:

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 ~ Intb ~ 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

  • 也称为“加入”。:) (2认同)
  • 我不太愿意将其称为“展开”,因为这意味着您可以展开单个“IO f”来获取“f”。我喜欢将其视为外部“IO”“吸收”内部“IO”,从而使“f”安全地包裹在整个过程中。 (2认同)