读取 haskell 中的行直到非空字符串

Reading lines in haskell until non-empty string

我试图从 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 Monoid(参见 link)。

我首先在符合我需要的列表上创建了一个 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 范围内导航时保持惰性?

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

理论 vs 实践,点头选择

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

我喜欢用<>而不是mappend,但是它被取了,<|>也取了Alternative,这就像Applicative的Monoid结构仿函数,你当然应该研究它。我在 this answer.

中写了一些关于 Alternative 的文章

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

infixr 6 <||>

用等式幺半群的单子制作一个幺半群

我们可以从 IO String 中创建一个幺半群,因为我们可以检查返回的值以查看它是否为 "",如果不是,则执行下一步操作。这相当于使用 == 来检查我们是否有 mempty,因此只要 s 是具有 Eq 实例的 Monoid,我们就可以泛化为 IO s。其次,我们不需要它是 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

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