将 m 元函数和 n 元函数组合在一个 (m+n) 函数中,返回它们的结果对

Combine m-ary function with n-ary function in a single (m+n)-ary function returning the pair of their results

提问人:Enlico 提问时间:4/4/2023 更新时间:4/5/2023 访问量:87

问:

我不知道这个应用程序会有多大用处,但我对它感到好奇,因为这个C++回答了我的一个问题

因此,给定,例如,三元和二进制,例如fg

f x y z = x + 10*y + 100*z
g x y = x + 10*y

我怎样才能得到一个函数,以便以下内容成立?h

h f g 1 2 3 4 5 == (321, 54)

显然我可以定义

h f g = \x y z v w -> (f x y z, g v w)

但是我很好奇,是否可以使用一些现有的抽象,以无点的方式为每个 arities mn 完成。

对于此特定示例 (),pointfree.io 显示结果变得不可读:m == 3 && n == 2

h = flip . ((flip . ((flip . (((.) . (.) . (,)) .)) .)) .)

但我仍然很好奇是否存在其他东西。

为了好玩,我尝试在机械上应用组合逻辑来推导一个公式,但仍然仅适用于以下特定情况:m == 3 && n == 2

b = (.)  -- bluebird (names from "To Mock a Mockingbird")
c = flip -- cardinal (names from "To Mock a Mockingbird")
p = (,)
f x y z = x + 10*y + 100*z
g x y = x + 10*y
{-
p(fxyz)(gvw) = ?xyzvw, with ? in terms of p, f, g and known birds
(p(fxyz))(gvw)
B(B(p(fxyz)))gvw
CBg(B(p(fxyz)))vw
B(CBg)B(p(fxyz))vw
B(B(CBg)B)p(fxyz)vw
B(B(B(CBg)B)p)(fxy)zvw
B(B(B(B(CBg)B)p))(fx)yzvw
B(B(B(B(B(CBg)B)p)))fxyzvw
B(B(B(B(B(CBB)(CB)g)p)))fxyzvw
B(B(B(BB(B(CBB)(CB))gp)))fxyzvw
B(B(BB(BB(B(CBB)(CB))g)p))fxyzvw
B(B(B(BB)(BB(B(CBB)(CB)))gp))fxyzvw
B(BB(B(BB)(BB(B(CBB)(CB)))g)p)fxyzvw
BB(BB(B(BB)(BB(B(CBB)(CB)))g))pfxyzvw
B(BB)(B(BB)(B(BB)(BB(B(CBB)(CB)))))gpfxyzvw
C(C(B(BB)(B(BB)(B(BB)(BB(B(CBB)(CB))))))p)fgxyzvw
-}
h = c (c (b (b b) (b (b b) (b (b b) (b b (b (c b b) (c b)))))) p)
h f g 1 2 3 4 5 == (321, 54) -- True
Haskell 函数式编程 Currying 组合逻辑

评论

6赞 willeM_ Van Onsem 4/4/2023
Haskell 中没有 m 元函数:每个函数接受一个参数。
0赞 Enlico 4/4/2023
是的,@WillemVanOnsem,但我想你明白我的意思。
2赞 willeM_ Van Onsem 4/4/2023
主要问题是,例如,根据类型类的实例,某些东西可以是函数,也可以不是函数。因此,例如通过语法,人们无法知道有多少个参数。一个函数只有一个参数的事实并不是一个“实现细节”,它是非常基本的。
2赞 willeM_ Van Onsem 4/4/2023
举个例子,在某种意义上,这是一个“可变”函数,因为通过类型类,结果可以是 、 、 或映射到其他项目的函数:hackage.haskell.org/package/base-4.18.0.0/docs/...printfStringIO String
0赞 chi 4/5/2023
通常的“分类”方法是使用 so 将一个“n-ary”函数转换为一个接受大元组的函数,然后使用类似的东西和 some(和投影来丢弃不需要的输入)。这或多或少是如何在笛卡尔闭合范畴中定义简单类型 lambda 演算的语义。uncurryh1 &&& h2curry

答:

3赞 DDub 4/4/2023 #1

正如 Willem Van Onsem 在评论中指出的那样,您的问题的问题在于,从技术上讲,Haskell 中的每个函数都只接受一个参数。我知道你的意思,我知道你想做什么,但没有理由不能只是一个返回函数的单参数函数,而不是一个双参数函数。事实上,如果我们为类似的东西定义一个实例,那么它完全有可能是一个 3 参数函数!(+)NumInt -> Int(+)

