为什么这种简单的合成方法不起作用?

时间:2018-09-24 08:56:54

标签: haskell monads applicative

我最近需要将head放在两个单子操作之间。这是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

该程序格式正确,可在不发出警告的情况下进行编译。但是,以上3个版本中只有一个版本有效。 1 。为什么会这样?

特别是,将fg与中间的head链接在一起的最佳方法是什么?我最终使用了第3个(以do表示法的形式),但我真的不喜欢它,因为它应该是微不足道的单线 2


1 剧透警报:第三个是唯一打印1的警报;在runhaskellrepl下,其他两个都保持沉默。

2 我确实意识到这些都是单线的,但是操作顺序在唯一可行的操作中确实让人感到困惑。

2 个答案:

答案 0 :(得分:4)

第一个和第二个完全相同,因为在函数monad中,head是左关联的,而<$>是一个函数,而. g . head <$> f = fmap (print . head) (return [1..5] :: IO [Int]) = do { x <- (return [1..5] :: IO [Int]) ; return ( print (head x) ) } = do { let x = [1..5] ; return ( print (head x) ) } :: IO _whatever = return ( print 1 ) :: IO (IO ()) 。然后,

return

我们那里有太多 = fmap (print . head) (return [1..5] :: IO [Int]) = return (print (head [1..5])) = return (print 1) 个。实际上,

    (head <$> f) >>= g
  = (fmap head $ return [1..5]) >>= print
  = (return (head [1..5])) >>= print
  = (return 1) >>= print

是一个较短的推导。

第三个是

array

这显然还可以。

答案 1 :(得分:4)

也许最好的写法是:

f >>= g . head

或更详细的形式:

f >>= (g . head)

因此,我们基本上对fmap的值执行f(因此,我们将head单子中包装的值的IO取值),然后传递然后转到g,例如:

(head <$> f) >>= g

在语义上是相同的。

但是现在如果我们使用g <$> head <$> f会发生什么?让我们首先分析类型:

f :: IO [Int]
g :: Int -> IO ()
(<$>) :: Functor m => (a -> b) -> m a -> m b

(我在这里使用m是为了避免与f函数混淆)

这的规范形式是:

((<$>) ((<$>) g head) f)

第二个(<$>)采用g :: Int -> IO ()head :: [c] -> c作为参数,这意味着a ~ Intb ~ IO ()m ~ (->) [c]。结果是:

 (<$>) g head :: (->) [c] (IO ())

或更简单:

g <$> head :: [c] -> IO ()

第一个(<$>)函数因此采用了g <$> head :: [c] -> IO ()IO [Int]作为参数,这意味着m ~ IOa ~ [Int]c ~ Intb ~ IO (),因此我们得到以下类型:

(<$>) (g <$> head) f :: IO (IO ())

因此,我们没有执行任何实际操作:我们将fmap的{​​{1}}列表添加到[Int]动作(包装在IO中)中。您可能会看到它是IO:您没有“评估” return (print 1),而是print 1包裹在return中。

您当然可以在此处“吸收”外部IO,然后使用内部IO,例如:

IO

或更短:

evalIO :: IO (IO f) -> IO f
evalIO res = do
   f <- res
   f

(可以将其概括为各种evalIO :: IO (IO f) -> IO f evalIO res = res >>= id ,但这与这里无关)。

Monad也称为join :: Monad m => m (m a) -> m a