我编写了一个名为'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我正在寻找的优雅方式是什么?
答案 0 :(得分:8)
我担心not oddOf
不是一个合理的部分应用函数。这是not
对oddOf
的应用。
not
的类型为Bool -> Bool
;它需要一个Bool
参数并返回Bool
。 oddOf
不是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直接,因为我们需要b
为Bool
,而不是[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
撰写not
与oddOf 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
仍然不起作用,因为我们在这里没有部分应用 - not
是Bool -> Bool
; 那里没有部分应用程序。相反,我们将回到旧的数学函数组成 -f(g(x)) - > (F,G)(X)。此处g
为oddOf
,f
为not
。然后Haskell表示如上所述。
你的问题的基本前提都归结为我的第二段 - 你有一个返回函数的函数。这里的所有答案都提供了一些方法,将其转换为返回 Bool 的函数,然后使用not
进行函数组合。尝试阅读所有答案,这应该会让你更好地理解。特别要考虑到AndrewC的简单解决方案#3。