Functional Programming Part 4: Currying, Memoization & Standard Functions

Lecture



Nowadays JavaScript is borrowing a lot of techniques from functional programming. The advantage of using functional approaches in JS is a concise implementation in just a couple lines of code and less duplication. From this follow the downsides as well — for example, in terms of readability and understanding, especially for those unfamiliar with how functional programming works.

The Concept of Currying

Functional Programming Part 4: Currying, Memoization & Standard Functions

In the previous chapter we had a problem that prevented us from composing the functions mult5 and add: mult5 takes a single parameter, while add takes two.

We can solve this problem very easily by reducing the number of inputs to one for every function.

Trust me. It's not as bad as it sounds.

We simply write our addition function to use two input parameters, but accept them one at a time. Curried functions let us do exactly that.

A curried function is a function that accepts one argument at a time.

Currying is a transformation of functions such that instead of taking arguments as f(a, b, c), they take them as f(a)(b)(c).

Currying does not call the function — it merely transforms it.

Currying (from the English currying, sometimes — curring) is the transformation of a function of many arguments into a set of functions, each of which is a function of a single argument. The possibility of such a transformation was first noted in the works of Gottlob Frege, systematically studied by Moses Schönfinkel in the 1920s, and the name comes from Haskell Curry, the developer of combinatory logic, in which reduction to functions of a single argument is fundamental.

Definition of Currying

For a function of two arguments Functional Programming Part 4: Currying, Memoization & Standard Functions, the currying operator Functional Programming Part 4: Currying, Memoization & Standard Functions performs the transformation Functional Programming Part 4: Currying, Memoization & Standard Functions — it takes an argument of type Functional Programming Part 4: Currying, Memoization & Standard Functions and returns a function of type Functional Programming Part 4: Currying, Memoization & Standard Functions. Intuitively, currying a function lets you fix one of its arguments, returning a function of the remaining arguments. Thus Functional Programming Part 4: Currying, Memoization & Standard Functions is a function of type Functional Programming Part 4: Currying, Memoization & Standard Functions.

Uncurrying is introduced as the inverse transformation — restoring the curried argument: for a function Functional Programming Part 4: Currying, Memoization & Standard Functions, the uncurrying operator Functional Programming Part 4: Currying, Memoization & Standard Functions performs the transformation Functional Programming Part 4: Currying, Memoization & Standard Functions; the type of the uncurrying operator is Functional Programming Part 4: Currying, Memoization & Standard Functions.

In practice, currying allows treating a function that has not yet received all of its intended arguments. The currying operator is built into some programming languages, allowing multi-argument functions to be reduced to curried form — the most characteristic examples of such languages are ML and Haskell. All languages that support closures allow curried functions to be written.

PARTIAL APPLICATION, CURRYING

Fixing some of a function's arguments produces a new function with fewer arguments.

The term "currying" has three interrelated meanings

1. Fixing the first few arguments

2. Currying f: X*Y -> Z is constructing f`: X -> (Y -> Z)

3. Applying a curried function to arguments g = f`(x) 25

a special case of partial application, in which the first several arguments of a function are fixed

add :: Integer -> Integer -> Integer
add x y = x + y
inc :: Integer -> Integer
inc = add 1  

Turning a function F over a 2-element tuple (a pair with component types X and Y) into a function over X that returns a function over Y (such a function is called the "curried" version of F)

matchesRegexpUncurried :: (String,String) -> Bool
matchesRegexpUncurried = ...
matchesRegexpCurried :: String -> (String -> Bool)
matchesRegexpCurried pattern = matcher
  where matcher s = matchesRegexpUncurried (pattern,s) 

Applying a curried function to arguments:

isNumber = matchesRegexpCurried ”−?[0−9]+”

In this case, the procedure isNumber is said to have been obtained by currying the procedure matchesRegexpCurried

The Mathematical Point of View

In theoretical computer science, currying provides a way to study functions of several arguments within very simple theoretical systems, such as the lambda calculus. Within set theory, currying is a correspondence between the sets Functional Programming Part 4: Currying, Memoization & Standard Functions and Functional Programming Part 4: Currying, Memoization & Standard Functions. In category theory, currying arises from the universal property of the exponential object; in a cartesian closed category this leads to the following correspondence. There is a bijection between the set of morphisms from the binary product Functional Programming Part 4: Currying, Memoization & Standard Functions and the morphisms into the exponential Functional Programming Part 4: Currying, Memoization & Standard Functions, which is natural in Functional Programming Part 4: Currying, Memoization & Standard Functions and in Functional Programming Part 4: Currying, Memoization & Standard Functions. This statement is equivalent to saying that the product functor and the Hom functor are adjoint functors.

