Imagine if OOP was developed using Functional Programming concepts. In this post I will be trying to explore that in JavaScript.
JS Code: (Online Demo Link)
/* OOP in a Functional way, with HOFs, Currying and Closures.
* Online Demo: https://repl.it/repls/BelovedLikelyDebugger */
const Rectangle = (h, w) => {
let _h = h;
let _w = w;
const methods = [
["getH", _ => _h],
["getW", _ => _w],
["getArea", _ => _h * _w],
["setH", h => { _h = h; return getMethod; }],
["setW", w => { _w = w; return getMethod; }],
];
const getMethod = methodName => {
const method = methods.filter(
method => method[0] === methodName);
// If method was found, then return it, else raise error
if (method.length > 0
&& method[0].length > 0
&& typeof method[0][1] === 'function')
return method[0][1];
throw new Error(`"${methodName}" is not a method name.`);
};
return getMethod;
// ^ Since the returned `getMethod` function uses `methods` array
// which contains functions that use the `_h` and `_w` variables,
// those variables don't get cleaned up when the function returns.
// Hence we have a saved state mimicking object fields.
// `getMethod` and functions in `methods` array are called
// Closures, because they kinda enclose local variables and
// keep them from being cleaned up.
};
const myRectangleObject = Rectangle(2, 3);
// `myRectangleObject` is actually a function returned
// by `Rectangle` which is a Higher Order Function (HOF).
// A HOF is a function that either takes functions
// as arguments or returns a function.
// In our case, the `Rectangle` HOF returns another
// HOF `getMethod` since it also returns a function
// upon calling. See below for demonstration.
console.log(myRectangleObject("getArea")()); // 6
myRectangleObject("setH")(4); // sets _h to 4
// ^ The first call `("setH")` returns another function
// that has the ability to change the local variable `_h`.
// The second call `(4)` then changes the `_h` variable.
// This sequential passing of arguments is Currying.
myRectangleObject("setW")(5); // sets _w to 5
console.log(myRectangleObject("getArea")()); // 20
Confused much? 😀
Lets go through it once again, in steps.
-
Rectangle
takes two argumentsh
andw
and stores their values in local variables_h
and_w
. -
Rectangle
has a local 2D array calledmethods
. It contains a string in first column and an anonymous function in second column in each row. The string can be used as sort of an identifier for the function. -
Rectangle
returns a function calledgetMethod
. -
getMethod
takes a string as an argument and uses that to filter out a matching pair of string and anonymous function inmethods
2D array. If it finds such pair, then it returns the anonymous function from that pair. Otherwise, it raises an error. - Based on what anonymous function got returned from
getMethod
, we can perform different operations on local variables_h
and_w
such as setting different values and getting their product i.e. area etc.. We can do this magic because the anonymous function that got returned and the local variables are in the same lexical scope.
I hope this has broadened (confused) your horizons a lot. 😀
And for the bonus, below is a snippet of the same concept implemented in Scheme Programming Language which is a dialect of Lisp.
Scheme Code: (Online Demo Link)
; OOP in a Functional way
; Online Demo: https://repl.it/repls/CalmSmoothClasslibrary
(define (Rectangle h w)
(define _h h)
(define _w w)
(define methods (list
(cons "getH" (lambda () _h))
(cons "getW" (lambda () _w))
(cons "getArea" (lambda () (* _h _w)))
(cons "setH" (lambda (h) (set! _h h) getMethod))
(cons "setW" (lambda (w) (set! _w w) getMethod))))
(define (getMethod methodName)
(cdr (car (filter
(lambda (n) (equal? (car n) methodName))
methods))))
getMethod)
(define myRectangle (Rectangle 2 3))
(print ((myRectangle "getArea"))) ; 6
((myRectangle "setH") 4) ; sets _h to 4
((myRectangle "setW") 5) ; sets _w to 5
(print ((myRectangle "getArea"))) ; 20
And that's it for now. Thank you.
Take Care && Good Bye. 😊
Top comments (0)