3

I have the example below of an impure function. Note the variable a is outside of the function scope is being changed. To get around it, one could clone the object inside the function and return a copy, but is that the right way?

What are some options to make the function transformObject pure?

var a = {
    a : 1,
    b : 2
};

function transformObject(obj) {
    var ref = obj;
    _.each(ref, function(val, index){
        ref[index] = val*2;
    });
    return ref;
}

s=JSON.stringify
$('#code').text(s(transformObject(a)))
$('#code2').text(s(a))

https://jsfiddle.net/br17kk2h/1/

filype
  • 8,034
  • 10
  • 40
  • 66

2 Answers2

3

A pure function follows three main principles:

The function has a single responsibility

The function should do only one thing.

The function has no side effects

The function does not change state outside its scope.

The function is referentially transparent

The function outputs the same value for the same inputs.

The next step is to explore the impact of these principles in the design of the function.

Your function already meets the first principle, although the name should be improved ;)

And it meets the second principle too, because it doesn't modify anything else outside its scope (its input parameters).

Unfortunately it doesn't meet the third principle:

var a = {
  a : 1,
  b : 2
};

var b = transformObject(a);//b = {2, 4}

var c = transformObject(a);//c = {4, 8}

Same inputs (a) but different outputs. That violates the third principle.

Referentially transparent functions needs immutable data.

A good answer has already been posted by Nina Scholz:

function transformObject(a) {
  var b = {};
  Object.keys(a).forEach(function (k) { b[k] = a[k] * 2; });
  return b;
}

But it only works because the input object does not contain any other object nested in it.

Right now there are some good libraries that gives you immutable structures (like ImmutableJS).

A simple example using ImmutableJS:

describe('a List', () => {

    function addMovie(currentState, movie) {
        return currentState.push(movie);
    }

    it('is immutable', () => {

        let state = List.of('Avengers', 'Antman');
        let nextState = addMovie(state, 'Superman');

        expect(nextState).to.equal(List.of(
            'Avengers',
            'Antman',
            'Superman'
        ));

        expect(state).to.equal(List.of(
            'Avengers',
            'Antman'
        ));
    });

    it('can be updated and returns another immutable List', () => {

        let state = List.of(1,2,3,4);

        let nextState = state.map(value => value*2);

        expect(nextState).to.equal(List.of(2,4,6,8));
        expect(state).to.equal(List.of(1,2,3,4));
    });
});

You can read more about immutable API here

Community
  • 1
  • 1
jfcorugedo
  • 9,793
  • 8
  • 39
  • 47
  • 1
    Maybe it violates rule 2 as well. The side effect is that variable a changes. Thanks for the reference on immutable Js. Do you mind writing a code example using immutable js? – filype Apr 29 '16 at 13:42
  • I'm not sure the third rule is violated for the reason you give: *Same inputs (`a`) but different outputs.* By that reasoning, a function that returns its own input unmutated would be in violation of that rule if the input were modified between calls. It's the mutability of `a` that makes the snippet impure. – Corey Sep 13 '18 at 14:47
  • To be certain, the function is not referentially transparent, but the reason is because you can't replace the call with its output and be sure that the behavior of our code won't change. – Corey Sep 13 '18 at 16:08
1

Change the object:

function transformObject(a) {   
    Object.keys(a).forEach(function (k) { a[k] *= 2; });
}

var a = {
        a : 1,
        b : 2
    };
transformObject(a);  
document.write('<pre>' + JSON.stringify(a, 0, 4) + '</pre>');

Keep the object and return a new object with the result:

function transformObject(a) {
    var b = {};
    Object.keys(a).forEach(function (k) { b[k] = a[k] * 2; });
    return b;
}

var a = {
        a : 1,
        b : 2
    },
    b = transformObject(a);  
document.write('<pre>' + JSON.stringify(a, 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(b, 0, 4) + '</pre>');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • So which of those approaches is the preferred one? Try giving only one answer :-) – Bergi Oct 23 '15 at 10:16
  • OP was asking for the right way to make this function pure. Now you gave two answers, one wrong and one correct, and I don't know whether to up- or downvote or both :-) – Bergi Oct 23 '15 at 10:21