5

我编写了一个名为“oddOf”的函数,它可以正确确定给定值在列表中是否存在奇数个。它是这样定义的:

oddOf :: (Eq a) => a -> [a] -> Bool
oddOf value list = oddOf' value list False
    where oddOf' val (x:xs) acc = if x == val
                                  then oddOf' val xs (not acc)
                                  else oddOf' val xs acc
          oddOf'  _     _   acc = acc

我想编写一个函数来确定给定值是否在列表中具有偶数个存在。当出现诸如此类的二元选择时,最佳实践是实现一个并将另一个定义为“不是它的补充”。考虑到这一点,我尝试了定义:

evenOf = not oddOf

对我来说,这看起来像是一个合理的部分应用函数,但它不是有效的 Haskell 代码。我需要更好地理解的语言是什么?定义我正在寻找的 evenOf 的优雅方式是什么?

4

4 回答 4

8

以这种方式考虑代码重用是一种很好的做法,我会谈到这一点,但首先:

使用函数组合写得更整洁

我可以先指出,我会oddOf更简单地使用现有函数来定义您的函数:

oddOf :: Eq a -> a -> [a] -> Bool
oddOf value list = odd . length . filter (== value) $ list

f $ x = f x但优先级较低,所以这是一种更简洁的写作方式(not . even . length . filter (== value) ) list

这是通过组合函数来工作的;我们获取列表并对其进行过滤,以便我们仅使用'operator section' 形式的value部分应用来获得等于 的列表。接下来我们找到那个,检查它是否是,然后最后用 否定答案。这暗示了一种简洁的写作方式:(==)(== value) :: Eq a => a -> BoollengthevennotevenOf

evenOf :: Eq a -> a -> [a] -> Bool
evenOf value list = even . length . filter (== value) $ list

事实上,由于前奏定义odd为,因此从not . even它开始evenOf和定义会稍微简单oddOf一些。

为什么not . oddOf不是你想要的

查看类型,您有

not :: Bool -> Bool
oddOf :: Eq a => a -> [a] -> Bool
(.) :: (b -> bb) -> (a -> b) -> a -> bb  -- function composition

现在->关联到右边,这意味着真的,

oddOf :: Eq a => a -> ([a] -> Bool)

秘密地,这意味着 Haskell 函数只接受一个参数!(我们所认为的接受多个参数的函数实际上是接受一个参数然后返回一个将接受另一个参数的函数的函数,等等......)不幸的是,这意味着我们不能(.)直接匹配类型,因为我们需要bBool不是[a] -> Bool

解决您的问题的简单方法

好的,抱歉我花了一段时间才得到你想要的答案,但我认为这一切都值得一说。

  • 简单的解决方案 1是使用函数组合编写evenOf,正如我在上面向您展示的那样。

    oddOf  value list =  odd . length . filter (== value) $ list
    evenOf value list = even . length . filter (== value) $ list
    
  • 简单的解决方案 2通过提供所有参数evenOf直接写入:oddOf

    evenOf value list = not $ oddOf value list
    oddOf  value list = odd . length . filter (== value) $ list
    
  • 简单的解决方案 3evenOf value通过notoddOf value

    唯一的问题not.oddOf是它oddOfBool直接返回 a ,而是返回 a [a]->Bool。我们可以通过提供其中一个参数来解决这个问题。注意oddOf valuehas 类型Eq a => [a] -> Bool,所以我们可以用 组合它not,因为它确实返回 a Bool

    evenOf value      = not . oddOf value
    oddOf  value list = odd . length . filter (== value) $ list
    
  • 简单的解决方案 4将简单的解决方案 1 和 2 放在一起来编写,而不是oddOf使用evenOf

    oddOf  value list = not $ evenOf value list
    evenOf value list = even . length . filter (== value) $ list
    

    (当然,您可以对简单的解决方案 1 和 3 做同样的事情。)

令人敬畏的改变大脑的方法来解决问题

每个 Haskell 程序员都应该阅读 Conal Elliott 出色的语义编辑器组合器网页/文章

特别是,您应该阅读它,因为它以非常笼统的方式回答了您的问题标题“在不考虑实现细节的情况下定义与其他功能相关的功能”;“语义编辑器组合器”正是 Conal 对这个概念的表述。

