Aurimas
Aurimas

Reputation: 2727

How to pass switch statement as function argument in Javascript ES6?

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?

Upvotes: 0

Views: 3738

Answers (6)

user3297291
user3297291

Reputation: 23397

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

Upvotes: 1

user6445533
user6445533

Reputation:

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.

Upvotes: 1

daniel_franz
daniel_franz

Reputation: 111

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,
      };
    }
  }
}

Upvotes: 1

jfriend00
jfriend00

Reputation: 708036

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);
}

Upvotes: 1

daniel_franz
daniel_franz

Reputation: 111

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
      }
    }
  })())
}

Upvotes: 3

Jim Deville
Jim Deville

Reputation: 10672

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.

Upvotes: 2

Related Questions