Proposal: switch expressions

kai zhu kaizhu256 at gmail.com
Thu Feb 28 06:04:49 UTC 2019


> This is unmaintainable -- 
> 
>     const x = v === 'foo' ? 1 : v === 'bar' ? 3 : v === 'baz' ? 6 : 99;  
> 
i feel proposed switch-expressions are no more readable/maintainable than ternary-operators, if you follow jslint's style-guide.  i'll like to see more convincing evidence/use-case that they are better:

```javascript
/*jslint*/
"use strict";
const v = "foo";
const x = (
    v === "foo"
    ? 1
    : v === "bar"
    ? 3
    : v === "baz"
    ? 6
    : 99
);
```

here's another example from real-world production-code, where switch-expressions probably wouldn't help:

```javascript
$ node -e '
/*jslint devel*/
"use strict";
function renderRecent(date) {
/*
 * this function will render <date> to "xxx ago"
 */
    date = Math.ceil((Date.now() - new Date(date).getTime()) * 0.0001) * 10;
    return (
        !Number.isFinite(date)
        ? ""
        : date < 60
        ? date + " sec ago"
        : date < 3600
        ? Math.round(date / 60) + " min ago"
        : date < 86400
        ? Math.round(date / 3600) + " hr ago"
        : date < 129600
        ? "1 day ago"
        : Math.round(date / 86400) + " days ago"
    );
}

console.log(renderRecent(new Date().toISOString())); // "0 sec ago"
console.log(renderRecent("2019-02-28T05:32:00Z")); // "10 sec ago"
console.log(renderRecent("2019-02-28T05:27:30Z")); // "5 min ago"
console.log(renderRecent("2019-02-28T05:14:00Z")); // "18 min ago"
console.log(renderRecent("2019-02-28T03:27:00Z")); // "2 hr ago"
console.log(renderRecent("2019-02-12T05:27:00Z")); // "16 days ago"
console.log(renderRecent("2018-02-28T05:27:00Z")); // "365 days ago"
'

0 sec ago
10 sec ago
5 min ago
18 min ago
2 hr ago
16 days ago
365 days ago

$
```

