我想在这种情况下打破一个循环:
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。如何在第一个找到的解决方案上立即打破循环?
答案 0 :(得分:7)
find
和join
看起来你正在寻找Data.List.find
。 find
具有类型签名
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]]
为什么你想要这个更先进的解决方案?它不仅更短;一旦你知道了这个习语(即当你熟悉Alternative
和asum
时),通过阅读代码的前几个字符,可以更加清楚这个函数是做什么的。
答案 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
对于此目的确实有点过于严格,msum
和Alternative
已经足够了。