tc39 / proposal-do-expressions

Proposal for `do` expressions
MIT License
1.11k stars 14 forks source link

Do Expression with Alternative Syntax - Immediately Executed Arrow Functions #74

Open EdSaleh opened 3 years ago

EdSaleh commented 3 years ago

Introduction

Hello,

I had an idea to allow returns from statements and decided to do a research and found Do Expression proposal very similar and more complex than my idea.

My idea is basically to create a new keyword syntax as a shortcut for an executed arrow function. It behaves same as arrow function.

Examples:

const 
 a=1,
 b = do if(a) return 1; else return 0;;

Transpilation:

const 
 a=1,
 b = (()=> {if(a) return 1; else return 2})();

We can also use alternative names or keywords for this new feature.

Examples:

const 
 a=1,
 b = -> if(a) return 1; else return 0;;
const 
 a=1,
 b = => if(a) return 1; else return 0;;

Or

const 
 a=1,
 b = =>{ if(a) return 1; else return 0;};

We might call => syntax a Immediately Executed Arrow Function

We can even pass parameters as an object:

const 
 a=1,
 b = {value:a} => if(value) return value; else return 0;

Match Example:

const a=1,b=2;
const c = 
    {a,b}=> {
        if(a) return a; 
        else if(b) return b; 
        else return 0;
    }
console.log(c)

Pipe and Chaining Example

Consecutive chains inherit the single parameter of single field object of parent chain, each assigning return value to that single field object property and passing that as parameters to next chain. We can enforce rule for my proposal to only have one object with one field as the parameter, that field can be be assigned as any object of course.

const value=1;
const c = {value} 
    => value+1
    => value+1
    => value+2
console.log(c) //5

Alternative Syntax:

const c = value
    -> value+1
    -> value+1
    -> value+2
console.log(c) //5

We can use for chaining ==> syntax to avoid specifying the parameter with the method: {value} ==> Filter ==> Map = {value} => Filter(value) => Map(value)

We can of course discuss the syntax, my proposal has 3 suggestions: =>, ->, do. The reason I prefer => is because my method is actually based on anonymous method, just immediately executed, and allowing flexibility.

Thank you,

theScottyJam commented 3 years ago

This is a syntax shorthand for IIFEs, correct? This kind of idea has been proposed a handful of times, and while it solves many of the same problems that the current proposal solves, it doesn't solve them all. You can find conversations related to this kind of idea here and here.

Additionally, I'm not sure this specific idea is as simple as you think it is. For example, you still have to figure out what to do about edge cases, like if-without-else, etc, just like the current proposal. You also linked the "implicit return" issue here, saying this was a solution to that problem. I'm not sure I see how. All of the issues with implicit returns are also an issue with this syntax.

const result = do {
  f()
  g() // Confusingly, g() is being implicitly returned.
}

const result = do if (true) {
  let x = y
  [2] // asi hazard, will get interpreted as y[2]
}

The main pushback I've seen for not wanting simply an iife shorthand, is the fact that iifes don't support using await, break/continue, return, etc against its parent function - those keywords would only apply to the iife.

Finally, a good thing to keep in mind, is that the pattern-matching proposal is planning on utilizing the do blocks for their blocks. It'll look something like this:

const result = match({ x: 2, w: 4 }) {
  when ({ w, x: 2 }) { // This is a do block, not a normal block
    let y = 3
    w + y
  }
}
console.log(result) // 7

We need to make sure we provide some user-friendly solution for pattern matching constructs to be able to execute a block of code in an expression position.

I too think the do block proposal in its current state is pretty complicated, and I'm all for trying to find ways to simplify it down. I've tried to propose an IIFE shorthand myself at one point, and received some of this information as feedback.

EdSaleh commented 3 years ago

Hi Scot,

Thank you for your reply. My proposal doesn't need to worry about implicit return. I'm very against it that I had my proposal for rust lint approved to add a role that allows disabling it.

My proposal is just a syntax suger for executed anonymous function (()=>{*function body*})(). So instead of that form, we just do => {*function body*} or {value} => {*function body with value object fields*} or do {*function body*} to achieve the same thing. It's not a new statement, just a syntax suger with a regular function.

Thank you

EdSaleh commented 3 years ago

Implicit return if it would come to JavaScript, should be generally added to all functions in JavaScript, not just one statement. Using regular functions to implement do expression would allow us to not worry about implementing implicit return now.

ljharb commented 3 years ago

It already exists in JavaScript, in concise arrow bodies, and also in eval.

Functions can’t be used for do expressions because they need to be able to control the surrounding function with return/await/yield.