另一方面,仅仅因为无法推断出参数的数量,并不意味着你注定要失败。如果你愿意给 GHC 一些提示,你可以在类型级别做你想做的事。请考虑以下几点:

data Nat = Zero | Succ Nat
type One = Succ Zero
type Two = Succ One
type Three = Succ Two
type Four = Succ Three

class NFun m n f g where
  type family FunT m n f g
  h :: f -> g -> FunT m n f g

instance NFun m n x y => NFun (Succ m) n (a -> x) y where
  type FunT (Succ m) n (a -> x) y = a -> FunT m n x y
  h f g a = h @m @n (f a) g

instance NFun Zero n x y => NFun Zero (Succ n) x (a -> y) where
  type FunT Zero (Succ n) x (a -> y) = a -> FunT Zero n x y
  h f g a = h @Zero @n f (g a)

instance NFun Zero Zero x y where
  type FunT Zero Zero x y = (x,y)
  h = (,)

(使用 GHC 的 TypeLits 可能可以做得更漂亮,但我总是在让归纳法计算出来时遇到一些麻烦,所以我自己滚动了自然数。

在这里,我定义了一个类型类,它采用两个数值类型,指示函数将采用多少个参数。第一个实例为第一个函数提供参数,第二个实例为第二个函数提供参数。最后一个实例将结果配对。NFun

你可以像这样使用它:

f x y z = x + 10*y + 100*z
g x y = x + 10*y

h @Three @Two f g 1 2 3 4 5 == (321, 54)

评论

0赞 Enlico 4/4/2023
您能说明一下,如果我们为 Int -> Int 之类的东西定义一个 Num 实例,那么 (+) 完全有可能是一个 3 参数函数! 手段?
3赞 willeM_ Van Onsem 4/4/2023
@Enlico:具有类型。如果我以某种方式进行定义,那么有一个函数或 ,一个函数将两个函数作为参数,并返回一个函数,从而获取第三个参数。(+)Num a => a -> a -> ainstance Num ((->) b b)(+) :: (b -> b) -> (b -> b) -> (b -> b)(+) :: (b -> b) -> (b -> b) -> b -> b
0赞 DDub 4/6/2023
对于特定情况,我可以定义 .然后,是一个函数,它使用一个值并产生 。换句话说,有一个实例是三个参数的函数。instance Num (a -> Integer) where (+) = liftA2 (+); fromInteger = const3 + 47(+)
2赞 willeM_ Van Onsem 4/5/2023 #2

除非函数是具体类型,否则函数的 arity(如果我们省略严格来说每个函数的 arity 为 1)可能是可变的。事实上,一个很好的例子是 printf :: PrintfType r => String -> r

这似乎是一个简单的功能。但是有一个问题:这里可以是 的任何实例,我们有作为实例:rPrintfType

instance PrintfType a ~ () => PrintfType (IO a) where
    -- ...

instance IsChar c => PrintfType [c] where
    -- ...

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    -- ...

前两个并不那么有趣。最后一个是。实际上,这意味着它可以返回另一个函数。

这意味着可以用作:printf

printf "foo"             :: String
printf "foo %i" 14       :: String
printf "foo %i %i" 14 25 :: String

他们实现了某种可变参数函数,其中参数的数量取决于您如何使用 .printf

虽然我并不完全满意,因为与许多其他 Haskell 函数相比,它不是很“安全”,但它演示了如何制作可调参数函数。例如,可以传递太多或太少的参数,并且不能保证检索整数或类似数字的值,因此有更好的方法来格式化字符串。printf%i

但无论如何,参数的数量并不取决于函数本身,因此您不能从函数的角度推导出来,而是从它的使用方式中得出。因此,除非类型在函数中都是具体的,否则如果使用类型类,则不再容易确定函数的 arity,从而将两个函数“合并”在一起。

评论

0赞 Enlico 4/20/2023
与许多其他Haskell函数相比,它不是很“安全”是什么意思?它有什么不安全的地方?如果回答需要很长时间,我可以再问一个临时问题。除非您有指向现有链接的链接。
0赞 willeM_ Van Onsem 4/20/2023
@Enlico:不是说字符串中的参数数与参数数匹配,也不是说类型匹配,比如有额外的 ,或者 ,或者你可以传递一个格式化的字符串。printf "foo %s %s bar" "qux"%sprintf "foo" 42%i