【问题标题】:Reading lines in haskell until non-empty string在haskell中读取行直到非空字符串
【发布时间】:2015-03-11 14:39:14
【问题描述】:

我正在尝试从 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(请参阅链接)。

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

【问题讨论】:

  • 我相信IO monad 的sequence 不够懒惰,它会在返回结果之前尝试评估整个IO 操作列表。
  • 顺便说一句,如果你简单地使用getLine' = getLine &gt;&gt;= \s -&gt; if s/="" then return s else getLine',你可以避免中间无限列表。尽管如此,幺半群方法还是很有趣的。

标签: haskell io getline string monoids


【解决方案1】:

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

理论 vs 实用性,向 Alternative 致敬

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

我喜欢用&lt;&gt; 代替mappend,但它被占用了,&lt;|&gt; 也被用作Alternative,这就像Applicative 函子的Monoid 结构,你当然应该研究一下。我在this answer 写了一些关于Alternative 的文章。

无论如何,让我们使用&lt;||&gt; 并复制&lt;&gt; 的固定性:

infixr 6 <||>

制作一个由 Eq Monoid 组成的 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 &lt;||&gt; getLine &lt;||&gt; getLine &gt;&gt;= print,让用户最多有 3 次机会输入非空白内容供我们打印。

标识和列表连接

在数学上这是一个有恒等式的幺半群

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

让我们也定义mconcat :: Monoid s =&gt; [s] -&gt; s的等价物:

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

这让我们重写为main = msconcat [getLine,getLine,getLine] &gt;&gt;= print

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

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

main = msconcat (repeat getLine) >>= print

这很好用,如果用户没有输入任何内容,它会在有限时间内终止。万岁!

【讨论】:

  • 感谢您非常详细的回答。对于 IO Monad 来说,序列似乎不是惰性的,这是有道理的,因为保证 IO 的顺序很重要。在 Monads/Monoid 中我还有很多东西要学!
猜你喜欢
  • 2021-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-19
  • 2014-11-10
相关资源
最近更新 更多