Haskell:将函数应用于所有参数组合

时间:2017-06-18 00:28:35

标签: haskell

我在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仅为一个参数组合返回TruecheckIfCorrect功能很长,所以我决定在这里发帖。在上面的示例中,函数checkIfCorrect被应用(通过函数try)到:第一个列表上的第一个[Char],第二个列表上的第一个[Char]和第一个{{1}第三个列表。如果未满足第一个保护等式,则函数[Char]将应用于:第一个列表上的第二个checkIfCorrect ...依此类推。我想要达到的目的是将函数[Char](按函数checkIfCorrect)应用于所有列表中try的所有组合  ([Char])。我的意思是以下(例如):第一个列表上的第三个[[Char]],第二个列表上的第八个[Char],第三个列表上的第十一个[Char],依此类推。每个人都有。我怎么能轻易达到这个目标?

3 个答案:

答案 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中的每个asb中的每个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)

如果我正确理解了问题,您想尝试asbscs的所有组合。您通常可以使用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函数。然后我使用listToMaybefromMaybe的组合来返回结果列表的第一个元素。这里的其他答案使用head,但是如果列表为空则会抛出异常,所以我使用了这个组合,因为它是安全的。