I want to model simple objects as functions, to see where the pure functional approach gets me. Let’s say my object is a person. There is some function that returns me a new person, but instead of just some data structure, it returns me a function that represents the person. I’m having difficulties to see what this function should exactly look like, because I mostly associate data/state with a person but not any function. So if I call this person, which is a function now, what input should it take and what should it return?
If I look at the lambda calculus and some examples of how to model numbers, 0 is just the identity function. 1 is a function that takes another function and a value as input and returns the argument function applied to the value. 2 applies the function twice, etc.
So should the implementation of a person be a function that takes another function as input and applies it to the person, returning its output? So it’s actually just a container for the person’s data allowing other functions to operate on it.
May be it makes no sense to think about this without knowing exactly what a persons consists of. In a most simple case a person could be just a name. A briefly tried to outline this in Clojure:
(defn new-person [firstname lastname] (fn [f] (f firstname lastname)))
(defn get-firstname [person] (person (fn [x y] x)))
(def p (new-person "Maja" "Abel"))
(get-firstname p)
"Maja"
Then again the names are strings and not very functional …
Hope this makes some sense, glad if anybody has some thoughts on it!
We can implement numerics as pure functions – but we don’t actually do so for performance reasons. We can model a cons cell (and thus arbitrary lists) through pure functions, but we don’t usually do that. Again, for performance reasons. And if we can model numbers and lists, we can model arbitrary strings or other things.
However, it might be useful to think about that cons cell/a pair for a moment.
A cons
constructor takes two elements and returns something. The car
function somehow accesses the first element in this something, whereas the cdr
accesses the second entry. Here is one implementation in JavaScript:
function cons(x, y) {
return function (gimme_first) {
if (gimme_first) return x;
return y;
};
}
function car(cell) {
return cell(true);
}
function cdr(cell) {
return cell(false);
}
So essentially what we did was return a closure over the state. We can access the state by applying the closure to a specific identifier.
We can easily extend this to arbitrary objects: Our constructor sets up some contents of the object, and returns a closure over it. This closure can be applied to a field name to return a value or a method (which also closes over the object’s data):
function Person(firstname, lastname) {
// set up the methods as closure over our data
// they could also take parameters if you'd like
var get_firstname = function() { return firstname; };
var get_lastname = function() { return lastname; };
var get_fullname = function() { return firstname + " " + lastname; };
// set up the dispatcher
return function(method) {
if ("get-firstname" == method) return get_firstname;
if ("get-lastname" == method) return get_lastname;
if ("get-fullname" == method) return get_fullname;
throw "Unknown method name " + method;
}
}
Now we can instantiate multiple objects:
var maja = Person("Maja", "Abel");
var freddy = Person("Freddy", "Smith");
maja("get-firstname")(); // Maja
freddy("get-lastname")(); // Smith
How does inheritance work? Our constructor defers to another constructor, and our dispatcher passes control to the parent:
function Employee(firstname, lastname, department) {
// set up the parent object
var parent = Person(firstname, lastname);
// override a method
var get_fullname = function() {
return parent("get-fullname")() + " (from " + department + ")";
};
// set up the dispatcher:
return function (method) {
if ("get-fullname" == method) return get_fullname;
return parent(method); // method lookup continues here
};
}
Etc.
This is actually quite similar to how JavaScript handles object orientation anyway: The dispatcher is usually a dictionary (called “object” anyway) which is more efficient than our functions, and object state is communicated via the implicit this
parameter instead of closures.