Make me some curry

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

Try it online!

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).

ბიმო

Posted 2018-04-14T10:26:32.913

Reputation: 15 345

Related. – ბიმო – 2018-04-14T10:26:43.063

so the input is def f(a,b,c): return a + b * c and the output is def 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 to f_curry. Or the input would be lambda a,b,c: a+b*c and the output a function equivalent to f_curry. – ბიმო – 2018-04-14T10:39:25.230

This 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" curry function is already built in.)

– Paŭlo Ebermann – 2018-04-15T02:03:58.363

Aside re. “instead of a higher order function”, a curried function type like A -> (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?

– Jon Purdy – 2018-04-15T06:15:22.613

@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

Answers

11

JavaScript (ES6), 35 bytes

f=g=>g.length<2?g:a=>f(g.bind(f,a))

Neil

Posted 2018-04-14T10:26:32.913

Reputation: 95 035

9

Idris, 204 bytes

import Data.HVect
C:(a:Vect n Type)->(HVect a->Type)->Type
C[]T=T[]
C(h::t)T=(x:h)->C t(T .(x::))
c:{a:Vect n Type}->{T:HVect a->Type}->((b:HVect a)->T b)->C a T
c{a=[]}f=f[]
c{a=h::t}f=\v=>c(\u=>f(v::u))

Try it online!

Sounds like a job for dependent types! Well, maybe.


C is a currying type function. Given a vector of types a = [t1, t2, … tn] and a type function T : HVect a → Type, it returns a new type:

           (x1 : t1) → (x2 : t2) → … → (T [x1, x2, … xn])

Here, HVect is the heterogeneous vector type from the Idris Prelude — the type of n-tuples whose elements are of n different types.

c is a function that takes a and T as implicit arguments, and then converts an uncurried function f of type ((b : HVect a) → T b) into a curried one of type C a T.

(C simply describes what we wish to do; c actually does it. But we can't get away with not defining C, as Idris demands that every top-level definition have a type signature.)


The TIO link gives a usage example. If we define a function on 3-tuples (Nat, Nat, String) as follows:

uncurried : HVect [Nat, Nat, String] -> String
uncurried [a, b, c] = show (a*a + b*b) ++ c

then uncurried [3, 4, "th"] yields the same result as c uncurried 3 4 "th". Idris infers the arguments a=[Nat, Nat, String] and T=const String for us, I believe.

I based this code on this gist by timjb.

Lynn

Posted 2018-04-14T10:26:32.913

Reputation: 55 648

In my opinion, tuples in Haskell and Idris should actually be HVect by default—HVect is essentially a tuple that you can uncons. – Esolanging Fruit – 2018-04-18T03:53:47.300

5

Python 3, 54 53 bytes

c=lambda n,f,*x:lambda y:(f,c)[n>1](*1%n*(n-1,f)+x,y)

Try it online!

Dennis

Posted 2018-04-14T10:26:32.913

Reputation: 196 637

5

R, 96 bytes

y=function(f,n=length(formals(f)),p=list())function(x,u=c(p,x))`if`(n<2,do.call(f,u),y(f,n-1,u))

Try it online!


Previous version (97 bytes)

-1 byte thanks to @JayCE

digEmAll

Posted 2018-04-14T10:26:32.913

Reputation: 4 599

I don't see how to fundamentally shorten it. You can golf away three bytes by getting rid of the braces and the space at the end of the first line. And two more due to the convention here of not including the name of the function in the byte count. TIO

– ngm – 2018-05-27T14:56:56.567

@ngm The function name must be included when it's recursive. – Ørjan Johansen – 2018-05-27T18:26:06.637

@ngm: I put the if statement inside the sub-function saving a tenth of bytes :) – digEmAll – 2018-05-28T16:11:02.827

4

Coconut, 54 bytes

def c(f,*a):
 try:return f(*a)
 except:return c$(f,*a)

Try it online!


Coconut, 40 bytes

Port of Erik's Python answer.

def c(f,n,*a)=n and c$(f,n-1,*a)or f(*a)

Try it online!

ovs

Posted 2018-04-14T10:26:32.913

Reputation: 21 408

3

Python 2, 60 bytes

c=lambda f,n,l=[]:lambda a:n-1and c(f,n-1,l+[a])or f(*l+[a])

Try it online!

The footer is a tester which uses STDIN in the following way per line:

  1. The function itself
  2. The number of the function's arguments, ≥2
  3. A list of the arguments ([a,b,...])

Note that, while a list of the arguments is given as input in the tester, in reality, the curried equivalent gets prepended to the list and the list is reduced by function call.

A similar 55-byte version has been kindly provided by ovs:

c=lambda f,n,*l:lambda a:n-1and c(f,n-1,*l,a)or f(*l,a)

Try it online!

Erik the Outgolfer

Posted 2018-04-14T10:26:32.913

Reputation: 38 134

2

Perl 6, 42 40 bytes

my&c={.count>1??{c(.assuming($^a))}!!$_}

Try it online!

-2 bytes thanks to Brad Gilbert b2gills.

nwellnhof

Posted 2018-04-14T10:26:32.913

Reputation: 10 037

You don't need to use a trailing *, it is only necessary if there is something after it like .assuming(*,1). – Brad Gilbert b2gills – 2018-04-14T15:12:23.953

2

Cauliflower, 84 bytes

(:= c(\($f$n(@a))(if$n(\($a)(call c(cat(list$f(-$n 1))@a(list$a))))else(call$f@a))))

Try it online!

ASCII-only

Posted 2018-04-14T10:26:32.913

Reputation: 4 687

1Mmm, Cauliflower curry. Delicious. ^_^ – DLosc – 2018-05-15T03:00:35.643

@DLosc there aren't enough answers to this challenge in langauges with food-related names :P (although I guess most of them don't actually have functions) – ASCII-only – 2018-05-15T05:41:02.387

