读取 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
这很好用,如果用户除了不输入任何内容外还做了其他事情,它会在有限的时间内终止。万岁!
我试图从 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.
无论如何,让我们使用<||>
并复制<>
的固定性:
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
这很好用,如果用户除了不输入任何内容外还做了其他事情,它会在有限的时间内终止。万岁!