EdSaleh commented 3 years ago

I think my proposal could be used for both do statement and match statement, using the form with a parameter {value}=>{*if/switch(value.)...}

EdSaleh commented 3 years ago

Hi Jordan,

Thank you for reply. My method is able to get the sorounding state block since it's an arrow function, but it won't be able to use return or yeild of sorounding state block.

Thank you

EdSaleh commented 3 years ago

Yes, it it's possible to do my method in JavaScript now, but I'm proposing a shortcut.

EdSaleh commented 3 years ago

You can always return a state from my proposed method that would allow controling yeild/return of outer state function though.

theScottyJam commented 3 years ago

Sorry - I didn't look close enough, you're right that you were using the return keyword and not doing implicit return, so yes, you don't have any issues there.

You can always return a state from my proposed method that would allow controlling yeild/return of outer state function though.

This can be cumbersome to do though. Compare the following:

// Current proposal
const result = do {
  const condition = ....
  if (condition) await f()
  g()
}

// To achieve a similar behavior in your proposal
const result = await do {
  const condition = ....
  if (condition) return f()
  return g()
}

// Note that we're needlessly awaiting g() as well.
// To achieve the same effect as the original proposal while
// not awaiting syncrounous values, we would have to do this:
let stuff = do {
  const condition = ....
  if (condition) return { promise: f() }
  return { result: g() }
}
const result = stuff.promise ? await stuff.promise : stuff.result

It's suspected that this will be a common issue, which is why people are a little wary of this sort of solution. Especially with returning from a function - trying to pipe a return value all the way down through multiple layers of match(), and doing this sort of unpacking could be very cumbersome.

I think my proposal could be used for both do statement and match statement, using the form with a parameter {value}=>{*if/switch(value.)...}

I'm not sure I fully understand your parameterized form. Is that just a shorthand for declaring local variables inside your iffe syntax?

ljharb commented 3 years ago

@EdSaleh its a requirement of some delegates to be able to await or yield a surrounding async/generator function, so if that’s not allowed by your proposal, then it’s a nonstarter.

theScottyJam commented 3 years ago

Alternatively, if you don't make it an iife shorthand, then you could have async/return/yield/etc within your shorthand syntax. You'll end up something very similar to this discussion #1.

