Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New syntax (probably again) #136

Closed
MaxGraey opened this issue Mar 18, 2019 · 26 comments
Closed

New syntax (probably again) #136

MaxGraey opened this issue Mar 18, 2019 · 26 comments

Comments

@MaxGraey
Copy link

MaxGraey commented Mar 18, 2019

Example of current proposal:

const getLength = vector => {
  case (vector) {
    when { x, y, z } ->
      return Math.sqrt(x ** 2 + y ** 2 + z ** 2)
    when { x, y } ->
      return Math.sqrt(x ** 2 + y ** 2)
    when [...etc] ->
      return vector.length
  }
}

New proposed syntax:

const getLength = vector => 
  do (vector) {
    ({ x, y, z }) -> Math.sqrt(x ** 2 + y ** 2 + z ** 2),
    ({ x, y }) -> Math.sqrt(x ** 2 + y ** 2),
    () -> vector.length, // or _ ->
  }

Motivation:

  1. Avoid switch/case keywords and "when".
  2. Maximal reusing already existing compiler's parsing job
  3. Use expression instead statement for matching which allow eliminate repeating return for each case and also allow reusing matched result later.
  4. do already reserved keyword. Existing parser expect do {, so then it come across to do ( it could try start parsing this differently. Also matching have many similarities with single iterated Dijkstra's Guarded loop and relate to do expressions proposal

Thoughts?

PS More examples:

const shallowClone = x =>
  do (x?.constructor) {
    String -> x + '',
    Array  -> x.slice(),
    Object -> ({ ...x }),
    Date   -> new Date(x.getTime()),
    _      -> x,
  }
const fib = n =>
   do (n) {
      0    -> throw new RangeError('zero is not possible'),
      1, 2 -> 1,
      3    -> 2,
      _    -> fib(n - 1) + fib(n - 2),
   }
const fizzbuzz = n => { 
  console.log(
    do (n % 3, n % 5) {
      (0, 0) -> 'FizzBuzz',
      (0, _) -> 'Fizz',
      (_, 0) -> 'Buzz',
      (_, _) -> n,
    }
  )
}

worst examples with existing proposal:

switch (y) {
  case 0: case 1: case (x) {
    when 1 -> { z = x }
  }
  break
}

switch (y1) {
  case 0: case (x1) {
    when 1 -> 
      return x1
    when 2 -> 
      return y1
  }
}

same examples with new syntax:

switch (y) {
  case 0: case 1: do (x) {
     1 -> { z = x },
  }
  break
}

switch (y) {
  case 0: return do (x1) {
     1 -> x1,
     2 -> y1,
  }
}

Using as IIFE

const result = (do (type) {
  'cdf' -> (x, mean, std) => 0.5 * erfc(-(x - mean) / (std * Math.sqrt(2))),
  'pdf' -> (x, mean, std) => (
    Math.exp(-Math.pow(x - mean, 2) / 2)) / (std * Math.sqrt(2 * Math.PI)
  ),
  _ -> () => {},
})(0.5, 0, 0.33)
@FireyFly
Copy link

There's already #116 about how to meet desires for both blocks and expressions as case bodies (well, specifically for how to re-introduce the expression case).

Making the cases syntactically identical to arrow functions might be a bit confusing since you wouldn't be able to extract the case out into a function and refer to it by identifier. I kinda like the parameter-list-style syntax for the pattern though..

@MaxGraey
Copy link
Author

Yeah, arrow syntax could be conflict with declared functions inside case body, so I switching to ->

@chicoxyzzy
Copy link
Member

In current proposal case is a statement since pattern matching not necessarily should return a value. You proposal changes pattern matching semantics by replacing statement with expression.

@MaxGraey
Copy link
Author

yes, I mentioned about replacing statement with expression. I don't see any problems with that. Semantics with expression in JavaScript don't require explicitly return some value and just automatically return undefined

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Mar 21, 2019

I'll elaborate.

I don't see how your proposal helps in

Maximal reusing already existing compiler's parsing job

In fact parser doesn't know syntax from your proposal at all. Also it interferes with do expressions proposal.

Use expression instead statement for matching which allow eliminate repeating return for each case and also allow reusing matched result later.

Omitting return is valid in do expression (JSX example from motivating examples).
Is it possible to have block after -> in your proposal? Current proposal lets you do any side effects. If so, should do expression propagate to blocks in right hand side and return result of latest expression in block or should block in RHS return undefined?

@MaxGraey
Copy link
Author

MaxGraey commented Mar 21, 2019

In fact parser doesn't know syntax from your proposal at all. Also it interferes with do expressions proposal.

Look at this part:

.. {
  ({ x, y, z }) -> Math.sqrt(x ** 2 + y ** 2 + z ** 2),
}

It pretty the same as:

{
  '0': ({ x, y, z }) => Math.sqrt(x ** 2 + y ** 2 + z ** 2),
}

Which already valid and parsed. Of course do (expr) { require additional effort.
Of course this tokenized as DFG entity, not as object.

Is it possible to have block after -> in your proposal

Yes. Semantics in -> the same as arrow function.

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Mar 21, 2019

Also

const getLength = vector => 
  do (vector) {
    ({ x, y, z }) -> Math.sqrt(x ** 2 + y ** 2 + z ** 2), // comma operator?
    ({ x, y }) -> Math.sqrt(x ** 2 + y ** 2),
    () -> vector.length,
  }

@MaxGraey
Copy link
Author

comma is operator the same as when just put in the end instead front as in case with when

@chicoxyzzy
Copy link
Member

Could ; be used instead to remove ambiguity?

@MaxGraey
Copy link
Author

MaxGraey commented Mar 21, 2019

yes, I think ; could be used instead ,. But ; usually optional in js. so I think , still little better for this case

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Mar 21, 2019

Yes, it's the same issue as in tc39/proposal-class-fields#7 (comment). Still, using ; is much better than , overloading inside do body (and only in case when do has (expression) before its body).

@MaxGraey
Copy link
Author

MaxGraey commented Mar 23, 2019

The comma is operator and can be inside expressions. The semicolon is not an operator and cannot be used inside an expression. It is used as part of JavaScript syntax to mark the end of an expression that is being treated as a statement and using as terminator instead \n. Of course current in current case we use block which is set of statements and could use commas or semicolons.

@zkat
Copy link
Collaborator

zkat commented Mar 27, 2019

I'm inclined to do this the way #116 proposes tbh.

@MaxGraey
Copy link
Author

MaxGraey commented Mar 27, 2019

@zkat but what about switching from case to do? See this example:

let a = ...
switch (b) {
    case (a): case (a) {
       when 1 -> ...
       whrn 2 -> ...
    }
}

case (a) inside switch block also valid and could cause to parsing problem (or will require back tracking) and human confusion

@chicoxyzzy
Copy link
Member

Of course current in current case we use block which is set of statements and could use commas or semicolons

I still don't agree with that because semantically comma operator has completely another meaning than semicolon and comma usage in your examples is error prone and unintuitive IMO.

@chicoxyzzy
Copy link
Member

I also prefer #116 though example from @MaxGraey example is ambiguous indeed

@dead-claudia
Copy link

dead-claudia commented Mar 28, 2019

@chicoxyzzy @MaxGraey case is not ambiguous from a technical perspective, and it's relatively easy to disambiguate: just look for a : or a { after parsing parenthesized case heads. Edit: And it doesn't require backtracking, either. It only requires a single token of lookahead.

  • case ( expr ) - In both cases, expr is a RHS expression, so it's parsed identically. This stands in contrast to async arrow functions vs function calls, since async ( { foo = 1 } ) ; is not valid, but async ( { foo = 1 } ) => expr is.
  • case ( expr ) : - A case label is of this form.
  • case ( expr ) { - A case expression starts with this form.
  • case expr : - Also a case label.
  • case expr { - Syntactically invalid - case expressions' conditions must be enclosed in parentheses.

(If it were ambiguous, it would've been caught by now and nobody would still be pushing it forward.)

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Mar 28, 2019

Sorry, by ambiguity I mean that it's not readable enough when combined with case clause from switch statement as in example above. It's not parsable by humans nor machines, so probably I chose the wrong word 🙂

@dead-claudia
Copy link

I did understand the "visually ambiguous" bit - I was just responding to the "technically ambiguous" claim by showing it was not.

Visually, I'm not convinced it's as ambiguous as you're making it seem, simply because it lacks that colon. Missing the colon before anything other than an open curly brace is already enough to get runtimes to yell at you if you're not already using a linter shouting even louder, so I find it unlikely anyone is going to forget that. A linter could warn about using a case statement within a switch/case since it's uncommon and potentially confusing, but this lies in a similar area as bitwise operators vs logical operators IMHO - if you're used to the syntax and precedence rules and aren't afraid to mix them both, you'll know it's different (and be less likely to make the mistake), but if you're not, you may benefit from such a lint rule.

@FireyFly
Copy link

I imagine replacing case with do would be similarly problematic if/when do expressions happen--and overload the do keyword further with different meanings.

@MaxGraey
Copy link
Author

MaxGraey commented Mar 28, 2019

@FireyFly How this could conflict with do expressions proposal? do expressions expect do { during parsing, but this proposal provide do ( which could determine earlier even case (x): vs case (x) {.

@FireyFly
Copy link

@MaxGraey I meant that it seems equally prone to confusion for humans as with case, and also that it would mean the do keyword has very different meaning in different contexts (do-while loops, do expressions, do pattern-matching). They seem equally (un)problematic from a formal parsing point-of-view, so I'm not concerned about that.

@MaxGraey
Copy link
Author

MaxGraey commented Mar 28, 2019

Unfortunately, the "case" also does not greatly correct the situation. Ideally, it would use the "match" keyword, but reserving this new keyword would lead to far more non-semantics conflicts.

@tloriato
Copy link

tloriato commented May 8, 2019

I don't think it isn't even worth talking about reserving a new keyword.

Between this and #116, I found this syntax cleaner and would honestly use it without problems. The motivations are clear and the fact that do ( can easily be caught during parsing is great.

@dead-claudia
Copy link

@tloriato It's easy, but that's not quite where you would draw the distinction. It would work more like case-within-switch, requiring a nearly identical disambiguation step to separate it from do ... while. The only three differences are in the initial keyword (do vs case), the specific token itself used to branch with ({ vs while), and where it needs to be checked (all statement expressions vs only in the immediate block of switch/case bodies). Performance considerations would also remain virtually identical.

  • do ( expr ) { - A pattern-matching do expression, where expr is the condition.
  • do ( expr ) ; - A do ... while statement, where ( expr ) is the statement body.
  • do ( expr ) <LineTerminator> while - A do ... while statement, where ( expr ) is the statement body.
  • do expr { - Invalid.
  • do expr ; - A do ... while statement, where ( expr ) is the statement body.
  • do expr <LineTerminator> while - A do ... while statement, where ( expr ) is the statement body.

@zkat
Copy link
Collaborator

zkat commented Jun 14, 2019

I'm not willing to conflict with do expressions (as #136 (comment) pointed out)

@zkat zkat closed this as completed Jun 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants