具有两个参数的 Haskell 组合

Posted

技术标签:

【中文标题】具有两个参数的 Haskell 组合【英文标题】:Haskell composition with two parameters 【发布时间】:2016-01-12 07:35:53 【问题描述】:

我正在尝试通过 Haskell 理解函数式编程,但在处理函数组合时遇到了很多麻烦。

其实我有这两个功能:

add:: Integer -> Integer -> Integer
add x y =  x + y

sub:: Integer -> Integer -> Integer
sub x y =  x - y

我希望能够创作它们。这没有任何意义好吧,但这是为了学习目的。

我的尝试:

foo:: (Integer -> Integer) -> (Integer -> Integer) -> Integer
foo = add . sub

我的理解:

Haskell 使用只有一个 args 的函数,因此我们在每个函数执行后返回一个新函数来执行。

所以第一个Integer 是参数类型,而第二个是必须添加第二个数字的生成函数的返回类型。

这将返回另一个函数 (sub),该函数将产生相同的流程(返回带有参数等的函数...)

我说的对吗?

这是我的实际错误代码:

src\Main.hs:23:7:
    Couldn't match type `Integer' with `Integer -> Integer'
    Expected type: Integer -> (Integer -> Integer) -> Integer
      Actual type: Integer -> Integer -> Integer
    In the first argument of `(.)', namely `add'
    In the expression: add . sub

src\Main.hs:23:13:
    Couldn't match type `Integer -> Integer' with `Integer'
    Expected type: (Integer -> Integer) -> Integer
      Actual type: Integer -> Integer -> Integer
    Probable cause: `sub' is applied to too few arguments
    In the second argument of `(.)', namely `sub'
    In the expression: add . sub

我不知道我做错了什么。

您能帮我多了解一下这个错误,以便我找到解决方案吗?

【问题讨论】:

你是对的,它“没有任何意义”——在数学中没有组合二进制函数的概念。如果你想让它有意义,你首先需要定义什么是“组合”加减法。 你的 foo 类型表示加法和减法的合成是一个接受两个 函数 (Integer -> Integer) 并返回一个整数的函数。 【参考方案1】:

给定一个函数

add :: Integer -> Integer -> Integer

记住(正如您在我的理解部分中指出的那样)-> 在类型签名中与右侧关联是很有用的,即上述类型与

add :: Integer -> (Integer -> Integer)

现在,考虑(.)的类型:

(.) :: (b -> c) -> (a -> b) -> a -> c

这意味着在一个表达式中

(.) add

(.) 类型中的bIntegerc 对应于Integer -> Integer。另一种写法是

b ~ Integer
c ~ Integer -> Integer

所以我们得到

(.) add :: (a -> Integer) -> a -> (Integer -> Integer)

如果您现在将(.) add 应用到sub,编译器会注意到a -> Integer 不能匹配Integer -> Integer -> Integer

我怀疑您可能希望组合采用 三个 参数:两个应用 sub,然后结果 - 与第三个参数一起 - 传递给 add .因此,组成这两个函数的可能定义是

foo :: (Integer -> Integer -> Integer) -> (Integer -> Integer -> Integer) -> Integer -> Integer -> Integer
foo f g x y = f (g x y) y

对于它的价值,有一个相关的问题:用一个参数函数组合一个两个参数函数,例如作曲

【讨论】:

定义稍有不同,foo f g x y = f (g x y) y 可以简写为foo = liftM2 (.)【参考方案2】:

我希望能够创作它们。这没有任何意义好吧,但这是为了学习目的。

这实际上是这里的问题。你想如何组合它们?让我们看看一些可能的组合:

foo x y = sub x (add x y)          -- x + y - x = y
foo x y = sub y (add x y)          -- x + y - y = x
foo x y = sub x (add y y)          -- 2 * y - x 
foo x y = sub y (add y y)          -- 2 * y - y = y
foo x y = sub y (sub y (add x x))  -- 2 * x - 2 * y

话虽如此,让我们通过手动检查类型来检查类型错误:

type I = Integer -- otherwise the lines are going to be very long

(.)         :: (b ->    c   ) -> (a ->   b   ) -> a -> c
add         ::  I -> (I -> I)
sub         ::                   I -> (I -> I)
--                               |||||||||||||
(.) add     ::                   (a  -> I    ) -> a -> (I -> I)
--                               ^^^^^^^^^^^^^

如您所见,(.) add 已经要求其他函数只能具有类型 a -> Integer 来表示任意 a。但是sub 的类型是Integer -> (Integer -> Integer)(请记住,(->) 是右结合的)。

现在,你能做些什么来真正解决这个问题?首先,让我们检查您建议的foo 类型:

foo :: (Integer -> Integer) -> (Integer -> Integer) -> Integer

这实际上是一个非常有趣的函数类型。你将如何真正得到你的结果?你手头只有两个函数,但没有值:

> foo f g = 

您可以通过使用其中一个函数的固定点然后应用另一个函数来解决此问题:

>   let x = f x in g x
>
> example = foo (const 12) (+1) -- returns 13

但这不是你的意思,对吧?此时,考虑组合的语义非常重要。由于这些尚不清楚,因此您无法在此处编写组合这两个函数的通用方法。

但是,如果您实际上是说

foo :: Integer -> Integer -> Integer -> Integer
foo x y z = add (sub x y) z

那么这是可能的

foo = (add .) . sub

因为

(.) add        :: (a -> I) -> a -> (I -> I)
(.) ((.) add)  :: (a -> b -> Integer) -> a -> b -> Integer -> Integer

但是(add .) . sub 已经不再那么容易吸引眼球了。如果这种函数是您最初的目标,那么您最好编写一个逐点定义 foo

【讨论】:

非常感谢您在答案末尾解释(add .) . sub!当我找到这个答案时,这就是我正在寻找的东西,而不是我认为在实际代码中使用它是一个好主意,就像我试图弄清楚是否有可能制作的脑筋急转弯一样一个二进制函数中的三元函数,没有指定参数或使用 lambda,但事实证明它确实如此,我真的很欣赏它的美丽,即使我(至少目前)不能真正看到它会被证明是有用的情况.

以上是关于具有两个参数的 Haskell 组合的主要内容,如果未能解决你的问题,请参考以下文章

具有haskell中的多参数函数的延迟过滤器

Haskell 示例中的函数组合

确保两个 (G) ADT 在 (GHC) Haskell 中具有相同的底层表示

如何在haskell中对两个参数进行模式匹配

Haskell 和函数组合

[Haskell电影搜索