2
\$\begingroup\$

I'm trying to wrap my head around JavaScript Functional Programming.

I have created a very basic script that creates a new elements and appends them to the DOM.

If anyone could share any feedback, or great resources on the subject, that would be awesome.

Where does this style of programming lend itself best? Web Development, or more Software development? I understand that React uses this coding paradigm. I'm actually very comfortable using React - but, I'm wondering how it might be best utilised with vanilla JavaScript

Thanks!


// create a button that accepts text and a callback function

const createButton = (text, func) => {
    const button = document.createElement('button');
    const buttonText = document.createTextNode(text);
    button.appendChild(buttonText);

    button.addEventListener('click', () => {
        return func();
    });

    return button;
};

// create paragraph tag that accepts a text arg

const createText = (text) => {
    const pTag = document.createElement('p');
    const pTagText = document.createTextNode(text);
    pTag.appendChild(pTagText);

    return pTag;
};

// pure functions

const myList = [1, 2, 3];

const sayHello = (name) => {
    return `Hello, ${name}!`;
};

const addNums = (x, y) => {
    return x + y;
};

const simpleLog = () => {
    return 'This is a simple log';
};

const addToArray = (item) => {
    return [...myList, item];
}


// view - component that will get rendered to the DOM


const view = () => {
    const component = document.createElement('div');

    // create a button element that has a callback function that will append the returned value to the DOM

    component.appendChild(createButton('Hello', () => {
        return component.appendChild( createText( sayHello('Kristian') ) );
    }));

    component.appendChild(createButton('Add Numbers', () => {
        return component.appendChild( createText( addNums(3, 7) ) );
    }));

    component.appendChild( createButton('Log', simpleLog) );

    component.appendChild(createButton('Add to array', () => {
        console.log( `Original array: [${myList}] - New array: [${addToArray(4)}]`);
    }));

    return component;
};


// grab the el that you want to append the view to

const rootNode = document.getElementById('app');


// append view component to el