This is a key property of a cartesian closed category, or, more generally, a closed monoidal category. The former is fully sufficient for classical logic, while the latter is a convenient theoretical basis for quantum computation. The difference is that the cartesian product contains only information about the pair of two objects, whereas the tensor product used in the definition of a monoidal category is suited to describing entangled states.

From the standpoint of the Curry–Howard correspondence, the existence of currying functions (inhabitation of the type Functional Programming Part 4: Currying, Memoization & Standard Functions) and uncurrying functions (inhabitation of the type Functional Programming Part 4: Currying, Memoization & Standard Functions) is equivalent to the logical statement Functional Programming Part 4: Currying, Memoization & Standard Functions (the product type Functional Programming Part 4: Currying, Memoization & Standard Functions corresponds to conjunction, and the function type Functional Programming Part 4: Currying, Memoization & Standard Functions corresponds to implication). The currying and uncurrying functions are Scott-continuous.

Currying from a Programming Point of View

Currying is widely used in programming languages, primarily those that support the functional programming paradigm. In some languages, functions are curried by default, meaning multi-argument functions are implemented as single-argument higher-order functions, and applying arguments to them is a sequence of partial applications.

In programming languages with first-class functions, there are usually operations curry (turning a function with signature A, B -> C into a function with signature A -> B -> C) and uncurry (performing the reverse transformation — mapping a function of signature A -> B -> C to a two-argument function of the form A, B -> C). In these cases the connection to the partial-application operation papply is transparent: curry papply = curry.

So, with their help we'll pass the first parameter into add before we compose it with mult5. Then, when mult5AfterAdd10 is called, add will receive its second parameter.

In JavaScript we can implement this idea by rewriting add:

var add = x => y => x + y;

This version of add is a function that takes one parameter right away and the second one later.

More precisely, the function add takes a single parameter, x, and returns a function that takes the next single parameter, y, which will ultimately return the result of adding x and y.

Now we can use the new add to write a working version of mult5AfterAdd10:

var compose = (f, g) => x => f(g(x));
var mult5AfterAdd10 = compose(mult5, add(10));

The compose function takes two parameters as input: f and g. It then returns a function that takes a single parameter, x, and when called, the composition of the functions f after g is carried out with the argument x.

So what did we actually do? Well, we converted our plain old add function into its curried version. This made add more flexible, since the first parameter, 10, can be passed before the function is actually executed, and the second one — when mult5AfterAdd10 is called.

At this point you're probably wondering how to rewrite the addition function for Elm. It turns out there's no need to. In Elm and other functional programming languages, all functions are automatically curried.

So the add function stays unchanged:

add x y =
    x + y

And here's how mult5AfterAdd10 should have been written, going back to Part 3:

mult5AfterAdd10 =
    (mult5 << add 10)

Speaking of syntax, Elm has the upper hand over imperative languages like JavaScript, since it was originally optimized for various functional programming tasks, such as currying or function composition.

Currying and Refactoring

Another case where currying can really shine is the refactoring process, during which you create a general-purpose version of a function with many parameters, and then use it to create a more specialized version with fewer inputs.

Let's say, for example, that we have the following functions that wrap a string in single and double brackets:

bracket str =
    "{" ++ str ++ "}"doubleBracket str =
    "{{" ++ str ++ "}}"

And here's how we use them:

bracketedJoe =
    bracket "Joe"doubleBracketedJoe =
    doubleBracket "Joe"

We can generalize bracket and doubleBracket:

generalBracket prefix str suffix =
    prefix ++ str ++ suffix

But now, on every call to generalBracket, we have to pass in the brackets themselves as input values:

bracketedJoe =
    generalBracket "{" "Joe" "}"doubleBracketedJoe =
    generalBracket "{{" "Joe" "}}"

What we really want is to get the best of both worlds.

If we regroup the input parameters in generalBracket, we can create bracket and doubleBracket while taking advantage of the fact that functions are curried:

generalBracket prefix suffix str =
    prefix ++ str ++ suffixbracket =
    generalBracket "{" "}"doubleBracket =
    generalBracket "{{" "}}"

