Functions as Data

Functions as Data

Mark Galea

Brain

Programming languages have evolved largely through a series of abstractions; these abstractions mostly deal with control (e.g. functions) or data. In the post, Houses, Arrays and Higher Order Functions, we have focused on the use of functions as control elements and have delved into the concept of higher order functions. Today we will revisit functions and focus on their use as data elements. Using functions to represent data might seem pretty counter intuitive at first but this blurred line between functions as control elements and functions as data elements is a powerful concept.

Sets

In order to illustrate how functions can be used as data elements we will create a Set library with the following operations: union, intersection and difference. We will define a set using the characteristic function T -> Boolean i.e. a function which takes an element T as input and return a true or false depending on whether the element is contained within the set. Using classic OOP we can define a set as follows:

class Set<T>(vararg values: T) {
    var elements: List<T> = values.toList()
    fun contains(element:T): Boolean = this.elements.contains(element)
}

In this case a set is defined as a class with a generic type T, we will use the underlying list data structure elements to implement the contains function. We can use the OOP representation as follows:

val s1 = Set(1)
val s2 = Set(2, 3, 4)

Using functions as data elements we would implement a Set as follows:

fun <T>setOf(element: T): (T) -> Boolean  = { test -> element!!.equals(test) }
fun <T>contains(set: (T)->Boolean, element: T): Boolean  = set(element)

The function setOf is a constructor function (as defined by SICP) which takes in an element and returns a higher order functions that implements the characteristic function (T) -> Boolean. contains just delegates the test to the setOf higher order function by calling set(element). Some observant readers might have noticed that the class Set can ingest a number of values whilst the setOf function can only ingest one value. Using the union operator (defined further on) we can extends the setOf function to accept a variable number of elements T.

fun <T>setOf(vararg elements: T): (T) -> Boolean {
    return elements.map { setOf(it) } .reduce { s, t -> union(s, t) }
}

Using the setOf function we can define the set s1 and s2 as follows:

val s1 = setOf(1)
val s2 = setOf(2, 3, 4)

Note that even though the representation is completely different the end user consuming either library will not be exposed to the underlying details of our implementations (OOP vs Functions). To an end user (except for minor syntactic differences) these two Sets are not different from each another.

Union

Now that we have a means of representing a set, let us implement union in both representations. In the OOP approach we would implement union as follows:

class Set<T>(vararg values: T) {
    fun union(other:Set<T>): Set<T> {
        val unionSet = Set<T>()
        val elements = ArrayList(this.elements)
        elements.addAll(other.elements.filter { x -> !this.elements.contains(x) })
        unionSet.elements = elements
        return unionSet
    }
    ...
}

In this case we are using the backing data stucture elements to keep track of what is in the set. We can test this implementation as follows:

val s1 = Set(1)
val s2 = Set(2, 3, 4)
println(s1.union(s2).contains(1)) // -> true
println(s1.union(s2).contains(2)) // -> true
println(s1.union(s2).contains(3)) // -> true
println(s1.union(s2).contains(4)) // -> true
println(s1.union(s2).contains(5)) // -> false
println(s1.union(s2).contains(0)) // -> false

Using functions we get a much more pure definition (mathematically speaking):

fun <T>union(s1: (T)->Boolean, s2: (T)->Boolean): (T)->Boolean = 
        {element -> s1(element) || s2(element) }

An element is contained in the union of set s1 and s2 if the element is either in set s1 or s2. This is clearly represented by the line element -> s1(element) || s2(element). Don't be fooled by the fact that Kotlin does not support structural types, had the language supported structural types our definition would be a bit more concise:

fun <T>union(s1: Set<T>, s2: Set<T>):Set<T> = 
        {element -> s1(element) || s2(element) }

Needless to say this is just semantic sugar; regular functions (with milk) will do just fine! One would use the union operator as follows:

val s1 = setOf(1)
val s2 = setOf(2, 3, 4)
println(contains(union(s1, s2), 1)) // -> true
println(contains(union(s1, s2), 2)) // -> true
println(contains(union(s1, s2), 3)) // -> true
println(contains(union(s1, s2), 4)) // -> true
println(contains(union(s1, s2), 5)) // -> false
println(contains(union(s1, s2), 0)) // -> false

Note again that the usage is very similar and an end user would find it hard to believe that the Set is implemented using functions.

Intersection

Having defined union, intersection will only be one synaptic leap away. Let's start off with the OOP version.

