Haskell:有条件地打破一个循环

时间:2015-04-15 10:53:36

标签: loops haskell break

我想在这种情况下打破一个循环:

import Data.Maybe (fromJust, isJust, Maybe(Just))

tryCombination :: Int -> Int -> Maybe String
tryCombination x y 
               | x * y == 20 = Just "Okay"
               | otherwise = Nothing

result :: [String]
result = map (fromJust) $ 
    filter (isJust) [tryCombination x y | x <- [1..5], y <- [1..5]]

main = putStrLn $ unlines $result

想象一下,“tryCombination”比这个例子更复杂。它耗费了大量的CPU功耗。这不是25种可能性的评估,而是26 ^ 3。

因此,当“tryCombination”找到给定组合的解决方案时,它返回Just,否则返回Nothing。如何在第一个找到的解决方案上立即打破循环?

4 个答案:

答案 0 :(得分:7)

简单解决方案:findjoin

看起来你正在寻找Data.List.findfind具有类型签名

find :: (a -> Bool) -> [a] -> Maybe a

所以你会做类似

的事情
result :: Maybe (Maybe String)
result = find isJust [tryCombination x y | x <- [1..5], y <- [1..5]]

或者,如果您不想要Maybe (Maybe String)(为什么会这样?),您可以将其与Control.Monad.join折叠在一起,其中包含签名

join :: Maybe (Maybe a) -> Maybe a

所以你有

result :: Maybe String
result = join $ find isJust [tryCombination x y | x <- [1..5], y <- [1..5]]

更高级的解决方案:asum

如果您想要更高级的解决方案,可以使用具有签名

Data.Foldable.asum
asum :: [Maybe a] -> Maybe a

它的作用是从众多列表中挑选出第一个Just值。它通过使用Alternative Maybe实例来完成此操作。 Alternative Maybe实例的工作方式如下:(导入Control.Applicative以访问<|>运算符)

λ> Nothing <|> Nothing
Nothing
λ> Nothing <|> Just "world"
Just "world"
λ> Just "hello" <|> Just "world"
Just "hello"

换句话说,它从两个备选项中选择第一个Just值。想象一下,将<|>放在列表的每个元素之间,以便

[Nothing, Nothing, Just "okay", Nothing, Nothing, Nothing, Just "okay"]

转向

Nothing <|> Nothing <|> Just "okay" <|> Nothing <|> Nothing <|> Nothing <|> Just "okay"

这正是asum函数的作用!由于<|>是短路的,因此它只会评估第一个Just值。有了它,你的功能就像

一样简单
result :: Maybe String
result = asum [tryCombination x y | x <- [1..5], y <- [1..5]]

为什么你想要这个更先进的解决方案?它不仅更短;一旦你知道了这个习语(即当你熟悉Alternativeasum时),通过阅读代码的前几个字符,可以更加清楚这个函数是做什么的。

答案 1 :(得分:4)

要回答您的问题,find功能就是您所需要的。获得Maybe (Maybe String)后,您可以使用Maybe String

将其转换为join

虽然find更好,更可读并且肯定只做所需,但我不太确定你在问题中的代码效率低下。懒惰的评估可能会解决这个问题并仅计算所需的内容,(仍然可以消耗额外的内存)。如果您有兴趣,请尝试基准测试。

答案 2 :(得分:3)

在这种情况下,懒惰实际上可以解决这个问题。

通过调用unlines,您正在请求 1 输出的所有,所以很明显它在第一次成功后无法停止{ {1}}。但如果您只需要一场比赛,只需使用tryCombination(来自listToMaybe);如果根本没有匹配项,或者Data.Maybe找到第一个匹配项,它会将您的列表转换为Nothing

懒惰意味着列表中的结果只会按需评估;如果您从不要求列表中的任何其他元素,那么生成它们所需的计算(或者甚至查看是否列表中的任何其他元素)将永远不会被运行!

这意味着您通常不必像在命令式语言中那样“打破循环”。您可以将完整的“循环”编写为列表生成器,并且消费者可以独立决定他们想要多少。这个想法的极端情况是Haskell非常乐意生成甚至过滤无限列表;它只会运行生成代码,足以生成与您稍后检查的元素完全相同的元素。


1 实际上,即使Just产生了一个惰性字符串,所以如果你是只读取结果连接字符串的第一行,您可以仍然“提前打破循环”!但你在这里打印整件事。

答案 3 :(得分:1)

您正在寻找的评估策略正是Maybe MonadPlus实例的目的。特别是,函数msum的类型专门针对

msum :: [Maybe a] -> Maybe a

直观地说,此版本的msum会获取可能失败的计算列表,一个接一个地执行它们,直到第一次计算成功并返回相应的结果。因此,result将成为

result :: Maybe String
result = msum [tryCombination x y | x <- [1..5], y <- [1..5]]

最重要的是,通过从Maybe推广到MonadPlus的任何实例,您可以使代码在某种意义上与确切的评估策略无关:

tryCombination :: MonadPlus m => Int -> Int -> m (Int,Int)
-- For the sake of illustration I changed to a more verbose result than "Okay".
tryCombination x y 
    | x * y == 20 = return (x,y) -- `return` specializes to `Just`.
    | otherwise   = mzero        -- `mzero` specializes to `Nothing`.

result :: MonadPlus m => m (Int,Int)
result = msum [tryCombination x y | x <- [1..5], y <- [1..5]]

要获得所需的行为,请运行以下命令:

*Main> result :: Maybe (Int,Int)
Just (4,5)

但是,如果您确定不仅需要第一个组合而且需要所有这些组合,请使用[]的{​​{1}}实例:

MonadPlus

我希望这比概念层面更有助于提供解决方案。

PS:我刚才注意到*Main> result :: [(Int,Int)] [(4,5),(5,4)] MonadPlus对于此目的确实有点过于严格,msumAlternative已经足够了。