That, together with requiring an explicit completion value marker (like discussed in #72) should give us something that's about as simple and intuitive as what you're proposing here.

EdSaleh commented 3 years ago

Thank you very much for replies.

The parameters work by mapping the single object fields value of my proposed method to its inner states. It allows only a one single object as a parameter.

Example:

const a=1,b=2;
const c = 
    {a,b}=> {
        if(a) return a; 
        else if(b) return b; 
        else return 0;
    }
console.log(c)

It's a simple, easy and less verbose shortcut.

If you decide to plan the statement block route for do rather than function block. You can use new keywords like break or new retrieve or done to exit and return from do block. Implicit return should a property of a whole programming language not one single statement of a programming language.

Thank you

Ontopic commented 3 years ago

I'm personally glad this proposal is alive again and discussing, but I think the main question should be if rethinking it now is simply gonna delay actual implementation (I think it's been stuck the longest I've personally experienced). Node by Chromium had it built in under a harmony flag, that is taken out. Babel is not moving it to basic packages, while moving far newer proposals up.

I love do expressions more than anything for some reason and it's the first thing I'll make sure is added in new repos whatever way possible. I followed all my node calls with a --harmony-do-expressions (at least till recently, now it 's taken out and quits node), install @babel/plugin-do-expressions and download Typescript pull request with do expressions. To be honest I only think Typescript has a chance of brining this in a "I can justify using it for production", as currently proposed, in a somewhat soonish fashion, let's not see what's gonna happen if we change the proposal up now.

What can we do to "push" for this proposal? I already force everyone that wants to read my code to learn about them, most start using them themselves and are intrigued. Don't really understand what's the holdup on the proposal. It was already in Node done by the Chromium v8 team two years ago...

Jack-Works commented 3 years ago

install @babel/plugin-do-expressions and download Typescript pull request with do expressions

Hi happy to see someone is really using that TS PR, but I want to notice you, babel plugin is implementing the older version of this proposal (doesn't support return, break and continue) and the TS version is missing some pieces (switch, etc).

EdSaleh commented 3 years ago

Pipe and Chaining Example: Consecutive chains inherit the single parameter of single field object of parent chain, each assigning return value to that single field object property and passing that as parameters to next chain

const value=1;
const c = {value} 
    => value+1
    => value+1
    => value+2
console.log(c) //5
ljharb commented 3 years ago

The difference between {x} => x and ({ x }) => x is far, far too subtle, so this syntax is also a nonstarter.

EdSaleh commented 3 years ago

We can of course discuss the syntax, my proposal has 3 suggestions: =>, ->, do. The reason I prefer => is because my method is actually based on anonymous method, just immediately executed, and allowing flexibility.

Alternative Syntax:

const c = value
    -> value+1
    -> value+1
    -> value+2
console.log(c) //5
ljharb commented 3 years ago

As I indicated here, the entire concept is a nonstarter - a proposal based on function boundaries will not achieve consensus.

Given that, the specific syntax isn't particularly important.

EdSaleh commented 3 years ago

Of course this proposal is open for any modifications and subjective discussion in regard to consensus. Thank you

ljharb commented 3 years ago

imo the modifications required would result in the current proposal.

EdSaleh commented 3 years ago

Yes, I like the ideas of the current proposal, except for the implicit return and that could be improved with creating a new keyword to break or exit or retrieve from do block. The reason I made this proposal is because it's simpler than current one, and would also allow matching, and piping.

ljharb commented 3 years ago

Matching and piping are separate proposals, and "would also allow" doesn't sound like "simpler" to me. The current proposal is simple.

Implicit return is likely inevitable, given that there does not exist any convention for any kind of interrupting flow control to provide a value besides yield or return or throw (none of which are options here), and nobody has yet come up with a keyword that would suffice. Either way, that can be discussed in #72.

EdSaleh commented 3 years ago

Ok, thank you.

EdSaleh commented 3 years ago

https://github.com/EdSaleh/proposal-immediately-executed-arrow-function-expression

theScottyJam commented 3 years ago

I'm not sure I understand your pipe syntax.

const c = value
    -> value+1
    -> value+1
    -> value+2
console.log(c) //5

This is pretty odd. From what it looks like, you can only put an identifier in the left-most position, and then each state of your "->" pipe will cause the value in your identifier to be modified? i.e. if I logged the value of "value" after I executed this, would it be 4 more than what it used to be? Normally, a pipeline operators would try not to modify anything - you can see the current proposal for the pipeline operator here. One variant would look something like this:

const c = value
  |> ^ + 1
  |> ^ + 1
  |> ^ + 2
console.log(c)

The "^" symbol is a topic variable, that holds the value of the result of the previous pipeline's completion value. It can, of course, take your "->" syntax inside the pipeline if you need a statement as an operand.

const result = y.z
  |> -> if (^) return 1; return 2;
  |> ^ + 1
EdSaleh commented 3 years ago

Each preceding item statement in chain acts as the parameter value for the next item statement in chain. So first item in the chain gets value parameter, then the next item in the chain gets the return from the previous item, but under the name 'value' as well. It's as if each item in the chain has a parameter 'value', equaling the return of the previous item in the chain.

EdSaleh commented 3 years ago

this

const c = value
    -> value+1
    -> value+1
    -> value+2
console.log(c) //5

equals:


const value1 = value;
const value2 = value1 -> value1+1
const value3 = value2 -> value2+1
 ....
console.log(c) //5
theScottyJam commented 3 years ago

So, would this not be allowed?

const c = obj.value
  -> ???? + 1 // What goes in the ????
EdSaleh commented 2 years ago

No it's not allowed since you need an object to be mapped to the immediately executable arrows function. {value: obj.value}->value+1 Or ({value: obj.value})->value+1 Or {obj}->obj.value+1 Or obj->obj.value+1 Or {obj}=>obj.value+1

EdSaleh commented 2 years ago

We can rename the proposal to Immediately Executable Arrow Function(s) or Immediately Invoked Arrow Function(s) or Immediately Invokable Arrow Function(s) since you can have multiple chain of functions in the same statement chain.

2A5F commented 2 years ago

I once assumed a syntax using ->

a->f()        // f(a)        f is ident
a->f(b)       // f(a, b)
a->[f](b)     // f(a, b)     f is expr

foo
  ->bar()
  ->baz()
// baz(bar(foo))

foo->bar { 
  // with tail block
}
// bar(foo, () => {})

as a substitute for pipelines, it can be used directly in existing functions I am worried about operator -> being occupied

Maybe we can use like

const a = do () => 1
EdSaleh commented 2 years ago

Hi 2A5F, That's actually good syntax, maybe start chain with do ()=> {} and use => for consecutive chain items do ()=> {} => {} ... Thank you

EdSaleh commented 2 years ago

Syntax '|> {}' could be used as well.