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
个参数上,不是吗?
是否可以在不使用>>
的情况下编写此函数?
答案 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 ()
进行折叠来打印一系列字符。