Proposal: switch expressions

David Koblas david at koblas.com
Thu Feb 28 14:06:05 UTC 2019


Naveen,

Thanks for your observation.  The example that I gave might have been 
too simplistic, here's a more complete example:

```

switch (animal) { case Animal.DOG, Animal.CAT => { // larger block 
expression // which spans multiple lines return "dry food"; } case 
Animal.TIGER, Animal.LION, Animal.CHEETA => { // larger block expression 
// which spans multiple lines return "fresh meat"; } case 
Animal.ELEPHANT => "hay"; default => { throw new Error("Unsupported 
Animal"); }; }

```

While you give examples that would totally work.  Things that bother me 
about the approach are, when taken to something more complex than a 
quick value for value switch you end up with something that looks like this.

```

     function houseAnimal() {        // larger block expression        
// which spans multiple lines        return "dry food";    }    function 
wildCatFood() {      // larger block expression      // which spans 
multiple lines      return "fresh meat";    }    const cases = {      
[Animal.DOG]: houseAnimal,      [Animal.CAT]: houseAnimal,      
[Animal.LION]: wildCatFood,      [Animal.TIGER]: wildCatFood,      
[Animal.CHEETA]: wildCatFood,    }    const food = cases[animal] ? 
cases[animal]() : (() => {throw new Error("Unsuppored Animal")})();

```

As we all know once any language reaches a basic level of functionality 
anything is possible.  What I think is that JavaScript would benefit by 
having a cleaner approach.

On 2/28/19 4:37 AM, Naveen Chawla wrote:
> Isn't the best existing pattern an object literal?
>
> const
>     cases =
>         {
>              foo: ()=>1,
>              bar: ()=>3,
>              baz: ()=>6
>         }
>     ,
>     x =
>         cases[v] ?
>             cases[v]() :
>             99
> ;
>
> What does any proposal have that is better than this? With optional 
> chaining feature:
>
> const
>     x =
>         {
>              foo: ()=>1,
>              bar: ()=>3,
>              baz: ()=>6
>         }[v]?.()
>         ||
>         99
> ;
>
> Do let me know your thoughts guys
>
>
> On Thu, 28 Feb 2019 at 06:04 kai zhu <kaizhu256 at gmail.com 
> <mailto:kaizhu256 at gmail.com>> wrote:
>
>>     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
>>     <mailto: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 <mailto:contact at isiahmeadows.com>
>>>     www.isiahmeadows.com <http://www.isiahmeadows.com>
>>>
>>>     -----
>>>
>>>     Isiah Meadows
>>>     contact at isiahmeadows.com <mailto:contact at isiahmeadows.com>
>>>     www.isiahmeadows.com <http://www.isiahmeadows.com>
>>>
>>>
>>>     On Tue, Feb 26, 2019 at 1:34 PM David Koblas <david at koblas.com
>>>     <mailto: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
>>>>     <mailto: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
>>>>>     <mailto: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
>>>>>     <mailto: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
>>>>>>     <mailto: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 <mailto: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 <mailto:es-discuss at mozilla.org>
>>>>>>>     https://mail.mozilla.org/listinfo/es-discuss
>>>>>>     _______________________________________________
>>>>>>     es-discuss mailing list
>>>>>>     es-discuss at mozilla.org <mailto: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
>>>>     <mailto: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
>>>>     <mailto: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
>>>>>     <mailto: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 <mailto: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 <mailto:es-discuss at mozilla.org>
>>>>>>     https://mail.mozilla.org/listinfo/es-discuss
>>>>>     _______________________________________________
>>>>>     es-discuss mailing list
>>>>>     es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>>>>>     https://mail.mozilla.org/listinfo/es-discuss
>>>>
>>>>     _______________________________________________
>>>>     es-discuss mailing list
>>>>     es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>>>>     https://mail.mozilla.org/listinfo/es-discuss
>>>>
>>>>     _______________________________________________
>>>>     es-discuss mailing list
>>>>     es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>>>>     https://mail.mozilla.org/listinfo/es-discuss
>>     _______________________________________________
>>     es-discuss mailing list
>>     es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>>     https://mail.mozilla.org/listinfo/es-discuss
>
>     _______________________________________________
>     es-discuss mailing list
>     es-discuss at mozilla.org <mailto: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/d37b376f/attachment-0001.html>


More information about the es-discuss mailing list