Search code examples
haskellfunctional-programmingcurryingcombinatory-logic

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


I have no idea how useful the application would be, but I got curious about it because of this C++ answer to a question of mine.

So, given, say, a ternary f and a binary g, e.g.

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

how can I get a function h such that the following holds?

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

Clearly I can define

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

But I was curious to know if it could be done in point-free style for every arities m and n, using some existing abstraction.

For this specific example (m == 3 && n == 2) pointfree.io shows that the result becomes unreadable:

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

but I'm still curious if something else exists.

For fun, I've tried to apply combinatory logic mecanically to derive a formula, but still only for the specific case of 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

Solution

  • Unless the function is a concrete type, the arity of the function (if we leave out the fact that strictly speaking each function has an arity of one), can be variable. Indeed, a good example is printf :: PrintfType r => String -> r.

    This seems a simple function. But there is a problem: r here can be any instance of PrintfType, and we have as instances:

    instance PrintfType a ~ () => PrintfType (IO a) where
        -- ...
    
    instance IsChar c => PrintfType [c] where
        -- ...
    
    instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
        -- ...
    

    The first two are not that interesting. The last one is. Indeed, it means that it can return another function.

    Which means that one can use printf as:

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

    They this implemented some sort of variadic function where the number of parameters depends on how you use printf.

    While I'm not entirely happy with printf since it is not very "safe" compared to a lot of other Haskell functions, it demonstrates how one can make variadic functions. For example one can pass too many or too few parameters, and %i is not guaranteed ot retrieve an integer or number-like value, so there are better ways to format strings.

    But regardless, the number of parameters does not depend on the function itself, so you can not derive this from the function's point of view, but from how it is used. Therefore, unless the types are all concrete in a function, if typeclasses are used, it is no longer easy to determine the arity of the function and therefore thus "merge" two functions together.