rootNode.appendChild(view());
\$\endgroup\$
  • 2
    \$\begingroup\$ So in "functional programming" you've decided to replace the normal function name(parameters) by const name = (parameters) =>. What's the point of that? It's the "new" thing? \$\endgroup\$ – KIKO Software Apr 30 at 19:12
  • 1
    \$\begingroup\$ Hi @WoodenChops; welcome to CR SE! I'm not much involved with javascript, so I'll let other folks actually answer your question. (I've found it illuminating to think of JS as "callback" rather than "functional" programming.) Most of your post looks fine. Can you update your title? It should focus on what your code does (and how, which is where you can mention "functional programming"). \$\endgroup\$ – ShapeOfMatter May 1 at 12:10
6
\$\begingroup\$

๐Ÿ“Œ Preface

"Best Practises" are very subjective, so your question cannot really be answered in that regard, but I will try to give you some tips nonetheless.

To lead into the rest of my answer I would say one of the most beginner-friendly resources for learning a functional style in javascript is this Medium article (without going into a lot of the nitty-gritty theoretical stuff). By reading this I think you will come to understand a lot of the ideas that come with functional programming.




๐Ÿ’โ€โ™€๏ธ Paradigm vs. Style

With that out of the way, let us start with a shocker: Javascript is not really a functional programming language. Yep, I said it. Javascript is more a procedural (or object-oriented) language (some newer features have allowed for more functional-ish style but it remains very procedural). But all that ofcourse does not mean you cannot apply a functional style, which I think is more the core of your question?

Presuming I did correctly interpret your question, let me paraphrase to you the first two sentences of the Wikipedia page about FP:

[...] functional programming is a programming paradigm where programs are constructed by applying and composing functions. It is a declarative programming paradigm [...], rather than a sequence of imperative statements [...].

This means that, when writing your programs, you do not tell the computer exactly what to do every step of the way (i.e. for (let i = 0; i < n; i++)) but rather you chunk a problem into smaller sub-problems, solve those, and compose a function that calls your smaller functions to solve the bigger problem. This leads me to my first tip. Javascript functions come in two forms: procedures, and lambda's. I am not going into the details that differentiate the two, but rather I would like to tell you about lambda's.




๐Ÿ’ก Tip 1: Embrace expressions

You seem to know javascript's lambda syntax, since you use the arrow notation (x => y), but you are using it in a procedural way. What I mean by that, is that you append a block to each lambda, as such:

const doSomething = (arg1, arg2) => { /*steps*/ };

This kind of defeats the purpose of the notation, and is basically another syntax for the procedural functions of JS:

function doSomething (arg1, arg2) { /*steps*/ }

In Javascript, x => y means a function that takes x and returns y. So a lot of your functions can be simplified. Let's change a couple to make it clear:

const sayHello = (name) => {
    return `Hello, ${name}!`;
};
// can be
const sayHello = name => `Hello ${name}!`;

and

const addNums = (x, y) => {
    return x + y;
};
// can be
const add = (x, y) => x + y;



๐Ÿ’ก Tip 2: Spice up your functions

But then what is the purpose of the lambda notation, and why is your code defeating its purpose? To put it simply: a lambda is a function that takes zero or one argument(s), maybe transforms it, and yields a result. Wikipedia's article about lambda calculus says the same, but with more jargon:

[...] all functions in the lambda calculus are anonymous functions, having no names. They only accept one input variable, with currying used to implement functions with several variables.

You use a lot of functions as procedures (sequence of steps) which you cannot have in FP, and you are using multiple parameter functions which are also disallowed (or rather just do not exist in FP). But, you might ask, how are you supposed to do anything with just one parameter and a single expression body? Let us take a snippet of your code and refactor it into a nice functional style:

// Original
const createButton = (text, func) => {
    const button = document.createElement('button');
    const buttonText = document.createTextNode(text);
    button.appendChild(buttonText);

    button.addEventListener('click', () => {
        return func();
    });

    return button;
};
// Functional alternative
// Some setup, notice the currying for multiple arguments.
const text = text => document.createTextNode(text);
const newButton = () => document.createElement("button");
const listen = event => handler => target => {
  // Javascript was not meant to be used purely functionally,
  // so we have to use a block with several statements to
  // mutate the target, and then return it (mutation will never
  // happen in functional programming, as you might know).
  target.addEventListener(event, handler);
  return target;
};
const append = child => target => {
  // Same story: we have to return the target after mutating it.
  target.append(child);
  return target;
};

// Here comes the true power of functional programming: composition
const onClick = listen("click");
const addLabel = label => append(text(label));
const sayHi = () => console.log("Hi!");
const sayBye = () => console.log("Bye.");
const button = label => handler =>
  addLabel(label)(
    onClick(handler)(
      newButton()));

const friendlyButton = button(":)")(sayHi);
const sadButton = button(":(")(sayBye);

// Try it out!
document.body.append(friendlyButton, sadButton);

Ok, giant code sample with weird and maybe hideous things. Let me explain it!

Firstly I would like to say that Javascript is a great language, but the DOM is not the best part to use FP for, because it's an amalgamation of mutation and imperativeness, which are the arch-enemies of FP. But let us do it anyways because it is fun!

The first statement of the refactor goes as follows:

const text = text => document.createTextNode(text);

which is essentially a function that, when called with an argument text will create a textNode with text being its text. Now this is a bit of a weird one, because we made a function that takes an argument, and then calls another function with that very argument, so we basically aliased it. Aliasing is the same as storing it in a variable with another name, so why not write it like this:

const text = document.createTextNode;

text is equal to document.createTextNode, so text("Hi!") is equal to document.createTextNode("Hi!").

Now JS is a bit weird (and full of side-effects), so we have to specify that the this of document.createElement is document (document.createElement.bind(document)). The peculiarities of Javascript's this go far beyond the purpose of this post so if you do not understand what that meant, just wrap it in a function like so: x => y(x);

The first real weirdness happens in the listen function:

const listen = event => handler => target => { /*...*/ };

What? A function that returns a function that returns a function that returns something else?! Yep, that is functional programming baby! But why would we do this? Because it allows for very easy composition. Notice how later on, we defined the function onClick, which is a composed listen:

const onClick = listen("click");
// This means the following:
const onClick = handler => target => target.addEventListener("click", handler);

So we made a function that always adds a click eventlistener to a target. We can take this a step further:

// Let's make a function that makes an element yell "OUCH!" on click!
const ouch = onClick(() => console.log("OUCH!"));
// This means the following:
const ouch = target => target.addEventListener("click", () => console.log("OUCH!"));

// Try it out!
const boxingDummy = ouch(addLabel("Don't hit me!")(newButton()));
document.body.append(boxingDummy);

I hope that made it clear why you would write programs in such a weird (coming from procedural programming) way. I would link you the wiki article but that is just unintelligble jibberish to me too.




๐Ÿ’ก Tip 3: Lose control

You seem to really rely on the imperativeness of Javascript to perform tasks in a set order, which does not fit well in a functional style -- most functional languages do not even allow it. The great functional programming evangelists have thought about this long and hard, and showed us how you do not need loops, if's or sequences to create complete and functional (in the literal sense) programs. Taking, for example, another function you wrote:

const view = () => {
    const component = document.createElement('div');

    // create a button element that has a callback function that will append the returned value to the DOM

    component.appendChild(createButton('Hello', () => {
        return component.appendChild( createText( sayHello('Kristian') ) );
    }));

    component.appendChild(createButton('Add Numbers', () => {
        return component.appendChild( createText( addNums(3, 7) ) );
    }));

    component.appendChild( createButton('Log', simpleLog) );

    component.appendChild(createButton('Add to array', () => {
        console.log( `Original array: [${myList}] - New array: [${addToArray(4)}]`);
    }));

    return component;
};

Pardon the joke, but this looks really WET... (as in, the opposite of DRY). You call component.appendChild(createButton(/*...*/)) a lot of times. Apart from that being a bit of a code smell, it also is a really great place to put in some FP magic. Analysing the abstract meaning of your code, let me write that out in a more functional style:

// Remember our previous functions?
// I made them nice and generic so they are
// reusable here!
const appendButton = label => action => append(button(label)(action));
const add = x => y => x + y;
const addFive = add(5);
const saySix = () => console.log(addFive(1));
const fold = (...functions) => value => functions.reduce(
  (result, fun) => fun(result),
  value);
const component = document.createElement("div");
const view = fold(
  appendButton("Greet")(sayHi),
  appendButton("What's five plus one?")(saySix)
  /* etc... */);

// Try it out!
document.body.append(view(component));

BAM! No blocks, no loops, no if's! Instead of a sequence of statements, we use a fold to abstract away a lot of the DOM boilerplate. Higher-order functions such as fold(left) are very important functions to keep in your arsenal. I suggest reading up on it a bit, if the article isn't too crytographic (there's a joke that says functional programmers tend to lose their ability to explain FP as soon as they understand, and I think that applies perfectly on most wiki pages).




Well that should be about it. To summarise:
  • Javascript DOM isn't the best place to apply FP (in my humble opinion), but go ahead and break the rules!
  • Don't get RSI: (x) => { return y; } is the same as x => y.
  • Procedures are so 1970; compose a function from functions to solve your problems (and most importantly: be able to reason about your solution).
  • Currying and higher-order functions are a functional programmer's best friends, conquer the world with them.
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Sometimes there is a wonderful interplay of the right question at the right time and the perfect balance of motivation and expertise of the respondent. This, what happens here, is a great example of such an interplay. Absolutely blown-away by your answer. Never even heard of Functional Programming and your answer just clicked. In my world there was Object-Oriented Programming and Procedural Programming. With the former being the "best" way. Where do you even start to understand programming on that level? With higher-order functions and currying? \$\endgroup\$ – Melvin Idema Apr 30 at 23:12
  • 1
    \$\begingroup\$ @MelvinIdema Thanks for the kind words. I was displeased by all of my imperative code because it didn't have the expressive power I wanted. I figured I couldn't be alone in thinking so. I quickly fell into a rabbit hole of computer science, and I just happened to come out of it having seen the light. To me, everything is composition. Subatomic particles make atoms, atoms make cells, cells make organs, organs make organisms... As I understand the world, everything is a function of smaller functions. Functional programming to me is the only next step in programming. \$\endgroup\$ – Maanlamp May 1 at 9:46

Your Answer

By clicking โ€œPost Your Answerโ€, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.