20
1
Having a function f that takes arguments x1, x2, …, xn
– ie. f : X1 × X2 × … × Xn → Y
– currying redefines f as a function taking a single argument a1 which maps to yet another function. This technique is useful for partial application, for example with a curried pow
function we could write exp = pow(e)
.
Example
Assuming we have the following function f taking three arguments (f : X1 × X2 × X3 → Y):
def f(a,b,c):
return a + b * c
Currying this function leaves us with f_curry: X1 → (X2 → (X3 → Y)), if we would now call that function twice with f_curry(1)(2)
we would get a function (h
) equivalent to the following returned:
def h(c):
return 1 + 2 * c
The curried function f
could be written like this (Python 3):
def f_curry(a):
def g_curry(b):
def h(c):
return a + b * c
return h
return g_curry
Challenge
Your challenge will be to curry a function as described above, here are the rules:
- Input will be a blackbox function which takes at least 2 arguments
- The input function will always have a fixed number of arguments (unlike
printf
or similar, note: you need to support functions with any number of arguments ≥2) - If your language uses curried functions by default (eg. Haskell), you may expect the input function to be defined over N-tuples, instead of a "higher-order function"
- You may take the number of arguments as input
- Output will be the input's curried equivalent*
- You may assume that the output function will only ever be:
- called with less or equal to the number of arguments that the input function takes
- called with arguments of the right type
* This would mean for an input f
with N
arguments and an output h
that for all valid arguments a1,…,aN
it holds that f(a1,a2,…,aN) == h(a1)(a2)…(aN)
.
Related. – ბიმო – 2018-04-14T10:26:43.063
so the input is
def f(a,b,c): return a + b * c
and the output isdef f_curry(a): def g_curry(b): def h(c): return a + b * c return h return g_curry
? – DanielIndie – 2018-04-14T10:35:40.700@DanielIndie: If you're taking that example the input would be
f
(which is defined somewhere) and the output should be something equivalent tof_curry
. Or the input would belambda a,b,c: a+b*c
and the output a function equivalent tof_curry
. – ბიმო – 2018-04-14T10:39:25.230This is hard to do in most statically typed languages ... I guess you need type functions for this. – Paŭlo Ebermann – 2018-04-14T19:47:32.060
@PaŭloEbermann: True, some languages won't be able to solve this task (note the tag [tag:functional-programming]). However some statically typed languages might be able to use function pointers which would be a valid I/O, that's mainly the reason I allowed taking the number of arguments as additional input. – ბიმო – 2018-04-14T21:22:04.677
That was no complaint, I just tried to do it in Ceylon, and noted that it is impossible to even write the type of this function. (A "just first argument"
– Paŭlo Ebermann – 2018-04-15T02:03:58.363curry
function is already built in.)Aside re. “instead of a higher order function”, a curried function type like
– Jon Purdy – 2018-04-15T06:15:22.613A -> (B -> C)
isn’t higher-order (meaning order > 1): its order is 1. A type like(A -> B) -> C
is order-2, since it has an order-1 function as a parameter—basically order refers to the nesting depth to the left of function arrows. See: What is a higher-kinded type?@PaŭloEbermann: Ah, I was thinking you meant languages like C or C++.. I don't know Ceylon but maybe you can define something like a type class, if you haven't seen it you should definitely check out Lynn's Idris answer.
– ბიმო – 2018-04-15T11:15:47.240@JonPurdy: I think you linked the wrong SO question, that one talks about kinded types which is different. However I see your point, the wikipedia article about higher-order functions states that my usage is disputed, so I put it in quotes. I think it's clear what it means, however feel free to edit in a better explanation.
– ბიმო – 2018-04-15T11:18:27.037@BMO: I linked that answer of mine because it gives examples of function orders (0:
A
, 1:A -> B
, 2:(A -> B) -> C
, 3:((A -> B) -> C) -> D
, …) and what “higher-” specifically means. It’s presented as a way to explain kinds (0:*
, 1:* -> *
, 2:(* -> *) -> *
, …), but these are closely related to orders, as are ranks for that matter (1:forall a. a -> a
, 2:(forall a. A a -> B a) -> C
, 3:((forall a. A a -> B a) -> C) -> D
, …). Just wanted to offer clarification. The use of “higher-” is “folk knowledge” in the PLT/CS world, so it’s not surprising that Wikipedia is unclear. – Jon Purdy – 2018-04-15T12:59:29.053@JonPurdy: I see, that makes sense. It's basically (as is often the case) the same concept as in higher-order logic, right? But in my defense, I didn't try to be too formal about all this ;P I had bad luck with keeping things too formal and making challenges/answers not accessible to everyone. I think in the end I wouldn't even be able to post a challenge like this, as I don't think there's a (good) objective way of testing the validity of submissions (ie. proving/testing the equivalence of two functions). – ბიმო – 2018-04-15T13:37:55.900
For Haskell-like languages, must it specifically be defined in terms of a tuple, or is a fixed-length list acceptable? Specifically in this case, the size of a tuple is part of the type and it isn't possible to deconstruct them without pattern matching which can't dynamically extend to an arbitrary number of members. – Οurous – 2018-05-27T21:34:15.413
@Οurous: Sure that should be fine! The main aspects should be, that it is of fixed size and - unless a language only has one type - that the chosen representation allows for heterogeneous types. – ბიმო – 2018-05-27T22:20:20.020
The challenge would be seemingly impossible in Haskell, if you give function over n-tuples, as certain tuples are simply not defined in Haskell. Also, the polymorphism required to make it work for all sizes requires either Template Haskell or a crazy amount of type families etc. – schuelermine – 2018-11-10T16:59:55.777
For an answer in assembly, I need some way to know the type (or actually just the size) of the arguments. May I assume all arguments are of some constant size or may I get a list of argument sizes as extra argument? – wastl – 2019-09-21T01:02:31.997