Currying

General Idea

Curry with Currying

In mathematics and computer science, currying is the technique of breaking down the evaluation of a function that takes multiple arguments into evaluating a sequence of single-argument functions.f Currying is also used in theoretical computer science, because it is often easier to transform multiple argument models into single argument models.

Composition of Functions

We define the composition h of two function f and g

$$h(x) = g(f(x))$$

in the following Python example.

The composition of two functions is a chaining process in which the output of the inner function becomes the input of the outer function.

def compose(g, f):
    def h(x):
        return g(f(x))
    return h

We will use our compose function in the next example. Let's assume, we have a thermometer, which is not working accurate. The correct temperature can be calculated by applying the function readjust to the temperature values. Let us further assume that we have to convert our temperature values into degrees fahrenheit. We can do this by applying compose to both functions:

def celsius2fahrenheit(t):
    return 1.8 * t + 32
def readjust(t):
    return 0.9 * t - 0.5
convert = compose(readjust, celsius2fahrenheit)
convert(10), celsius2fahrenheit(10)
The above Python code returned the following output:
(44.5, 50.0)

The composition of two functions is generally not commutative, i.e. compose(celsius2fahrenheit, readjust) is different from compose(readjust, celsius2fahrenheit)

convert2 = compose(celsius2fahrenheit, readjust)
convert2(10), celsius2fahrenheit(10)
The above Python code returned the following output:
(47.3, 50.0)

convert2 is not a solution to our problem, because it is not readjusting the original temeratures of our thermometre but the transformed Fahrenheit values!

Example Currency Conversion

In our chapter on Magic Functions we had an excercise on currency conversion.

"compose" with Arbitrary Arguments

The function compose which we have just defined can only copy with single-argument functions. We can generalize our function compose so that it can cope with all possible functions.

In [ ]:
def compose(g, f):
    def h(*args, **kwargs):
        return g(f(*args, **kwargs))
    return h

Example using a function with two parmameters.

In [ ]:
def BMI(weight, height):
    return weight / height**2
def evaluate_BMI(bmi):
    if bmi < 15:
        return "Very severely underweight"
    elif bmi < 16:
        return "Severely underweight"
    elif bmi < 18.5:
        return "Underweight"
    elif bmi < 25:
        return "Normal (healthy weight)"
    elif bmi < 30:
        return "Overweight"
    elif bmi < 35:
        return "Obese Class I (Moderately obese)"
    elif bmi < 40:
        return "Obese Class II (Severely obese)"
    else:
        return "Obese Class III (Very severely obese)"
f = compose(evaluate_BMI, BMI)
weight = 1
while weight > 0:
    weight = float(input("weight (kg) "))
    height = float(input("height (m) "))
    print(f(weight, height))
weight (kg) 73
height (m) 1.76
Normal (healthy weight)
weight (kg) 75
height (m) 1.76
Normal (healthy weight)
def arimean(*args):
    return sum(args) / len(args)
def curry(func):
    f_args = []
    f_kwargs = {}
    def f(*args, **kwargs):
        nonlocal f_args, f_kwargs
        if args or kwargs:
            f_args += args
            f_kwargs.update(kwargs)
            return f
        else:
            return func(*f_args, *f_kwargs)
    return f
            
s = curry(arimean)
s(2)(5)(9)(4, 5)
s(5, 9)
print(s())
s2 = curry(arimean)
s2(2)(500)(9)(4, 5)
s2(5, 9)
s2()
5.571428571428571
The previous Python code returned the following output:
76.28571428571429