Notice that by putting the static parameters first — prefix and suffix — and the changing parameter last — str — we can easily create specialized versions of generalBracket.

The order of the input parameters matters a great deal for making the most of currying.

Also notice that the functions bracket and doubleBracket are written in point-free style, meaning the argument str is only implied. Both bracket and doubleBracket are waiting for their last parameter.

Now we can use them the way we wanted to in the first place:

bracketedJoe =
    bracket "Joe"doubleBracketedJoe =
    doubleBracket "Joe"

Except now we're using the general currying function — generalBracket.

Example of Computing a Solid's Volume Without Currying

Functional Programming Part 4: Currying, Memoization & Standard Functions

if some variables don't change, currying can be used in different ways - via bind, via nested functions, via arrow functions.

Example of Currying in JS Using bind

Functional Programming Part 4: Currying, Memoization & Standard Functions

Example of Currying in JS Using Nested Functions

Functional Programming Part 4: Currying, Memoization & Standard Functions

Example of Currying in JS ES6

const getBrickVolume = width => length => height => width * length * height;

let brickVolumeWithWidthAndLength = getBrickVolume(10)(20)

volume1 = brickVolumeWithWidthAndLength(8);
volume2 = brickVolumeWithWidthAndLength(12);
volume3 = brickVolumeWithWidthAndLength(15);

console.log(volume1,volume2,volume3)

prints

1600 2400 3000

Examples

C ++ 11 [ edit | edit code ]

#include <functional>
auto  curry  =  ([] ( int  x ) -> std :: function < int ( int ) > {
       return  [ x ] ( int  y ) -> int  {
          return  x + y ;
       };
});
int  a  =  curry ( 4 ) ( 5 )  // 9 
auto  curry_4  =  curry ( 4 )
int  b  = curry_4 ( 5 )  // 9

C # (3.0) [ edit | edit code ]

Func < int ,  Func < int ,  int >>  curry  =  ( x  =>  ( y  =>  x  +  y ));
curry ( 4 ) ( 5 )  // 9

Erlang [ edit | edit code ]

Curry  =  fun ( A )  ->  fun ( B )  ->  A  +  B  end  end .
( Curry ( 3 )) ( 4 ).  % => 7

F # [ edit | edit code ]

let  add  a  b  =  a  +  b  // 'a ->' a -> 'a 
let  addOne  =  add  1  //' a -> 'a 
let  x  =  addOne  10  // 11

Common Lisp [ edit | edit code ]

( Defun  curry ( x )
  ( lambda  ( y )  ( +  x  y )))
(( curry  2 )  3 )  ; returns 5 
; due to semantic differences it returns an error (unlike Scheme) ... 
( funcall  ( curry  2 )  3 )  ; returns 5

Haskell [ edit | edit code ]

curry  x  =  ( \ y  ->  x  +  y )  - can also be written curry = (+) 
curry  2  3  - returns 5

Hope [ edit | edit code ]