1

Python 2, 78 bytes

c=lambda f,*a:f.func_code.co_argcount>len(a)and(lambda x:c(f,*a+(x,)))or f(*a)

Try it online!

ovs

Posted 2018-04-14T10:26:32.913

Reputation: 21 408

1

Attache, 5 bytes

Curry

Try it online!

Simple built in, largely uninteresting. But, here's a version from scratch:

Attache, 35 bytes

{If[#__2<#_,Fold[`&:,$'__],_@@__2]}

Explanation:

{If[#__2<#_,Fold[`&:,$'__],_@@__2]}
{                                 }    lambda
 If[       ,              ,      ]     if
    #__2                                 the number of parameters after the first
        <#_                              is less than the arity of the first
            Fold[   ,    ]             then fold:
                 `&:                     right-function bonding
                     $                     over this function
                      '__                  paired with the rest of the parameters
                          ,            otherwise:
                           _@@           call the first parameter
                              __2        with the rest of them

Conor O'Brien

Posted 2018-04-14T10:26:32.913

Reputation: 36 228

1

Java 8, 46 + 318 = 364 bytes

This is a curried (hah) lambda taking a function and an argument count and returning the curried function.

import java.lang.reflect.*;import java.util.*;

f->p->new java.util.function.Function(){Object F=f;Method m=F.getClass().getDeclaredMethods()[0];int P=p;List a=new Stack();public Object apply(Object r){a.add(r);try{return a.size()<P?this:m.invoke(F,a.toArray());}catch(Throwable t){t=t.getCause();if(t instanceof Error)throw(Error)t;else throw(RuntimeException)t;}}}

Try It Online

Submission type

Input function

The function input is an object with a single method (excluding inherited methods) representing the function. Note that a standard functional interface cannot be used as the input type because functions of (e.g.) 3 parameters must be supported. Also note that a lambda expression cast to a standard java.util.function.Function-like type may be passed (the single method is apply).

Checked exceptions may be declared on the input function, but they may not be thrown (i.e. they will not be propagated to the caller of the output function). This is presumed to be acceptable because Java's functional interfaces do not permit checked exceptions (and propagating them would prevent the submission from returning a Function). Runtime exceptions (assignable to RuntimeException or Error) are propagated.

Output function

The output of the submission is a java.util.function.Function<Object, Object>. I considered returning a plain Object with an apply method (like in the input), but then reflection would be required to invoke the result, which seemed inconvenient enough to be disallowable—in particular, calling all the way down would no longer be possible in a single expression.

Usage

Because the submission returns a function from Object to Object, the output may be invoked directly (with apply), but subsequent intermediate return values must be cast to an appropriate type (e.g. java.util.function.Function<Object, Object>) before being invoked. Consult the TIO for some usage examples.

Note that in Java functions (i.e. methods) are not first-class objects. Thus the syntax used in the output bullet of the challenge description is meaningless in Java. Rather than f(a1, a2, a3) we have f.apply(a1, a2, a3), and rather than f(a1)(a2)(a3) we have f.apply(a1).apply(a2).apply(a3).

Limitations

When an intermediate result is applied (an argument added), the result is actually a mutated copy of the original result. For example, in this snippet:

Function<Object, Function<Integer, Function>> submission = ...;
Function c = submission.apply((IntBinaryOperator) (a, b) -> a + b).apply(2);
Function c2 = (Function) c.apply(2);
System.out.println(c2.apply(2));
System.out.println(c2.apply(3));

line 4 would print 4, but line 5 would fail, because by that time c2 already holds arguments 2 and 2 (note too that c2 == c). This violates the spirit of currying, but meets the specific requirement stated in the challenge.

Ungolfed

See the TIO for an ungolfed copy.

Jakob

Posted 2018-04-14T10:26:32.913

Reputation: 2 428

1

Julia 0.6, 48 bytes

c=(f,n,a=[])->n>1?x->c(f,n-1,[a;x]):x->f(a...,x)

Try it online!

Port of @EricTheOutgolfer's Python answer.

sundar - Reinstate Monica

Posted 2018-04-14T10:26:32.913

Reputation: 5 296

0

APL (Dyalog Classic), 58 57 bytes

∇r←(a f g)x
:If a[1]=≢a
r←g 1↓a,x
:Else
r←(a,x)f g
:End
∇

Try it online!

Calling syntax (with curried function g, arguments x1 through x3, and number of arguments n):

((n x1 f g) x2) x3

Requires ⎕IO←1

Zacharý

Posted 2018-04-14T10:26:32.913

Reputation: 5 710