13

How is most "pure" way to manipulate DOM in script written in "functional" way.

For example if I simply need to change element width should I use typical syntax like:

document.querySelector(".class").style.width = ...

or write dedicated function, like:

function resize(el, w) {
    return el.style.width = w;
}

resize(document.querySelector(".class", 100));

I think, I understand general idea of functional programming but all examples I encountered are focused on manipulating numbers or strings. Manipulating DOM is considered side-effect so I'm wondering how to do it in correct way.

EDIT:

Updated code below, the question is, is iit functionall? If not I'd be gratefull for suggestion how to rewrite it.

"use strict";

const outerW = num => 100 * num + "%";
const innerW = num => 100 / num + "%";
const setW = (el, w) => el.style.width = w;

const mask = document.querySelector(".slider__slides__mask");
const slides = document.querySelectorAll(".slider__slide");

setW(mask, outerW(slides.length));
slides.forEach(function(item) {
    setW(item, innerW(slides.length));
});
przemekk
  • 351
  • 3
  • 10
  • 1
    When it comes to side effects in functional programming, it's good to try to keep all of your side effects in one place in your code. The typical syntax you suggest is fine, but what really matters is where you do it. – 4castle Feb 22 '17 at 20:17
  • You're missing the second argument to the function. – Barmar Feb 22 '17 at 20:26
  • There's no such thing as output in "pure" functional programming, because output is inherently a side effect. You have to break the paradigm to manipulate the DOM. – Barmar Feb 22 '17 at 20:27
  • You'll want to have a look at FRP – Bergi Feb 22 '17 at 20:31
  • The main idea behind functional programming is that there are no hidden side effects, that what the function does is clear from the declared inputs and outputs. So, I see no issue with `document.querySelector(".class").style.width = x` by itself (there are no hidden side effects in that statement). The issue would more be what that is contained in and whether this effect is obvious given the containing function's declared inputs and outputs. – jfriend00 Feb 22 '17 at 21:30

1 Answers1

23

Every meaningful program must eventually carry out some effects, whether it is in functional or imperative style. The idea of functional programming is to shift theses effects to the edge of a program, so that a large portion remains pure. This makes it much easier to reason about it.

But how can this be put to practice?

Make functions composable

Javascript functions are usually defined as multi-argument functions. Hence we can't defer their evaluation at the calling side:

// pure

const add = (x, y) => x + y;

const log = x => console.log(x);

// impure

log(add(2, 3));

It's the same with the imperative style:

let x = 2, y = 3;

// impure

console.log(x + y);

The consequence is that the pure computation (add) can not be separated from the effect (log). This isn't a big deal for this sketch, but as soon as your program grows bigger, these scattered effects impair its readability and comprehensibility.

To prevent this behavior functions must become composable, i.e. their last argument must be partially applicable:

// pure

const comp = (f, g) => x => f(g(x));

const add = x => y => x + y;

const log = x => console.log(x);

const addAndLog = comp(log, add(2));

// impure

addAndLog(3);

You might want to look into currying for more information.

Wrap effects in thunks

Using thunks we can move the effects even further to the edges. A thunk is just a function that expects no arguments and thus represents a deferred computation:

// pure

const comp = (f, g) => x => f(g(x));

const add = x => y => x + y;

const log = x => () => console.log(x);

const addAndLog = comp(log, add(2));

const eventuallyLog = addAndLog(3); // still pure

// impure (release the effect)

eventuallyLog(); // invoke the thunk

You might want to look into IO monads for more information.

An (almost) real world example

// pure

const on = (type, element) => f => {
  element.addEventListener(type, f, true);
  return () => element.removeEventListener(type, f, true);
}

const compose = (...fs) => x => fs.reduce((acc, f) => f(acc), x);

const countFrom = x => () => (x++, x);

const log = x => console.log(x);

const filter = pred => f => x => pred(x) ? f(x) : x;

const even = x => x % 2 === 0;

const concat = y => x => x + y;

const filterEven = filter(even);

const clickStream = on("click", document);

const computation =
 compose(countFrom(0), filterEven(compose(concat("!"), log)));

// impure (release the effects)

console.log("click on the section above");

clickStream(computation);

As a side effect (pun intended)

compose(countFrom(0), filterEven(compose(concat("!"), log)))

reads like plain English. I was recently told that this is not a desirable property. Well, I disagree.

Dmitri Zaitsev
  • 13,548
  • 11
  • 76
  • 110
  • Thank you for your answer. It was quite helpfull but I still don't enitrly get it. First of all, could you please explain me how does the filter function works, is it recursive? And secondly, can you look at updated code in my question? At the very beginning I need to change dimensions of two elements, am I doing it correctly? – przemekk Feb 26 '17 at 18:16
  • `filter` is the equivalent expression of the following `if` statement: `if (pred(x)) f(x); else x;`. It is not recursive. However, the statement doesn't make much sense. You must add a variable assignment (mutation) in order to do something meaningful with the result. In contrast, an expression can simply be passed to a function as an argument (no mutation). –  Feb 27 '17 at 09:06
  • @ftor nice one. really like the explanation of fp being a "shifting of effects" to the edge – htor Jan 30 '18 at 19:27
  • @ftor I know it is a nitpicking but technically your `compose` is not compose but `pipe`, compose works from right to left, great example otherwise. – Matus Dubrava Jun 15 '18 at 08:34
  • 1
    Putting `const log = x => console.log(x);` into the `// pure` section is misleading. Truly, that statement does not cause any effects. But! Purity in most of the cases is attributed to _functions_. And this `log` fn is totally impure, because it causes side-effects in the form of outputting to the console. This will puzzle a lot of padawans. – avalanche1 Feb 20 '21 at 08:55