!  curry  -  turn  a  binary  function  into  a  function  producing  a  function . 
! 	( Named  after  Haskell  B .  Curry )
!  e . g .  curry  f  x  y  =  f ( x ,  y )
dec  curry  :  ( alpha  #  beta  ->  gamma )  ->  alpha  ->  beta  -> gamma ;
--- curry f <= lambda x => lambda y => f (x, y) 
curry  ( + )  1 ;
>>  lambda  y  =>  1  +  y :  num -> num
( curry  ( + )  1 )  2 ;
>> 3 :  num

JavaScript [ edit | edit code ]

function  curry ( x ) {
    return  function ( y ) {
        return  x  +  y ;
    }
}
Curry ( 4 ) ( 5 )  // returns 9

Starting with ECMAScript 5, this code is possible:

const  curry  =  ( fn , x )  =>  ( y )  =>  fn ( x , y )
const  add  =  ( x , y )  =>  x + y ;
curry ( add , 4 ) ( 5 )  // returns 9

Lisp Scheme [ edit | edit code ]

; definition 
( define ( curry  x )
  ( lambda ( y )
    ( + x  y )))
; call 
( let (( curr  ( curry  4 )))
  ( curr  5 ))  , result 9 
; or like this 
(( curry  4 )  5 )

OCaml [ edit | edit code ]

let  curry  x  =  function  y  ->  x  +  y ;;    (* Val curry: int -> int -> int = <fun> *) 
let  a  =  curry  4  5 ;;    (* -: int = 9 *)

OCaml is a language of the ML family; in languages of this family, converting a multi-argument function into curried form happens automatically:

let  curry  x  y  =  x  +  y ;;    (* Val curry: int -> int -> int = <fun> *) 
let  a  =  curry  4 ;;    (* Val a: int -> int = <fun> *) 
a  5 ;;    (* -: int = 9 *)

Python [ edit | edit code ]

curry  =  lambda  fn ,  x :  lambda  y :  fn ( x ,  y )
add  =  lambda  x ,  y :  x  +  y
curry ( add ,  4 ) ( 5 )    # => 9

Perl [ edit | edit code ]

sub  curry 
{
    my  $ x  =  shift ;
    return  sub  {  return  $ x  +  shift  }
}
curry ( 4 ) -> ( 5 )   # 9

PHP [ edit | edit code ]

Starting with PHP 5.3, which added closures .

function  curry ( $ x )  {
    return  function  ( $ y )  use  ( $ x )  {
               return  $ x  +  $ y ;
           };
}
$ A  =  curry ( 5 )
$ b  =  $ a ( 10 )  // 15

Ruby [ edit | edit code ]

def  curry ( x )
  Proc . new { | y |  x  +  y }
end 
curry ( 1 ) . call ( 2 )  # => 3

Scala [ edit | edit code ]

def  curry ( x :  Int ) ( y :  Int )  =  x  +  y  // curry: (Int) (Int) Int 
f  =  curry ( 4 ) _ 
f ( 5 )  // Int = 9

Objective-C [ edit | edit code ]

An example implementation using blocks:

typedef  int  ( ^ Add ) ( int  y )

Add  curry ( int  x )  {
	return   Block_copy ( ^ ( int  y )  {
		return  x  +  y ;
	});
}

Int  res  =  curry ( 5 ) ( 6 )
NSLog ( @ "% i" , res );
>> 11

Google Go [ edit | edit code ]

package  main

func  main ()  {
  curry  =  func ( x  int )  func ( int )  int  {
    return  func ( y  int )  int  {
      return  x + y
    }
  }
  print ( curry ( 2 ) ( 3 ))  // 5 
}

MATLAB [ edit | edit code ]

curry = @ ( x ) @ ( y ) x + y ;

a = curry ( 5 )
disp ( a ( 6 )); % 11

Visual Prolog [ edit | edit code ]

F  =  {( Y )  =  {( X ) =X + Y }}
write ( F ( 2 ) ( 3 )),     % 5

SWI Prolog [ edit | edit code ]

t ( A ,  B ): -  A  >  B ,  !.
call ( call ( t ,  3 ),  0 ).  % true

FUNCTIONS INSTEAD OF OBJECTS: the Link Between Closures and Currying

Closures and currying tie data and functions together in a way similar to objects

Memoization as a Functional Programming Technique

Memoization is a technique that implements the storage of function execution results in order to avoid repeated computations. The idea is quite simple - before each function call, a check is made whether the function was already called with the same arguments before. If it was, the stored result is returned; otherwise, the answer is computed. The result obtained is then stored and returned.

Memoization — in programming, storing the results of function execution to prevent repeated computations. It is one of the optimization techniques used to increase the execution speed of computer programs. Before a function is called, it is checked whether the function has already been called before:

  • if it hasn't been called, the function is called, and the result of its execution is stored;
  • if it has been called, the stored result is used.

Memoization can be used not only to increase the speed of a program. For example, it is used in simple mutually recursive top-down parsing in the generalized top-down parsing algorithm.

Despite its connection to caching, memoization is a special kind of optimization, distinct from caching methods such as buffering and page replacement.

In logic programming languages, memoization is known as "tabling."

A generic function for creating a memoized function is quite simple:


 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function memo(fn) {
    const cache = {};
    const slice = Array.prototype.slice;

    return function () {
        const args = slice.call(arguments);
        const key = JSON.stringify(args);

        if (cache[key]) {
            return cache[key];
        }

        const result = fn.apply(null, args);
        cache[key] = result;

        return result;
    };
}

Where can this be used? For instance, in cases where we need to perform complex, lengthy, and/or repeated computations.

Standard Functions of Functional Programming

Let's take a look at three standard functions used in functional programming languages.

But first, let's take a look at the following JavaScript code:

for (var i = 0; i < something.length; ++i) {
    // do stuff
}

This code has one significant harmful trait. And it's not a bug. The problem is that it's boilerplate code — that is, code that gets used over and over again.

If you write in an imperative language like Java, C#, JavaScript, PHP, Python, and so on, you'll find this pattern repeating more than any other.

That's exactly what's wrong with this code.

So let's get rid of it. Let's wrap it in a function (or several functions) and never write a for loop again. Well, almost never — at least until we've fully switched over to functional programming.

var things = [1, 2, 3, 4];
for (var i = 0; i < things.length; ++i) {
    things[i] = things[i] * 10; // WARNING: MUTATING DATA !!!!
}
console.log(things); // [10, 20, 30, 40]

Well, damn! Mutability!

Let's try again. This time we won't mutate things:

var things = [1, 2, 3, 4];
var newThings = [];for (var i = 0; i < things.length; ++i) {
    newThings[i] = things[i] * 10;
}
console.log(newThings); // [10, 20, 30, 40]

Okay, so we didn't mutate things, but technically we did mutate newThings. Let's let that slide for now — after all, we're in the world of JavaScript. Once we've touched functional programming ideas, we no longer want to resort to mutability.

At this point it's important to understand how these functions work and how they help us reduce the "noise" in our code.

Let's take this code and put it into a function. We'll call our first standard function map (translator's note: the English verb "to map"), since it maps each value from the old array to a new value in the new array:

var map = (f, array) => {
    var newArray = [];    for (var i = 0; i < array.length; ++i) {
        newArray[i] = f(array[i]);
    }
    return newArray;
};

Notice that the function, f, is passed in, and that lets our map function do whatever we want with each element of the array.

Now we can rewrite the previous code using map:

var things = [1, 2, 3, 4];
var newThings = map(v => v * 10, things);

"Mom, mom, look, I wrote code without a for loop!" Now it's easier to read, and therefore easier to analyze.

Well, technically, the for loop is still there, inside the map function. But now we're free from constantly repeating this boilerplate code.

Now let's write another standard function, one that filters objects in an array:

var filter = (pred, array) => {
    var newArray = [];for (var i = 0; i < array.length; ++i) {
        if (pred(array[i]))
            newArray[newArray.length] = array[i];
    }
    return newArray;
};

Notice that if the predicate function, pred, returns TRUE, we keep the element, and if it returns FALSE, we discard it.

Here's how you can apply filter to filter out odd numbers:

var isOdd = x => x % 2 !== 0;
var numbers = [1, 2, 3, 4, 5];
var oddNumbers = filter(isOdd, numbers);
console.log(oddNumbers); // [1, 3, 5]

Using our new filter is much simpler than constantly rewriting it by hand with a for loop.

The last standard function is called reduce (translator's note: the English verb "to reduce"). It's typically used when you need to take a list and reduce it to a single value, though in reality its capabilities are much broader.

Usually, in functional languages, this function is called fold (translator's note: the English verb "to fold").

var reduce = (f, start, array) => {
    var acc = start;
    for (var i = 0; i < array.length; ++i)
        acc = f(array[i], acc); // f() takes 2 arguments
    return acc;
});

The reduce function takes a folding function, f, an initial value, start, and array.

Keep in mind that the folding function, f, takes two parameters: the current array element and the accumulator, acc. It uses these parameters to update the accumulator on every new iteration. The value of the accumulator at the last iteration is what gets returned from the function.

Here's an example that will help us understand how this works:

var add = (x, y) => x + y;
var values = [1, 2, 3, 4, 5];
var sumOfValues = reduce(add, 0, values);
console.log(sumOfValues); // 15

Notice that the add function takes two parameters and adds them together. Our reduce function is exactly expecting a function that takes two parameters, so they work well together.

We start with the start value, equal to zero, and step by step sum up the values of our values array. With each new iteration, the sum inside the reduce function grows. And, in the end, the accumulated value is returned as sumOfValues.

Each of these functions, map, filter, and reduce, makes it easier for us to work with arrays and frees us from the tedious repetition of boilerplate for loops.

But in functional programming they're even more valuable, since in cases that call for looping, it's difficult to get by with recursion alone. Iterative functions aren't just extremely useful. They're simply necessary.

See Also

  • Strength reduction — an optimization that replaces expensive operations with cheaper equivalents.
  • Lookup table — a key data structure used in memoization.
  • Flyweight (design pattern) — a pattern that uses memoization.
  • Dynamic programming — applications of memoization techniques.
  • Lazy evaluation.
  • lazy computation

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Functional programming"

Terms: Functional programming