Haskell 有两个参数的组合

Haskell composition with two parameters

我正在尝试通过 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 使用只有一个参数的函数,因此我们 return 在每次函数执行后执行一个新函数。

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

这将 return 另一个函数 (sub) 将产生相同的流程(return 使用参数等函数...)

我说得对吗?

这是我的实际错误代码:

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

我不知道我做错了什么。

你能帮助我更多地理解这个错误,以便我找到解决方案吗?

给定一个函数

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

就其价值而言,还有一个相关问题:将双参数函数与单参数函数组合在一起,例如作曲

I want to be able to compose them. It doesn't make any sense okay, but it's for a learning aim.

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

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 的 point-wise 定义。