主要思想是,您可以在编写函数后通过在括号中写入要更改的值的路径,要在该点应用的函数,然后是原始函数来编辑函数。在这种情况下,您需要将原始函数( )的结果( )应用于not结果()。::Bool::Eq a => [a]->Bool:: Eq a => a -> [a] -> Bool

因此,如果您想使用 编辑结果的结果not,请执行以下操作:

oddOf = (result.result) not evenOf
evenOf value list = even . length . filter (== value) $ list

Conal Elliott 定义result = (.)是因为它在概念上更容易,但您可以定义

oddOf = ((.).(.)) not evenOf
evenOf value list = even . length . filter (== value) $ list

如果您愿意,可以直接使用。

如果你需要改变more :: a -> b -> [a] -> [b] -> Bool,你可以使用

less = (result.result.result.result) not more

或者

less = ((.).(.).(.).(.)) not more

使用相同的想法,您可以在列表中更改,一对的一部分,一个论点,...
阅读论文以获得更深入和更全面的解释。

于 2013-07-15T10:07:33.587 回答
8

恐怕这not oddOf不是一个合理的部分应用功能。它是 to 的一个应用not程序oddOf

not有类型Bool -> Bool;它接受一个Bool参数并返回一个Bool. oddOf不是 a Bool,所以not不能应用于它。但是您想要的不是应用于not自身oddOf,而是应用于应用于两个参数not结果。oddOf

做你想做的最直接的方法是写这样的东西:

evenOf value list = not $ oddOf value list

从理论上讲,这比它可能的直接和抽象要少一些,因为不仅仅是evenOf通过它与oddOf直接的关系来定义,而是“手动”将输入连接evenOf到输入,oddOf然后对其结果进行后处理。一个免积分的版本,比如魏子尧的建议:

evenOf = ((not .) .) oddOf

更直接地说明了这种关系,使读者不必验证 2 个参数evenOf是否以相同的顺序简单地传递,oddOf并且没有用于任何其他目的。但通常需要非常熟悉 Haskell 才能找到更清晰的版本(否则您必须改为验证嵌套组合部分的确切作用才能看到“直接陈述”的关系)。因此,如果您更愿意明确命名和“连接”参数,那可能就是您正在寻找的优雅方式。

于 2013-07-15T07:22:15.893 回答
1

您的示例代码实际上是使用as 参数调用该not函数。oddOf(.)函数用于组合 2 个函数,但仅当您具有二进制函数时,即采用单个参数并返回值的函数,当然不是这种情况,oddOf因为它是a -> [a] -> Bool.

如果可以的话,我们可以让oddOf 成为一个二元函数(a, [a]) -> Bool。这可以使用uncurry. 调用的另一个函数curry则相反,即它将(a,[a]) -> Bool返回到a -> [a] -> Bool.

我们可以使用这些curry uncurry函数来实现我们想要的:

evenOf :: (Eq a) => a -> [a] -> Bool
evenOf = curry $ not . (uncurry oddOf)
于 2013-07-15T09:41:10.503 回答
0

除了其他选项之外,为了帮助您了解正在发生的事情,请认识到如果您将元组用于您的 oddOf 参数,它将起作用

let oddOf (value, list) = ...
let evenOf = not . oddOf

这是因为oddOf 现在是一个返回 Bool 的函数。使用 untupled 参数,那么它是一个返回 Bool 的函数的函数,这是不同的。

所以现在not oddOf仍然行不通,因为我们在这里没有部分应用——notBool -> Bool;那里不可能有部分应用。相反,我们将回到旧的数学函数组合——f(g(x)) -> (fg)(x)。这goddOffnot。然后 Haskell 表示如上。

你的问题的基本前提都归结为我的第二段——你有一个返回函数的函数。这里的所有答案都提供了某种将其转换为返回Bool的函数的方法,然后使用not. 尝试从这个角度阅读所有答案,它应该可以让您更好地理解。考虑到这一点,请特别注意 AndrewC 的简单解决方案 #3。

于 2013-07-15T06:04:27.090 回答