[英]Reading lines in haskell until non-empty string
I am trying to read lines from input in Haskell until I find a non-empty line. 我试图从Haskell的输入中读取行,直到找到非空行。 Actually, I know how to do it simply using the following code:
实际上,我知道如何使用以下代码来做到这一点:
notEmpty [] = return ""
notEmpty (l:xs) = do
s <- l
if s /= "" then return s
else notEmpty xs
getLine' = notEmpty $ repeat getLine
Test (I typed two empty lines then 'foo'): 测试(我键入两个空行,然后输入“ foo”):
*> getLine'
foo
"foo"
However, for the sake of exercise, I am trying to achieve this using Monoids ( http://learnyouahaskell.com/functors-applicative-functors-and-monoids#monoids ), trying to mimick the First/getFirst Monoid (see link). 但是,为了便于练习,我尝试使用Monoids( http://learnyouahaskell.com/functors-applicative-functors-and-monoids#monoids )来实现这一点,尝试模仿First / getFirst Monoid(请参见链接) 。
I first created a Monoid on lists that fits my needs (concatenation only keeps the first argument): 我首先在适合我需要的列表上创建了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
Which works well on a infinite list of strings (thanks to laziness): 在无限的字符串列表上效果很好(由于懒惰):
> getFirstSt . mconcat . map FirstSt $ ["", "", "foo", "", "bar"] ++ repeat ""
"foo"
However, I can't get it to work in the IO Monad. 但是,我无法在IO Monad中使用它。 I tried the following:
我尝试了以下方法:
ioFirstSt = (=<<) (return . FirstSt)
getLine'' = getFirstSt <$> mconcat <$> (sequence . map ioFirstSt $ repeat getLine)
Which has the correct type: 具有正确的类型:
*> :t getLine''
getLine'' :: IO [Char]
However, Haskell keeps wanting to evaluate the whole list before giving it to mconcat
... In there a way to keep laziness while navigating in the Monoid/Monad scope? 但是,Haskell一直希望先评估整个列表,然后再将其提供给
mconcat
...在Monoid / Monad范围内导航时,有什么方法可以保持惰性?
Your thinking is excellent. 你的想法很棒。 Monoid is a great structure for this, but sadly as bheklilr points out,
sequence
is going to perform all the IO regardless. Monoid是一个很好的结构,但是令人遗憾的是,正如bheklilr指出的那样,
sequence
将执行所有IO。
It would be nice to make instance Monoid (IO String)
but we'd have to wrap it in a newtype
to get it to compile, but then we'd lose some interoperability with other IO, so let's just write the functions without the instance. 这将是很好使
instance Monoid (IO String)
,但我们不得不把它包在newtype
得到它来编译,但随后我们就会失去与其他IO一些互操作性,所以我们只写功能,而没有实例。
I like to use <>
instead of mappend
, but it's taken, and <|>
is also taken for Alternative
, which is like a Monoid structure for Applicative functors, and you should certainly look into it. 我喜欢使用
<>
而不是mappend
,但是它被采用了,而<|>
也被用于Alternative
,这类似于适用函子的Monoid结构,您当然应该对其进行研究。 I wrote a bit about Alternative in this answer . 我在这个答案中写了一些关于Alternative的文章。
Anyway, let's use <||>
and copy the fixity of <>
: 无论如何,让我们使用
<||>
并复制<>
的固定性:
infixr 6 <||>
We can make a monoid out of IO String
because we can check the value returned to see if it's ""
and then do the next action if not. 我们可以使用
IO String
来制作一个monoid,因为我们可以检查返回的值以查看其是否为""
,如果不是,则执行下一步操作。 That's equivalent to using ==
to check whether we have mempty
, so we can generalise to IO s
as long as s
is a Monoid with an Eq instance. 这等效于使用
==
检查我们是否有mempty
,因此只要s
是带有Eq实例的Monoid,我们就可以将IO s
推广为IO s
。 Secondly, we don't need it to be IO
, we could use any Monad: 其次,我们不需要将其用作
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
Notice that that's lazy about computing n
- it doesn't bother if we're happy with the output of m
. 请注意,这对计算
n
是懒惰的-如果我们对m
的输出感到满意,就不会打扰。 We could then define main = getLine <||> getLine <||> getLine >>= print
to give the user up to 3 chances of entering something non-blank for us to print. 然后,我们可以定义
main = getLine <||> getLine <||> getLine >>= print
为用户提供3次输入非空白内容供我们打印的机会。
Mathematically that's a monoid with identity 从数学上讲,这是具有身份的半身像
msempty :: (Monoid s, Monad m) => m s
msempty = return mempty
Let's also define the equivalent of mconcat :: Monoid s => [s] -> s
: 我们还要定义
mconcat :: Monoid s => [s] -> s
的等效项:
msconcat :: (Monoid s, Eq s, Monad m) => [m s] -> m s
msconcat = foldr (<||>) (return mempty)
Which lets us rewrite as main = msconcat [getLine,getLine,getLine] >>= print
这让我们重写为
main = msconcat [getLine,getLine,getLine] >>= print
The real test of laziness here is infinite lists of actions: 懒惰的真正考验是无限的动作列表:
main = msconcat (repeat getLine) >>= print
That works fine, and terminates within a finite time if the user ever does something other than enter nothing. 这可以正常工作,并且如果用户除了输入什么都不做以外,在有限的时间内终止。 Hooray!
万岁!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.