loading...

Daily Challenge #123 - Curry me Softly

thepracticaldev profile image dev.to staff ・2 min read

Disclaimer: Currying is not exactly what is described below. Stretch your mind!
Task

Currying is a technique named (as is the language Haskell) after the mathematician Haskell Curry which allows a function's arguments to be fed to it through separate instances of running that function.

Challenge
Create a function which accepts a single argument of another function and returns a "curried" version of that function.

For example, take the following function:

function adder(arg1, arg2) {
  return arg1 + arg2;
}

This function would normally be invoked like so:

var example = adder(1,3); // 4

A "curried" version of this function could be executed in either of the following ways

var example = adder(1)(4); // 5

OR

adder(5)
var example2 = adder(6); // 11

Your goal is to produce a higher-order function that accepts another function as an argument and returns a "curried" version of that function.

function adder () {
  return [].slice.call(arguments).reduce(function(a,b){
    return a + b
  },0);
}
var curryAdder = CurryIt(adder);

This "curried" version of the original function should expand its arguments when invoked with arguments. It should allow multiple arguments to be passed into each invocation.

It should execute the original function and then restore that function's original argument-less state when invoked without arguments.

See example here, assuming curryAdder from above has been created already:

curryAdder(1);
curryAdder(1,2,3);
curryAdder(2)(2,5);
var example = curryAdder(); // 16
curryAdder(1)(2);
var example2 = curryAdder(); // 3

For fun, let's make sure this works with native global functions and methods too! (will involve some context)

var curryEval = CurryIt(eval);
curryEval("var y = function(){return true}");
curryEval();
y(); // => true

Context should be fixed the first time the "curried" function is called after creation.

Happy coding! :)


This challenge comes from SirRodge on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❤️

Discussion

pic
Editor guide
 

TypeScript

"use strict";

function curry<ParameterType, ReturnType>(callable: Function, ...initial: ParameterType[]): Function {
    return function(...additional: ParameterType[]): Function | ReturnType {
        const parameters: ParameterType[] = [...initial, ...additional];

        if (parameters.length >= callable.length) {
            return callable(...parameters);
        }

        return curry(callable, ...parameters);
    };
}

function add(x: number, y: number): number {
    return x + y;
}

const $add = curry<number, number>(add);
const $pow = curry<number, number>(Math.pow);

console.log($add(1, 2));    // 3
console.log($add(1));       // [Function]
console.log($add(1)(2));    // 3

console.log(Math.pow(10, 2));   // 100
console.log($pow(10));          // [Function]
console.log($pow(10, 2));       // 100

Playground

Repl.it.

 

I don't think this one is going to work with the last adder function, the one that uses reduce.

 

Yes indeed you are correct. My proposal is based on finite arguments. This means that it won't work for an infinite arguments as well as functions that relies on the arguments keyword.

 

Not really currying, but, in any case

function adder () {
  return [].slice.call(arguments).reduce(function(a,b){
    return a + b
  },0);
}

var curryAdder = function() {
  if (!curryAdder['acc']) curryAdder['acc'] = 0
  if (arguments.length > 0) {
    var args = [...arguments];
    curryAdder['acc'] += adder(...args);
    return curryAdder;
  } else {
    var ret = curryAdder['acc'];
    curryAdder['acc'] = 0;
    return ret;
  }
}

repl.it/@logycon/Daily-Challenge-123

 

I don't know what's going on with those weird curryAdder calls.

curryAdder(1);
curryAdder(1,2,3);
curryAdder(2)(2,5);
var example = curryAdder(); // 16

It doesn't look like normal currying to me. You're not using the returned curried function (if there is one).

Anyway. Here is mine. Don't judge me.