> On 27 Feb 2019, at 13:12, David Koblas <david at koblas.com> wrote:
> 
> Just for folks who might be interested, added a babel-plugin to see what was involved in making this possible.
> 
> Pull request available here -- https://github.com/babel/babel/pull/9604
> 
> I'm sure I'm missing a bunch of details, but would be interested in some help in making this a bit more real.
> 
> Thanks
> 
> On 2/26/19 2:40 PM, Isiah Meadows wrote:
>> You're not alone in wanting pattern matching to be expression-based:
>> 
>> https://github.com/tc39/proposal-pattern-matching/issues/116
>> 
>> -----
>> 
>> Isiah Meadows
>> contact at isiahmeadows.com
>> www.isiahmeadows.com
>> 
>> -----
>> 
>> Isiah Meadows
>> contact at isiahmeadows.com
>> www.isiahmeadows.com
>> 
>> 
>> On Tue, Feb 26, 2019 at 1:34 PM David Koblas <david at koblas.com> wrote:
>>> Jordan,
>>> 
>>> Thanks for taking time to read and provide thoughts.
>>> 
>>> I just back and re-read the pattern matching proposal and it still fails on the basic requirement of being an Expression not a Statement.  The problem that I see and want to address is the need to have something that removes the need to chain trinary expressions together to have an Expression.
>>> 
>>> This is unmaintainable --
>>> 
>>>     const x = v === 'foo' ? 1 : v === 'bar' ? 3 : v === 'baz' ? 6 : 99;
>>> 
>>> This is maintainable, but is less than ideal:
>>> 
>>>    let x;
>>> 
>>>    switch (v) {
>>>    case "foo":
>>>      x = 1;
>>>      break;
>>>    case "bar":
>>>      x = 3;
>>>      break;
>>>    case "baz":
>>>      x = 6;
>>>      break;
>>>    default:
>>>      x = 99;
>>>      break;
>>>    }
>>> 
>>> Pattern matching does shorten the code, but you have a weird default case and also still end up with a loose variable and since pattern matching is a statement you still have a initially undefined variable.
>>> 
>>>    let x;
>>> 
>>>    case (v) {
>>>      when "foo" -> x = 1;
>>>      when "bar" -> x = 3;
>>>      when "baz" -> x = 6;
>>>      when v -> x = 99;
>>>    }
>>> 
>>> Let's try do expressions, I'll leave people's thoughts to themselves.
>>> 
>>>    const x = do {
>>>      if (v === "foo") { 1; }
>>>      else if (v === "bar") { 3; }
>>>      else if (v === "baz") { 6; }
>>>      else { 99; }
>>>    }
>>> 
>>> Or as another do expression variant:
>>> 
>>>    const x = do {
>>>      switch (v) {
>>>        case "foo": 1; break;
>>>        case "bar": 3; break;
>>>        case "baz": 6; break;
>>>        default: 99; break;
>>>      }
>>>    }
>>> 
>>> And as I'm thinking about switch expressions:
>>> 
>>>    const x = switch (v) {
>>>      case "foo" => 1;
>>>      case "bar" => 3;
>>>      case "baz" => 6;
>>>      default => 99;
>>>    }
>>> 
>>> What I really like is that it preserves all of the normal JavaScript syntax with the small change that a switch is allowed in an expression provided that all of the cases evaluate to expressions hence the use of the '=>' as an indicator.  Fundamentally this is a very basic concept where you have a state machine and need it switch based on the current state and evaluate to the new state.
>>> 
>>>    const nextState = switch (currentState) {
>>>       case ... =>
>>>    }
>>> 
>>> 
>>> 
>>> On 2/25/19 4:00 PM, Jordan Harband wrote:
>>> 
>>> Pattern Matching is still at stage 1; so there's not really any permanent decisions that have been made - the repo theoretically should contain rationales for decisions up to this point.
>>> 
>>> I can speak for myself (as "not a champion" of that proposal, just a fan) that any similarity to the reviled and terrible `switch` is something I'll be pushing back against - I want a replacement that lacks the footguns and pitfalls of `switch`, and that is easily teachable and googleable as a different, distinct thing.
>>> 
>>> On Mon, Feb 25, 2019 at 12:42 PM David Koblas <david at koblas.com> wrote:
>>>> Jordan,
>>>> 
>>>> One question that I have lingering from pattern matching is why is the syntax so different?  IMHO it is still a switch statement with a variation of the match on the case rather than a whole new construct.
>>>> 
>>>> Is there somewhere I can find a bit of discussion about the history of the syntax decisions?
>>>> 
>>>> --David
>>>> 
>>>> 
>>>> On Feb 25, 2019, at 12:33 PM, Jordan Harband <ljharb at gmail.com> wrote:
>>>> 
>>>> Additionally, https://github.com/tc39/proposal-pattern-matching - switch statements are something I hope we'll soon be able to relegate to the dustbin of history.
>>>> 
>>>> On Mon, Feb 25, 2019 at 6:01 AM David Koblas <david at koblas.com> wrote:
>>>>> I quite aware that it’s covered in do expressions. Personally I find do expressions non-JavaScript in style and it’s also not necessarily going to make it into the language.
>>>>> 
>>>>> Hence why I wanted to put out there the idea of switch expressions.
>>>>> 
>>>>> --David
>>>>> 
>>>>> 
>>>>> On Feb 25, 2019, at 5:28 AM, N. Oxer <blueshuk2 at gmail.com> wrote:
>>>>> 
>>>>> Hi,
>>>>> 
>>>>> This would be covered by do expressions. You could just do:
>>>>> 
>>>>> ```js
>>>>> const category = do {
>>>>>   switch (...) {
>>>>>     ...
>>>>>   };
>>>>> };
>>>>> ```
>>>>> 
>>>>> On Sun, Feb 24, 2019 at 10:42 AM David Koblas <david at koblas.com> wrote:
>>>>>> After looking at a bunch of code in our system noted that there are many
>>>>>> cases where our code base has a pattern similar to this:
>>>>>> 
>>>>>>      let category = data.category;
>>>>>> 
>>>>>>      if (category === undefined) {
>>>>>>        // Even if Tax is not enabled, we have defaults for incomeCode
>>>>>>        switch (session.merchant.settings.tax.incomeCode) {
>>>>>>          case TaxIncomeCode.RENTS_14:
>>>>>>            category = PaymentCategory.RENT;
>>>>>>            break;
>>>>>>          case TaxIncomeCode.INDEPENDENT_PERSONAL_SERVICE_17:
>>>>>>            category = PaymentCategory.SERVICES;
>>>>>>            break;
>>>>>>          case TaxIncomeCode.INDEPENDENT_PERSONAL_SERVICE_17:
>>>>>>            category = PaymentCategory.SERVICES;
>>>>>>            break;
>>>>>>        }
>>>>>>      }
>>>>>> 
>>>>>> I also bumped into a block of go code that also implemented similar
>>>>>> patterns, which really demonstrated to me that there while you could go
>>>>>> crazy with triary nesting there should be a better way.  Looked at the
>>>>>> pattern matching proposal and while could possibly help looked like it
>>>>>> was overkill for the typical use case that I'm seeing. The most relevant
>>>>>> example I noted was switch expressions from Java.  When applied to this
>>>>>> problem really create a simple result:
>>>>>> 
>>>>>>      const category = data.category || switch (setting.incomeCode) {
>>>>>>        case TaxIncomeCode.RENTS_14 => PaymentCategory.RENT;
>>>>>>        case TaxIncomeCode.ROYALTIES_COPYRIGHTS_12 =>
>>>>>> PaymentCategory.ROYALTIES;
>>>>>>        case TaxIncomeCode.INDEPENDENT_PERSONAL_SERVICE_17 =>
>>>>>> PaymentCategory.SERVICES;
>>>>>>        default => PaymentCategory.OTHER;
>>>>>>      }
>>>>>> 
>>>>>> Note; the instead of using the '->' as Java, continue to use => and with
>>>>>> the understanding that the right hand side is fundamentally function.
>>>>>> So similar things to this are natural, note this proposal should remove
>>>>>> "fall through" breaks and allow for multiple cases as such.
>>>>>> 
>>>>>>      const quarter = switch (foo) {
>>>>>>        case "Jan", "Feb", "Mar" => "Q1";
>>>>>>        case "Apr", "May", "Jun" => "Q2";
>>>>>>        case "Jul", "Aug", "Sep" => "Q3";
>>>>>>        case "Oct", "Nov", "Dec" => { return "Q4" };
>>>>>>        default => { throw new Error("Invalid Month") };
>>>>>>      }
>>>>>> 
>>>>>> Also compared this to the do expression proposal, it also provides a
>>>>>> substantial simplification, but in a way that is more consistent with
>>>>>> the existing language.  In one of their examples they provide an example
>>>>>> of the Redux reducer
>>>>>> https://redux.js.org/basics/reducers#splitting-reducers -- this would be
>>>>>> a switch expression implementation.
>>>>>> 
>>>>>>      function todoApp(state = initialState, action) => switch
>>>>>> (action.type) {
>>>>>>        case SET_VISIBILITY_FILTER => { ...state, visibilityFilter:
>>>>>> action.filter };
>>>>>>        case ADD_TODO => {
>>>>>>            ...state, todos: [
>>>>>>              ...state.todos,
>>>>>>              {
>>>>>>                text: action.text,
>>>>>>                completed: false
>>>>>>              }
>>>>>>            ]
>>>>>>          };
>>>>>>        case TOGGLE_TODO => {
>>>>>>            ...state,
>>>>>>            todos: state.todos.map((todo, index) => (index ===
>>>>>> action.index) ? { ...todo, completed: !todo.completed } : todo)
>>>>>>          };
>>>>>>        default => state;
>>>>>>      }
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> _______________________________________________
>>>>>> es-discuss mailing list
>>>>>> es-discuss at mozilla.org
>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> es-discuss at mozilla.org
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>> On 2/25/19 3:42 PM, David Koblas wrote:
>>> 
>>> Jordan,
>>> 
>>> One question that I have lingering from pattern matching is why is the syntax so different?  IMHO it is still a switch statement with a variation of the match on the case rather than a whole new construct.
>>> 
>>> Is there somewhere I can find a bit of discussion about the history of the syntax decisions?
>>> 
>>> --David
>>> 
>>> 
>>> On Feb 25, 2019, at 12:33 PM, Jordan Harband <ljharb at gmail.com> wrote:
>>> 
>>> Additionally, https://github.com/tc39/proposal-pattern-matching - switch statements are something I hope we'll soon be able to relegate to the dustbin of history.
>>> 
>>> On Mon, Feb 25, 2019 at 6:01 AM David Koblas <david at koblas.com> wrote:
>>>> I quite aware that it’s covered in do expressions. Personally I find do expressions non-JavaScript in style and it’s also not necessarily going to make it into the language.
>>>> 
>>>> Hence why I wanted to put out there the idea of switch expressions.
>>>> 
>>>> --David
>>>> 
>>>> 
>>>> On Feb 25, 2019, at 5:28 AM, N. Oxer <blueshuk2 at gmail.com> wrote:
>>>> 
>>>> Hi,
>>>> 
>>>> This would be covered by do expressions. You could just do:
>>>> 
>>>> ```js
>>>> const category = do {
>>>>   switch (...) {
>>>>     ...
>>>>   };
>>>> };
>>>> ```
>>>> 
>>>> On Sun, Feb 24, 2019 at 10:42 AM David Koblas <david at koblas.com> wrote:
>>>>> After looking at a bunch of code in our system noted that there are many
>>>>> cases where our code base has a pattern similar to this:
>>>>> 
>>>>>      let category = data.category;
>>>>> 
>>>>>      if (category === undefined) {
>>>>>        // Even if Tax is not enabled, we have defaults for incomeCode
>>>>>        switch (session.merchant.settings.tax.incomeCode) {
>>>>>          case TaxIncomeCode.RENTS_14:
>>>>>            category = PaymentCategory.RENT;
>>>>>            break;
>>>>>          case TaxIncomeCode.INDEPENDENT_PERSONAL_SERVICE_17:
>>>>>            category = PaymentCategory.SERVICES;
>>>>>            break;
>>>>>          case TaxIncomeCode.INDEPENDENT_PERSONAL_SERVICE_17:
>>>>>            category = PaymentCategory.SERVICES;
>>>>>            break;
>>>>>        }
>>>>>      }
>>>>> 
>>>>> I also bumped into a block of go code that also implemented similar
>>>>> patterns, which really demonstrated to me that there while you could go
>>>>> crazy with triary nesting there should be a better way.  Looked at the
>>>>> pattern matching proposal and while could possibly help looked like it
>>>>> was overkill for the typical use case that I'm seeing. The most relevant
>>>>> example I noted was switch expressions from Java.  When applied to this
>>>>> problem really create a simple result:
>>>>> 
>>>>>      const category = data.category || switch (setting.incomeCode) {
>>>>>        case TaxIncomeCode.RENTS_14 => PaymentCategory.RENT;
>>>>>        case TaxIncomeCode.ROYALTIES_COPYRIGHTS_12 =>
>>>>> PaymentCategory.ROYALTIES;
>>>>>        case TaxIncomeCode.INDEPENDENT_PERSONAL_SERVICE_17 =>
>>>>> PaymentCategory.SERVICES;
>>>>>        default => PaymentCategory.OTHER;
>>>>>      }
>>>>> 
>>>>> Note; the instead of using the '->' as Java, continue to use => and with
>>>>> the understanding that the right hand side is fundamentally function.
>>>>> So similar things to this are natural, note this proposal should remove
>>>>> "fall through" breaks and allow for multiple cases as such.
>>>>> 
>>>>>      const quarter = switch (foo) {
>>>>>        case "Jan", "Feb", "Mar" => "Q1";
>>>>>        case "Apr", "May", "Jun" => "Q2";
>>>>>        case "Jul", "Aug", "Sep" => "Q3";
>>>>>        case "Oct", "Nov", "Dec" => { return "Q4" };
>>>>>        default => { throw new Error("Invalid Month") };
>>>>>      }
>>>>> 
>>>>> Also compared this to the do expression proposal, it also provides a
>>>>> substantial simplification, but in a way that is more consistent with
>>>>> the existing language.  In one of their examples they provide an example
>>>>> of the Redux reducer
>>>>> https://redux.js.org/basics/reducers#splitting-reducers -- this would be
>>>>> a switch expression implementation.
>>>>> 
>>>>>      function todoApp(state = initialState, action) => switch
>>>>> (action.type) {
>>>>>        case SET_VISIBILITY_FILTER => { ...state, visibilityFilter:
>>>>> action.filter };
>>>>>        case ADD_TODO => {
>>>>>            ...state, todos: [
>>>>>              ...state.todos,
>>>>>              {
>>>>>                text: action.text,
>>>>>                completed: false
>>>>>              }
>>>>>            ]
>>>>>          };
>>>>>        case TOGGLE_TODO => {
>>>>>            ...state,
>>>>>            todos: state.todos.map((todo, index) => (index ===
>>>>> action.index) ? { ...todo, completed: !todo.completed } : todo)
>>>>>          };
>>>>>        default => state;
>>>>>      }
>>>>> 
>>>>> 
>>>>> 
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> es-discuss at mozilla.org
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>> _______________________________________________
>>>> es-discuss mailing list
>>>> es-discuss at mozilla.org
>>>> https://mail.mozilla.org/listinfo/es-discuss
>>> 
>>> _______________________________________________
>>> es-discuss mailing list
>>> es-discuss at mozilla.org
>>> https://mail.mozilla.org/listinfo/es-discuss
>>> 
>>> _______________________________________________
>>> es-discuss mailing list
>>> es-discuss at mozilla.org
>>> https://mail.mozilla.org/listinfo/es-discuss
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190228/c566848a/attachment-0001.html>


More information about the es-discuss mailing list