在不考虑实现细节的情况下定义与其他函数相关的函数

时间:2013-07-15 05:23:18

标签: haskell

我编写了一个名为'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 个答案:

答案 0 :(得分:8)

我担心not oddOf不是一个合理的部分应用函数。这是notoddOf的应用。

not的类型为Bool -> Bool;它需要一个Bool参数并返回BooloddOf不是Bool,因此not无法应用于not。但您想要的不是将oddOf应用于not本身,而是将oddOf应用于将evenOf value list = not $ oddOf value list 应用于两个参数的结果

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

evenOf

这在理论上不那么直接和抽象,因为它不是直接通过它与oddOf的关系来定义evenOf,而是“手动”将输入连接到{{ 1}}到oddOf的输入,然后对其结果进行后处理。一个无点版本,如Ziyao Wei的建议:

evenOf = ((not .) .) oddOf

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

答案 1 :(得分: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的一种更简洁的方式。

这是通过编写功能来实现的;我们采用列表并对其进行过滤,以便我们得到value等于(==)的那些,使用“{1}}的”(== value) :: Eq a => a -> Bool部分应用“length。接下来我们找到even,检查它是not,然后最后用evenOf否定答案。这暗示了写作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函数只能使用一个参数! (我们认为带有多个参数的函数实际上是接受一个参数然后返回另一个函数的函数,等等......)不幸的是,这意味着我们无法将类型与{{1直接,因为我们需要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
    
  • 简单解决方案3 正在撰写evenOf value撰写notoddOf value

    not.oddOf的唯一问题是oddOf没有直接返回Bool,而是返回[a]->Bool。我们可以通过提供其中一个参数来解决这个问题。请注意,oddOf value的类型为Eq a => [a] -> Bool,因此我们可以使用not进行组合,因为 会返回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的优秀semantic editor combinators webpage/article

特别是,您应该阅读它,因为它以非常一般的方式回答您的问题标题“定义与其他函数无关的函数而不考虑实现细节”; “语义编辑器组合器”是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

使用相同的想法,你可以改变列表内部,一对的一部分,其中一个参数,...
阅读本文以获得更深入,更全面的解释。

答案 2 :(得分:1)

您的示例代码实际上是以not为参数调用oddOf函数。 (.)函数用于组成2个函数,但只有当你有二进制函数时,即只接受一个参数并返回一个值的函数,oddOf的情况不属于a -> [a] -> Bool,因为它是{{1 }}。

我们可以将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)

答案 3 :(得分:0)

除了其他选项,并且为了帮助您了解正在发生的事情,请认识到如果您为oddOf参数使用元组,工作。

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

这是因为oddOf现在是一个返回Bool的函数。使用非upupled参数,然后它是一个返回一个函数的函数,该函数返回一个不同的Bool。

所以现在not oddOf仍然不起作用,因为我们在这里没有部分应用 - notBool -> Bool; 那里没有部分应用程序。相反,我们将回到旧的数学函数组成 -f(g(x)) - > (F,G)(X)。此处goddOffnot。然后Haskell表示如上所述。

你的问题的基本前提都归结为我的第二段 - 你有一个返回函数的函数。这里的所有答案都提供了一些方法,将其转换为返回 Bool 的函数,然后使用not进行函数组合。尝试阅读所有答案,这应该会让你更好地理解。特别要考虑到AndrewC的简单解决方案#3。