使用(>>)打印字符的IO功能

时间:2015-04-06 01:11:52

标签: haskell

Thinking Functionally in Haskell提供以下功能:

f :: String -> IO ()
f xs = foldr (>>) (return ()) (map putChar xs) >> putChar '\n'

我对foldr (>>)感到困惑。

ghci> :t foldr (>>)
foldr (>>) :: Monad m => m b -> [m a] -> m b

在这里使用(>>)的目的似乎是将Monad m“提升”到剩余的foldr个参数上,不是吗?

是否可以在不使用>>的情况下编写此函数?

3 个答案:

答案 0 :(得分:5)

在IO monad的上下文中,>>执行一个操作,然后执行另一个操作,而不在其间传递任何数据。当您不关心返回值时,它只是绑定(>>=)。

putChar x,其中x是一些Char,其类型为IO ()。它是一个返回()的IO动作('没有')。 map putChar xs获取一个字符列表并返回一系列IO操作,所有操作都返回“没有”。

通过foldr >>这个列表,我们只是按顺序执行每个操作,而不是关心任何中间结果。正如@leftaroundabout在评论中指出的那样,这通常用sequence_表示。

是的,我们当然可以在不使用>>的情况下编写此内容。

f' :: String -> IO ()
f' [] = return ()
f' (x:xs) = do putChar x
               f' xs
-- or
f [] = return ()
f' (x:xs) = (putChar x) >> f' xs
-- or
f' xs = sequence_ $ map putChar xs

修改

@rampion在评论中提出了一个很好的观点,我只是真的模糊了>>。在所有这三种情况下,在幕后的某个地方都有一个>>。这是使用普通绑定运算符>>=编写的函数。

-- same base case
f' (x:xs) = putChar x >>= \_ -> f' xs

但我再次作弊,只是根据>>扩展了>>=的定义。我们在monad中组合函数。我们必须在某处使用>>=>>=,否则为什么要使用monad?

答案 1 :(得分:2)

假设我写f "hey"。我们从String

开始
f "hey"

实际上是[Char]

f ['h', 'e', 'y']

我们将其替换为f

的定义
foldr (>>) (return ()) (map putChar ['h', 'e', 'y']) >> putChar '\n'
                                    ~~~~~~~~~~~~~~~

我们生成一系列操作以打印字符[IO ()]

foldr (>>) (return ()) [putChar 'h', putChar 'e', putChar 'y'] >> putChar '\n'
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

并使用foldr使用>>

对它们进行排序
putChar 'h' >> putChar 'e' >> putChar 'y' >> return () >> putChar '\n'

这是do语句的desberared版本,它不会将返回值与<-绑定

do
  putChar 'h'
  putChar 'e'
  putChar 'y'
  return ()
  putChar '\n'

所以>>或等效函数是必需的,因为这个函数正在做的是构建一个IO动作来打印你给它的String中的每个角色,就像在你用do符号写了一系列的陈述。

当然,这是常见的事情,因此foldr (>>) (return ())被称为Control.Monad.sequence_sequence_ . map f被称为Control.Monad.mapM_ f

答案 2 :(得分:0)

  

是否可以在不使用>>的情况下编写此函数?

有三种方法可以回答这个问题。第一个是:是的,它可以用>>=实现,因为>>可以缩减为>>=

a >> b = a >>= \_ -> b

第二个是:不是真的,因为Haskell中的任何实际实现都会以>>为基础,或以>>的方式编写>>=

第三个,更复杂的答案是:是的,从某种意义上说,因为这个函数从根本上不使用monad的全部功能,只有 monoids 的功能。 IO ()是一个幺半群,>>作为关联操作,return ()作为其标识元素。那么函数正在做什么,从根本上说,是foldMap operation from the Foldable class做的事情 - 通过将元素类型映射到适当的monoid,将列表减少为单个值。

您可以通过将每个人映射到他们的工资并使用+0 monoid折叠来获取人员工资列表的总和。您可以通过将每个字符映射到打印它的IO ()操作,然后使用>>return ()进行折叠来打印一系列字符。