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

时间:2015-01-13 15:25:38

标签: haskell io getline string monoids

我试图从Haskell中的输入读取行,直到找到非空行。 实际上,我只知道如何使用以下代码:

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

getLine' = notEmpty $ repeat getLine

测试(我打了两个空行然后&#39; foo&#39;):

*> getLine'


foo
"foo"

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

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

1 个答案:

答案 0 :(得分:1)

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

理论与实用性,同意对替代

制作instance Monoid (IO String)会很好,但我们必须将它包装在newtype中才能编译,但是我们会失去与其他IO的互操作性,所以让我们来吧在没有实例的情况下编写函数。

我喜欢使用<>而不是mappend,但它已被采用,<|>也用于Alternative,这就像应用函子的Monoid结构一样,你当然应该调查它。我在this answer中写了一些关于Alternative的文章。

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

infixr 6 <||>

制作Monq Monq的Eq Monoids

我们可以从IO String中生成一个monoid,因为我们可以检查返回的值以查看它是否为"",然后执行下一个操作。这相当于使用==检查我们是否有mempty,因此只要IO s是带有Eq实例的Monoid,我们就可以推广到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

懒洋洋地结合无限多个monadic monoids

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

main = msconcat (repeat getLine) >>= print

工作正常,并且如果用户做了除了什么都不输入之外的其他事情,则在有限的时间内终止。万岁!