我在Haskell的一些代码下面发帖。请将代码作为一个例子,我将使用它来解释我想知道的内容。
try :: [[Char]] -> [[Char]] -> [[Char]] -> [[Char]]
try (a:as) (b:bs) (c:cs) | ((checkIfCorrect a b c) == True) = a:b:[c]
| otherwise = try as bs cs
checkIfCorrect :: [Char] -> [Char] -> [Char] -> Bool
checkIfCorrect a b c = True
最终,checkIfCorrect
仅为一个参数组合返回True
。 checkIfCorrect
功能很长,所以我决定在这里发帖。在上面的示例中,函数checkIfCorrect
被应用(通过函数try
)到:第一个列表上的第一个[Char]
,第二个列表上的第一个[Char]
和第一个{{1}第三个列表。如果未满足第一个保护等式,则函数[Char]
将应用于:第一个列表上的第二个checkIfCorrect
...依此类推。我想要达到的目的是将函数[Char]
(按函数checkIfCorrect
)应用于所有列表中try
的所有组合
([Char]
)。我的意思是以下(例如):第一个列表上的第三个[[Char]]
,第二个列表上的第八个[Char]
,第三个列表上的第十一个[Char]
,依此类推。每个人都有。我怎么能轻易达到这个目标?
答案 0 :(得分:11)
我只想向您展示另一种撰写@WillemVanOnsem's code的方法。我猜你是Haskell的初学者,所以希望这个答案可以让你对一个丰富而美丽的想法有一个小小的了解,当你用语言进步时,你很快就会充分了解这个想法。 。如果您不立即了解有关此代码的所有内容,请不要过于担心;我只想尝试一下你的味道!
列表理解总是可以使用列表 monad 重新表达:
import Control.Monad (guard)
try as bs cs = head $ do
a <- as
b <- bs
c <- cs
guard $ checkIfCorrect a b c
return [a,b,c]
我使用do
表示法作为嵌套循环的特殊表示法:对于a
中的每个as
,b
中的每个bs
,对于c
中的每个cs
,如果[a,b,c]
返回checkIfCorrect
,我们会产生True
。列表推导的翻译很简单:&#34;枚举&#34;列表理解的一部分变成&#34;绑定&#34;使用<-
,&#34;过滤&#34; s转为对guard
的调用,&#34;产生&#34; s转为return
s。
在像Python这样的命令式语言中,您可以这样写:
def try(as, bs, cs):
for a in as:
for b in bs:
for c in cs:
if checkIfCorrect(a, b, c):
yield [a,b,c]
像西方新自由主义霸权下的政治一样,势在必行的代码逐渐向前发展。 &#34;楼梯&#34;像这样的代码实际上在命令式编程中频繁出现(想想&#34;回调地狱&#34;在JS中),因此发明monad来帮助抵消这种趋势。事实证明它们非常有用,为它们发明了一种特殊的语法,即do
- 符号。
答案 1 :(得分:5)
是,您可以通过列表理解使其看起来更优雅:
try :: [[Char]] -> [[Char]] -> [[Char]] -> [[Char]]
try as bs cs = head [ [a,b,c] | a <- as, b <- bs, c <- cs, checkIfCorrect a b c ]
-- \__ __/ \__________ ____________/ \__________ _______/
-- v v v
-- yield enumeration filter
代码的工作原理如下:列表理解的右侧部分包含&#34; 枚举 &#34;部分(由评论部分表示)。由于我们写a <- as, b <- bs, c <- cs
,这意味着 a
将从as
获取任何值,而每个a
,{{1}将取b
的任何值,等等。这意味着将发出每个可能的组合。
接下来是&#34; 过滤器 &#34;阶段:有一个谓词bs
将被调用,只有当该谓词返回checkIfCorrect a b c
时,结果才会被&#34;产生&#34;。
在左侧,我们看到&#34; 产量 &#34;。它描述了在过滤器成功的情况下要添加到列表中的内容(基于枚举)。如果发生这种情况,我们会将True
添加到该列表中。如果有多个此类配置成功,我们最终可能会得到一个包含多个解决方案的列表。但请注意,列表理解是 lazily :所以只要你不要求至少一个这样的元素,它就不会生成第一个元素,也不会生成第二个元素等。
现在我们还需要[a,b,c]
(在列表理解之前)。 head
返回列表的第一个元素。因此head :: [a] -> a
将返回满足条件的第一个元素。
答案 2 :(得分:3)
虽然Willem Van Onsem和The Orgazoid的答案都很好(赞成),但你也可以用更一般化的方式处理部分问题,而不仅仅是列表。
对于以下内容,您将需要这些导入:
import Control.Monad (MonadPlus, mfilter)
import Data.Maybe (fromMaybe, listToMaybe)
如果我正确理解了问题,您想尝试as
,bs
和cs
的所有组合。您通常可以使用Applicative
类型类:
combinations = (,,) <$> as <*> bs <*> cs
(,,)
是一个从三个单独的值创建三元组(三元素元组)的函数。
这适用于列表,因为列表是适用的:
*Prelude> (,,) <$> [1,2] <*> ["foo", "bar"] <*> [True, False]
[(1,"foo",True),(1,"foo",False),(1,"bar",True),(1,"bar",False),(2,"foo",True),(2,"foo",False),(2,"bar",True),(2,"bar",False)]
但它也适用于例如Maybe
S:
*Prelude> (,,) <$> Just 1 <*> Just "foo" <*> Just False
Just (1,"foo",False)
有了这个,您现在可以定义函数的核心:
try' :: MonadPlus m => ((a, a, a) -> Bool) -> m a -> m a -> m a -> m [a]
try' predicate as bs cs =
tripleToList <$> mfilter predicate combinations
where
combinations = (,,) <$> as <*> bs <*> cs
tripleToList (a, b, c) = [a, b, c]
你会注意到这个辅助函数是完全通用的。它适用于任何包含的元素MonadPlus
的任何a
实例。
以下是一些例子:
*Answer> try' (const True) ["foo", "bar", "baz"] ["qux", "quux", "quuz", "corge"] ["grault", "garply"]
[["foo","qux","grault"],["foo","qux","garply"],["foo","quux","grault"],["foo","quux","garply"],["foo","quuz","grault"],["foo","quuz","garply"],["foo","corge","grault"],["foo","corge","garply"],["bar","qux","grault"],["bar","qux","garply"],["bar","quux","grault"],["bar","quux","garply"],["bar","quuz","grault"],["bar","quuz","garply"],["bar","corge","grault"],["bar","corge","garply"],["baz","qux","grault"],["baz","qux","garply"],["baz","quux","grault"],["baz","quux","garply"],["baz","quuz","grault"],["baz","quuz","garply"],["baz","corge","grault"],["baz","corge","garply"]]
*Answer> try' (const False) ["foo", "bar", "baz"] ["qux", "quux", "quuz", "corge"] ["grault", "garply"]
[]
*Answer> try' (const True) (Just "foo") (Just "bar") (Just "baz")
Just ["foo","bar","baz"]
*Answer> try' (const False) (Just "foo") (Just "bar") (Just "baz")
Nothing
您应该注意到,如果predicate
始终返回False
,您将无法获得任何回报。对于列表,您将获得空列表;对于Maybe
,您实际上得到Nothing
。
到目前为止,它都是通用的,但checkIfCorrect
不是。看起来你也想要只获得匹配的第一个元素。您可以通过将try'
与checkIfCorrect
:
try :: [String] -> [String] -> [String] -> [String]
try as bs cs = fromMaybe [] $ listToMaybe $ try' isCorrect as bs cs
where isCorrect (a, b, c) = checkIfCorrect a b c
在这里,我创建了一个私有isCorrect
函数,以便取消checkIfCorrect
函数。然后我使用listToMaybe
和fromMaybe
的组合来返回结果列表的第一个元素。这里的其他答案使用head
,但是如果列表为空则会抛出异常,所以我使用了这个组合,因为它是安全的。