function curry(fn, arity) {
  if (arguments.length === 1) {
    // you won't tell me? Fine, I'll guess.
    arity = fn.length;

    if(arity === 0) {
      // you are a sneaky one.
      // you trying to curry a variadic function?
      // hope you know what you're doing.
      return curry.bind(null, fn, {last: 0});
    }
  }

  // Gather the relevant arguments.
  var rest = Array.prototype.slice.call(arguments, 2);

  var arity_satisfied = arity <= rest.length;

  // for that edge case. If it is the same you didn't add new arguments.
  var called_without arguments = arity.last === rest.length;

  if (arity_satisfied || called_without arguments){
    return fn.apply(fn, rest);
  }

  if(typeof arity.last == 'number') {
    // state. Remember the arguments count.
    arity.last = rest.length;
  }

  // you can blame `bind` for not taken an array.
  var args = [null, fn, arity].concat(rest);

  // call `bind` with `args` and give me a function.
  return curry.bind.apply(curry, args);
}

Oh and the test. The test was fun. Did you know that the assert module on node had a strict mode? I didn't.

// this is done with mocha
// mocha --ui qunit -r esm test/**/*.test.js

import { strict as t } from 'assert';
import { curry } from '../../src/utils';

suite('# curry');

test('variadic function without arguments list', function() {
  function add () {
    return [].slice.call(arguments).reduce(function(a,b){
      return a + b
    },0);
  }

  let curried = curry(add);
  t.equal(typeof curried, 'function', 'curried is a function');

  let curried_1 = curried(1)(1,2,3)(2)(2,5);
  t.equal(typeof curried_1, 'function', 'still currying');

  t.equal(curried_1(), 16, 'stops when no argument is supplied');
});
 

A simple impl in Typescript...

function doCurrishSortOfThingToIt(fn: Function) {
  const savedArgs: any[] = [];
  return curriedFn;

  function curriedFn(...newArgs: any[]) {
    savedArgs.push(...newArgs);
    if (newArgs.length) return curriedFn;

    const result = fn.apply(this, savedArgs);
    savedArgs.length = 0;
    return result;
  }
}

Running though the examples...


function adder () {
  return [].slice.call(arguments).reduce(function(a,b){
    return a + b
  },0);
}
var curryAdder = doCurrishSortOfThingToIt(adder);

curryAdder(1);
curryAdder(1,2,3);
curryAdder(2)(2,5);
var example = curryAdder();
curryAdder(1)(2);
var example2 = curryAdder();

console.log(example, example2) // output: 16, 3

var curryEval = doCurrishSortOfThingToIt(eval);
curryEval("var y = function(){return true}");
curryEval();
console.log(y()); // output: true
 

Type erasure sucks. Kotlin doesn't escape from that fact.

fun <A, B, C> curry(a: A, f: (A, B) -> C): (B) -> C = { b -> f(a, b) }

fun <A, B> surround(a: A, b: B): String = "$a$b$a"

fun <A, B, C> curryN(a: A, f: (A, Array<B>) -> C): (Array<B>) -> C = { b -> f(a, b) }

fun main() {

    val add12 = curry(12) { a, b: Int -> a + b }
    println("${add12(13)}")


    fun <T> currySurround7(b: T): String = curry<Int, T, String>(7, ::surround)(b)

    println(currySurround7("Some example"))
    println(currySurround7(123))

    fun <T> currySurroundOther(b: T) = curry<String, T, String>("not as elegant", ::surround)(b)
    println(currySurroundOther("Some example"))
    println(currySurroundOther(123))

    fun addAllTimes3(vararg bs: Int) =
        curryN(3) { a, bsInner: Array<Int> -> bsInner.sum().times(a) }(bs.toTypedArray())

    println(addAllTimes3(1, 1, 2, 3, 4))
}
 

This really doesn't seem like currying. It seems counter-productive to even call it that because it seems to go exactly against functional programming's principles by relying on mutability.

Anyways, here's my JavaScript solution for this.

function CurryIt(f) {
  let currentArgs = [];
  const curriedF = (...as) => {
    if (as.length === 0) {
      const v = f(...currentArgs);
      currentArgs = [];
      return v;
    } else {
      currentArgs = currentArgs.concat(as);
      return curriedF;
    }
  };

  return curriedF;
}
 

In F#:

let add x y = x + y
let add10 = add 10

// add10 is now a curried function
add10 1 // = 11
add10 50 // = 60
 

Since I know you’re learning F#: you don’t need to define the add function.

let add10 = (+) 10