class Set<T>(vararg values: T) {
    fun intersection(other:Set<T>): Set<T> {
        val intersectionSet= Set<T>()
        val elements = ArrayList<T>()
        elements.addAll(this.elements.filter { x -> other.contains(x) })
        intersectionSet.elements = elements
        return intersectionSet
    }
    ...
}

One would use the definition above as follows:

val s3 = Set(1, 2)
val s4 = Set(2, 3, 4)
println(s3.intersection(s4).contains(1)) // -> false
println(s3.intersection(s4).contains(2)) // -> true
println(s3.intersection(s4).contains(3)) // -> false
println(s3.intersection(s4).contains(4)) // -> false
println(s3.intersection(s4).contains(5)) // -> false
println(s3.intersection(s4).contains(0)) // -> false

Using functions, we would represent the intersection function as:

fun <T>intersection(s1: (T)->Boolean, s2: (T)->Boolean): (T)->Boolean = 
        { element-> s1(element) && s2(element) }

Note how close this function is to the original mathematical definition; an element is contained in the intersection of Set s1 and s2 if the element is contained in set s1 and set s2, hence:

element -> s1(element) && s2(element)

One would use the above definition as follows:

val s3 = setOf(1, 2)
val s4 = setOf(2, 3, 4)
println(contains(intersection(s3, s4), 1)) // -> false
println(contains(intersection(s3, s4), 2)) // -> true
println(contains(intersection(s3, s4), 3)) // -> false
println(contains(intersection(s3, s4), 4)) // -> false
println(contains(intersection(s3, s4), 5)) // -> false
println(contains(intersection(s3, s4), 0)) // -> false

Difference

For completeness let us now implement difference in both representations. In the OOP approach we would implement difference as follows:

class Set<T>(vararg values: T) {
    fun difference(other:Set<T>): Set<T> {
        val differenceSet= Set<T>()
        val elements = ArrayList<T>()
        elements.addAll(this.elements.filter { x -> !other.contains(x) })
        differenceSet.elements = elements
        return differenceSet
    }
    ...
}

Using functions we can implement this using:

fun <T>difference(s1: (T)->Boolean, s2: (T)->Boolean): (T)->Boolean = 
        {element-> s1(element) && !s2(element) }

Set of multiple elements

Before we conclude I would like to give an intuition for the function

fun <T>setOf(vararg elements: T): (T) -> Boolean {
    return elements.map { setOf(it) } .reduce { s, t -> union(s, t) }
}

Specifically how we used map and reduce to create a characteristic function which accepts multiple elements as follows:

val s3 = setOf(2, 3, 4, 7)

What elements.map { setOf(it) } does is map all elements to a characteristic function, hence 2 becomes:

test -> 2.equals(test)

3 becomes:

test -> 3.equals(test)

and so on.

The reduce function will combine the characteristic functions as follows:

union (
    union(
        union(
            {test -> 2.equals(test)}, 
            {test -> 3.equals(test)}
        ), 
        {test -> 4.equals(test)}
    ), 
    {test -> 7.equals(test)}
)

Each union function creates a new characteristic function combining the underlying two characteristic functions. For e.g.

union(
    {test -> 2.equals(test)}, 
    {test -> 3.equals(test)}
)

would create the following characteristic function:

{test -> 
    {test1 -> 2.equals(test1)}(test) || 
    {test2 -> 3.equals(test2)}(test)
}

If we want to test whether 2 is in the set union, we would reduce the characteristic function as follows:

union({test -> 2.equals(test)}, {test -> 3.equals(test)})(2)
{test -> 
    {test1 -> 2.equals(test1)}(test) || // 2 here is the element in the set s1 
    {test2 -> 3.equals(test2)}(test)    // 3 here is the element in the set s2
}(2) // 2 is the value to test.  

Replace all occurrences of test with the value 2, we obtain:

{test1 -> 2.equals(test1)}(2) || {test2 -> 3.equals(test2)}(2)

Replace all occurrences of test1 and test2 with the value 2, we obtain:

2.equals(2) || 3.equals(2)
true

Hence the characteristic function for the set union between s1 and s2 when applied to 2 has reduced to true, which is correct.

Conclusion

In this post we have looked at how we can use functions as data elements. Using functions as a data representation might feel unnatural at first but this blurred boundary between functions as elements of control and functions as elements of data is quite powerful. I really hope that you find this technique useful. Stay safe and keep hacking!