0

How do you pass a switch statement as a function argument in Javascript?

I want to do this with Redux library:

const agendaPointsReducer = function (state = initialState, action) {
  return Object.assign({}, state, switch (action.type) {
    case OPEN_AGENDA_POINT: {
      state.modal_is_open = true,
      state.point_id = action.id
    }
    case CLOSE_AGENDA_POINT: {
      state.modal_is_open = false
    }
  })
}

But I get an unexpected token error. I know this could be possible in Coffeescript, is there a way to do this in Javascript es6?

Aurimas
  • 2,577
  • 5
  • 26
  • 37
  • What do you mean? How? – Aurimas Feb 26 '17 at 22:42
  • What is the point of passing a side effect as a function argument? Even if `switch` statements were expressions, the one in the example would result in `undefined` – Balázs Édes Feb 26 '17 at 22:48
  • You can't pass a switch statement. You have to wrap it in a function. But, `Object.assign()` is expecting you to pass it an object with properties that it will copy. So, you have to pass an object there. – jfriend00 Feb 26 '17 at 22:54
  • Has your question been answered now? If so, please select the best answer and click the green checkmark next to it to indicate to the community that your question has been answered. – jfriend00 Mar 24 '17 at 04:25

6 Answers6

3

One possibility is to wrap it into a function like this:

const agendaPointsReducer = function (state = initialState, action) {
  return Object.assign({}, state, (() => {
    switch (action.type) {
      case OPEN_AGENDA_POINT: {
        state.modal_is_open = true,
        state.point_id = action.id
      }
      case CLOSE_AGENDA_POINT: {
        state.modal_is_open = false
      }
    }
  })())
}
daniel_franz
  • 111
  • 4
  • Looks interesting, will try this out – Aurimas Feb 26 '17 at 22:47
  • I think a `return` statement is needed before each object! – ibrahim mahrir Feb 26 '17 at 22:47
  • Just used the original code where no `break` or `return` were given. – daniel_franz Feb 26 '17 at 22:49
  • Plus, there is no need for passing the parametter `state` it's already defined inside the anonymous function! – ibrahim mahrir Feb 26 '17 at 22:49
  • Uhhh, `Object.assign()` is expecting an object with properties and the properties will be copied. It would be a lot clearer to just create the desired object BEFORE you call `Object.assign()` and then just pass that object. This is really hard to follow code (if it even works). – jfriend00 Feb 26 '17 at 22:57
  • you're completely right. i think it should be working, but it is really hard to get. – daniel_franz Feb 26 '17 at 22:59
  • I still don't see how this can possibly be the right way to do this. You are modifying the `state` object which is a side-effect. You are passing `undefined` to `Object.assign()` as the third argument which is just wrong. It might accidentally work because of the side effect, but it's certainly not the right way to do things. – jfriend00 Feb 27 '17 at 00:54
  • Can someone provide an example with correct code and correct redux state handling? @jfriend00 – Aurimas Feb 28 '17 at 14:03
2

switch statements are not expressions in Javascript. In Coffeescript, everything is an expression (a la Ruby). Due to this, there is not a way to do this. Further, in the example you have just shown, there is no reason to, as your switch statement is just modifying the state variable which is already being passed.

Jim Deville
  • 10,632
  • 1
  • 37
  • 47
1

So, Object.assign() copies properties from the objects you pass it onto the destination object. So, what you need to pass it is objects with properties that it can copy from.

If what you're trying to do is to create an object that has a property based on the current execution of a switch statement (which is my guess for what you're trying to do), then I'd suggest you just create the source object before calling Object.assign() and pass that object to it.

