Haskell太多的条款,任何替代建议

时间:2009-11-22 18:40:41

标签: haskell functional-programming

我在Haskell中是全新的,在编写小程序时,我通常最终会有太多where子句来检查函数中的很多东西,所以编写where子句或者还有其他好的替代方法是好的做法吗?

例如在下面的代码中,我试图找到二维列表的每一行中是否存在ant重复元素,它是有效的,并且每个东西都是相同功能的内容,但我不满意代码看起来如何,我发现它更接近问题的风格,所以我正在寻找有经验的人在那里的任何建议或想法。

noDups :: [[a]] -> Bool
noDups du = and (checkSu du)
       where
       checkDup []     = []
       checkDup (x:xs) = checkRow x ++ checkDup xs
             where
             checkRow []     = [] 
             checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

再一次,这段代码只是为了说明一个问题,我正在寻找在功能风格中制定问题的方法。你的建议或文章,链接会很有帮助。

谢谢

7 个答案:

答案 0 :(得分:24)

尝试编写抽象的,可重用的函数 你将能够更容易地构建它们

isUnique :: Eq a => [a] -> Bool
isUnique [] = True
isUnique (x:xs) = all (/= x) xs && isUnique xs

noDups :: Eq a => [[a]] -> Bool
noDups = all isUnique

答案 1 :(得分:6)

您不需要第二个where子句。您可以在同一where子句下放置多个函数。同一where子句中的所有函数名称都在这些函数的主体中。想想顶级功能如何工作。所以你可以写:

noDups :: [[a]] -> Bool
noDups du = and (checkSu du)
   where checkDup []     = []
         checkDup (x:xs) = checkRow x ++ checkDup xs

         checkRow []     = [] 
         checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

事实上,这一点要清楚得多,因为在xcheckDup绑定x的版本中,where仍在 second 的范围内checkRow子句,但您将{{1}}的参数绑定到同一名称。我认为这可能会让GHC抱怨,这肯定令人困惑。

答案 2 :(得分:4)

暂且不谈你的特定例子的一些细节(这些名字并不是特别精心挑选的),我非常喜欢where子句:

  • where子句中定义的函数可能比顶级函数更好,因为读者知道函数的范围是有限的 - 它可以在几个地方使用

  • where子句中定义的函数可以捕获封闭函数的参数,这通常使其更容易阅读

在您的特定示例中,您不需要嵌套where子句---单个where子句将执行,因为在同一where子句中定义的函数都是相互递归。关于可以改进的代码还有其他一些事情,但是使用单个where子句我喜欢大规模的结构。

N.B。没有必要像你一样深深地缩进where条款。

答案 3 :(得分:3)

noDups :: [[a]] -> Bool
noDups = and . checkDup
  where
    --checkDup
    checkDup []     = []
    checkDup (x:xs) = checkRow x ++ checkDup xs
    --alternatively
    checkDup xs = concat $ map checkRow xs
    --alternatively
    checkDup = concat . map checkRow
    --alternatively
    checkDup = concatMap checkRow
    --checkRow
    checkRow []     = [] 
    checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

答案 4 :(得分:2)

虽然有例外,但一般情况下你可能想要定义“肯定”函数,即在这种情况下定义一个函数,如果参数 包含一些重复数据,则返回True。你可以这样写:

has_nested_duplicate :: (Eq a) => [[a]] -> Bool
has_nested_duplicate = any has_duplicate
  where
    has_duplicate []     = False
    has_duplicate (x:xs) = x `elem` xs || has_duplicate xs

这使用模式匹配,anyelem(||)。要获得否定,请使用not

noDups :: (Eq a) => [[a]] -> Bool
noDups = not . has_nested_duplicate

答案 5 :(得分:1)

Haskell允许您从where子句中引用where子句中定义的内容(与let绑定相同)。实际上,where子句只是一个let绑定的语法糖,允许多个定义和相互引用等。

一个例子是有序的。

noDups :: [[a]] -> Bool
noDups du = and (checkDup du)
    where
       checkDup []     = []
       checkDup (x:xs) = checkRow x ++ checkDup xs
           where
               checkRow []     = [] 
               checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

变为

noDups :: [[a]] -> Bool
noDups du = and (checkDup du)
    where
        checkDup []     = []
        checkDup (x:xs) = checkRow x ++ checkDup xs
        --checkDup can refer to checkRow
        checkRow []     = [] 
        checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

变为

noDups :: [[a]] -> Bool
noDups du = 
    let checkDup []     = []
        checkDup (x:xs) = checkRow x ++ checkDup xs
        --checkDup can refer to checkRow
        checkRow []     = [] 
        checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

    in  and (checkDup du)

答案 6 :(得分:1)

关于保持全局范围清洁,我感觉与Norman一样。您在模块中公开的函数越多,命名空间就越笨拙。另一方面,在模块的全局范围内具有函数使其可重用。

我认为你可以做出明确的区分。有些函数是模块的主要功能,它们直接对api做出贡献。 还有一些功能,当它们出现在模块文档中时,读者会想知道该特定功能与模块的用途有什么关系。这显然是一个辅助功能。

我想说这样的辅助函数应该是调用函数的下属。如果要在模块中重用此辅助函数,则通过使其成为模块的直接可访问函数,将此辅助函数与调用函数分离。您可能不会在模块定义中导出此功能 我们称之为FP风格的重构。

遗憾的是,没有一本用于函数式编程的“代码完整”的书。我认为原因是行业实践太少。但是,让我们收集stackoverflow上的智慧:D