1

我正在尝试从 Haskell 中的输入中读取行,直到找到非空行。实际上,我知道如何简单地使用以下代码来做到这一点:

notEmpty [] = return ""
notEmpty (l:xs) = do
  s <- l
  if s /= "" then return s
             else notEmpty xs

getLine' = notEmpty $ repeat getLine

测试(我输入了两个空行,然后是“foo”):

*> getLine'


foo
"foo"

但是,为了锻炼,我试图使用 Monoids(http://learnyouahaskell.com/functors-applicative-functors-and-monoids#monoids)来实现这一点,试图模仿 First/getFirst Monoids(见链接) .

我首先在满足我需要的列表上创建了一个 Monoid(连接只保留第一个参数):

newtype FirstSt a = FirstSt { getFirstSt :: [a] }
    deriving (Eq, Ord, Read, Show)

instance Monoid (FirstSt a) where
    mempty = FirstSt []
    FirstSt [] `mappend` x = x
    FirstSt s  `mappend` _ = FirstSt s

它适用于无限的字符串列表(由于懒惰):

> getFirstSt . mconcat . map FirstSt $ ["", "", "foo", "", "bar"] ++ repeat ""
"foo"

但是,我无法让它在 IO Monad 中工作。我尝试了以下方法:

ioFirstSt = (=<<) (return . FirstSt)
getLine'' = getFirstSt <$> mconcat <$> (sequence . map ioFirstSt $ repeat getLine)

哪个具有正确的类型:

*> :t getLine''
getLine'' :: IO [Char]

然而,Haskell 一直想在将整个列表交给mconcat...之前评估整个列表。有没有办法在 Monoid/Monad 范围内导航时保持懒惰?

4

1 回答 1

1

你的想法很棒。Monoid 是一个很好的结构,但遗憾的是,正如 bheklilr 指出的那样,无论如何sequence都要执行所有的 IO。

理论与实用性对替代方案的认可

这样做会很好,instance Monoid (IO String)但我们必须将它包装在 anewtype中才能编译它,但是我们会失去与其他 IO 的一些互操作性,所以让我们只编写没有实例的函数。

我喜欢使用<>而不是mappend,但它被采用,并且<|>也被用于Alternative,这就像 Applicative 函子的 Monoid 结构,你当然应该研究它。我在这个答案中写了一些关于 Alternative 的内容。

无论如何,让我们使用<||>并复制 的固定性<>

infixr 6 <||>

制作 Eq Monoid 的 Monad 的 Monoid

我们可以制作一个幺半群,IO String因为我们可以检查返回的值是否是"",如果不是,则执行下一步操作。这相当于用==来检查我们是否有mempty,因此我们可以推广到IO s只要s是带有 Eq 实例的 Monoid。其次,我们不需要它IO,我们可以使用任何 Monad:

(<||>) :: (Monoid s, Eq s, Monad m) => m s -> m s -> m s
m <||> n = do
    x <- m
    if x == mempty then n else return x

请注意,这对于计算来说是懒惰的n——如果我们对m. 然后我们可以定义main = getLine <||> getLine <||> getLine >>= print给用户最多 3 次输入非空白内容供我们打印的机会。

身份和列表连接

从数学上讲,这是一个有身份的幺半群

msempty :: (Monoid s, Monad m) => m s
msempty = return mempty

让我们也定义等价于mconcat :: Monoid s => [s] -> s

msconcat :: (Monoid s, Eq s, Monad m) => [m s] -> m s
msconcat = foldr (<||>) (return mempty)

这让我们重写为main = msconcat [getLine,getLine,getLine] >>= print

懒惰地结合无限多的一元幺半群

这里对懒惰的真正考验是无限的行动列表:

main = msconcat (repeat getLine) >>= print

这很好用,如果用户除了不输入任何内容之外做了其他事情,它就会在有限的时间内终止。万岁!

于 2015-01-13T18:05:03.210 回答