Doing it the way I show here will avoid any side effects that would change the passed in object too (that's why we set the properties in the switch statement on a local object, not on the state object):

const agendaPointsReducer = function (state = initialState, action) {
    let srcObj = {};
    switch (action.type) {
        case OPEN_AGENDA_POINT:
            srcObj.modal_is_open = true;
            srcObj.point_id = action.id;
            break;
        case CLOSE_AGENDA_POINT:
            srcObj.modal_is_open = false;
            break;
    }

    return Object.assign({}, state, srcObj);
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

I think what you might really want to do is something like this:

const agendaPointsReducer = function (state = initialState, action) {
  switch (action.type) {
    case OPEN_AGENDA_POINT: {
      return {
        ...state,
        modal_is_open: true,
        point_id: action.id
      };
    }
    case CLOSE_AGENDA_POINT: {
      return {
        ...state,
        modal_is_open: false,
      };
    }
  }
}
daniel_franz
  • 111
  • 4
1

Additionally to the given answers specific to your question I want to provide you a more general answer.

To pass a switch statement to a function you need to transform it into an expression. There are two distinct ways to achieve this.

Wrap switch in a function

const apply = f => x => f(x);

const enumerate = apply(x => {
  switch (x.constructor) {
    case Number: {
      return inc(x);
    }

    case String: {
      return succ(x);
    }

    default: throw TypeError();
  }
});

const inc = x => x + 1;

const succ = x => String.fromCharCode(x.charCodeAt(0) + 1);

console.log("enum 1:", enumerate(1)); // 2

console.log("enum a:", enumerate("a")); // "b"

With this approach I use apply as a little auxiliary function. It makes the calling code more readable, e.g. Object.assign({}, state, apply(x => {/* switch statement */}) (action.type))

Express switch itself as a function

const caseOf = (...pairs) => x => {
  let g;

  pairs.some(([guard, f], i) => guard(x) 
   ? (g = f, true)
   : false);

  return g(x);
}

const inc = x => x + 1;

const succ = x => String.fromCharCode(x.charCodeAt(0) + 1);

const enumerate = caseOf(
  [x => x.constructor === Number, x => x + 1],
  [x => x.constructor === String, x => String.fromCharCode(x.charCodeAt(0) + 1)]
);

console.log("enum 1:", enumerate(1));

console.log("enum a:", enumerate("a"));

caseOf is a normal function, a higher order function to be more precise. You can apply it wherever an expression is allowed.

Destructuring assignment

Destructuring assignment is an non-obvious advantage that both approaches have over normal switch statements. You can apply destructuring assignment without additional variables:

const apply = f => x => f(x);

const sqr = x => x * x

const xs = [2],
 ys = [];

console.log(
  sqr(apply(([x]) => { // <= destructuring assignment
    switch (x) {
      case undefined: return 0;
      default: return x;
    }
  }) (xs))
);

console.log(
  sqr(apply(([x]) => { // <= destructuring assignment
    switch (x) {
      case undefined: return 0;
      default: return x;
    }
  }) (ys))
);

const caseOf = (...pairs) => x => {
  let g;

  pairs.some(([guard, f], i) => guard(x) 
   ? (g = f, true)
   : false);

  return g(x);
}

const sqr = x => x * x;

const isAssigned = x => x !== undefined && x !== null;

const always = x => _ => x;

const xs = [5],
 ys = [];
 
console.log(
  caseOf(
    [([x]) => isAssigned(x), sqr], // <= destructuring assignment
    [always(true), always(0)] // default case
  ) (xs)
);

console.log(
  caseOf(
    [([x]) => isAssigned(x), sqr], // <= destructuring assignment
    [always(true), always(0)] // default case
  ) (ys)
);

I hope that helps.

1

I think the answers given by daniel_franz and jfriend00 are the best and most straightforward solutions. But it's still fun to think about alternatives :)

I had some fun experimenting with trying to store switch logic in a Map. It probably has many downsides (for one, you can't chain cases by omitting a break), and it makes stuff overly complicated, so I guess we'll have to look at it as just an exploration and not as a "this is how I would do it in a real project"...

The approach:

  • Create a Map with for each case, an entry
  • Each entry has the case as a key
  • Each entry has a function that returns the desired result as a value
  • For undefined cases, Map's getItem returns null, which we'll wrap in a function as well

The code:

const apply = f => typeof f === "function" ? f() : null;
const Switch = (input, ...entries) => apply(new Map(entries).get(input));

const types = {
  INCREMENT: "INCREMENT",
  DECREMENT: "DECREMENT",
  SQUARE: "SQUARE"
};


const myNumberReducer = function(state = { myNumber: 0 }, action = {}) {
  return Object.assign({}, state, Switch(action.type,
    [ types.INCREMENT, () => ({ myNumber: state.myNumber + 1 }) ],
    [ types.DECREMENT, () => ({ myNumber: state.myNumber - 1 }) ],
    [ types.SQUARE,    () => ({ myNumber: state.myNumber * state.myNumber }) ]
  ));
}


let lastState = myNumberReducer();
console.log(lastState); // myNumber: 0

lastState = myNumberReducer(lastState, { type: types.INCREMENT });
console.log(lastState); // myNumber: 1

lastState = myNumberReducer(lastState, { type: types.INCREMENT });
lastState = myNumberReducer(lastState, { type: types.SQUARE });
console.log(lastState); // myNumber: 4
Community
  • 1
  • 1
user3297291
  • 22,592
  • 4
  • 29
  • 45