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

blockexpr discussion #2

Open
JanSharp opened this issue Nov 21, 2021 · 7 comments
Open

blockexpr discussion #2

JanSharp opened this issue Nov 21, 2021 · 7 comments

Comments

@JanSharp
Copy link
Owner

JanSharp commented Nov 21, 2021

I am planning on introducing a new expression which is basically just a statement list. You can think of it like

local foo = (function()
  print("hello world!")
  return 1
end)()
print(foo)

except that the syntax would be

local foo = do
  print("hello world!")
  return 1
end
print(foo)

Except that the return in this case returns from the entire function, not from the blockexpr.
(Refer to this issue for discussion about which keyword to use)

Honestly the fact that this needs a new keyword is a gigantic downside, but if we can find a decent keyword for it I consider it worth it.

Since we are also still within the same function that the expression is in, keywords like break and goto can actually link to loops and labels outside of the expression. I thought about it and I don't see any issues with this.

The inspiration for this expression is that it is just a simpler form for the imo disgusting syntax for immediately invoked function expressions which is (function() return expr end)()... or worse (() => expr)().
We already use the former, with lambda expressions we will do the latter, and ultimately I believe it is worth improving this syntax.

Additionally, lambda expressions can be simplified thanks to this blockexpr by simply always expecting an expression. In fact, lambda expressions would just end up being syntatic sugar for function() return <expr> end. Think about lamba expressions like

  • (foo) => print(foo)
  • () => do print("hi") return 1 end

It just works.

Lastly note that this will always - somehow - be an opt in language feature due to the introduction of a new keyword which can potentially break Lua code.

That all said, I'm asking for feedback on this, if it is feature creep or not.

@mspielberg
Copy link

mspielberg commented Nov 24, 2021

I would greatly prefer if it were just a block that ends in an expression without a keyword:

local two_squared = do
  print(“squaring a thing”)
  2 * 2
end

Then a lambda is local oneAdder = (x) => do x + 1 end.

@JanSharp
Copy link
Owner Author

Hm. Having heard and learned about expression languages recently I can understand where you're comming from. I even thought about the blockexpr just being a block of many expressions where the last expression ends up being the result, which to my knowledge is an expression language thing, but decided against it because it's a completely new concept that Lua isn't made for. (you couldn't use any statements in there at that point).

Now a statlist followed by an expression is interesting... However I believe it suffers from the same issue that it's a completely new concept in Lua. Compare

local use_three = true -- or false

local two_or_three_squared = (function()
  if use_three then
    return 3 * 3
  else
    return 2 * 2
  end
end)()

with

local use_three = true -- or false

local two_or_three_squared = do
  local result
  if use_three then
    result = 3 * 3
  else
    result = 2 * 2
  end
  result
end

Not only is it a new and very different concept, but it also looks just as different.

Because of that I'm leaning towards a no. Not because I think expression languages are bad, but because Lua (and with it Phobos) is not made for it

@mspielberg
Copy link

As mentioned on Discord, with expression-if it gets better:

local use_three = true -- or false

local two_or_three_squared = do
  if use_three then
    3 * 3
  else
    2 * 2
  end
end

In an expression language, a list of expressions just has the resulting value of the last expression in the list. So:

function four()
  print(“doing things”)
  4
end

Then do-end would be less special, and just the same as the immediately-invoked-function version.

@JanSharp
Copy link
Owner Author

Yea that's true, and I understand that and I agree. But I feel like it would only make sense if it really was a block of just expressions where the last one then gets returned.
Which would then need an expression form for all statements (fun fact: there are actually more statements than expressions), at which point you'd be writing completely different kind of code in this context. It might look similar, but you have to think about it differently - and that specifically is what I consider to be problematic. The coexistence of both systems in such close proximity (an expression language inside a statement+expression language (whatever that's called)).

The "statlist followed by just one expression" approach would be less work, but suffer from the same issue as described above, plus that people who know expression languages might end up reading the entire block as expressions, which is them comming to the wrong conclusion which then leads to confusion.

Lastly, hardly anybody who knows Lua would expect that kind of behavior when reading Lua-like code. I care about readability and with it maintainability a lot.

I would say an expression-language mode for Phobos would be possible... and while it would definitely be possible, chances of me implementing it are incredibly low, because of reasons such as feature creep and a significant increase in complexity.

So yea, once again I can see where you're comming from, but the more I think about it the less I see it in Phobos. Sorry :/ .

@curiosity-a
Copy link

There is something important to consider: do you want do to become an expression statement, like the function call?

If yes, what sense does the return from blockexpr make in the statement form, especially when you can do the same with goto? Does it even fit Lua, considering that is one of your criteria?

If no, what keyword do you want to start it with, blockexpr being the obvious choice?

In any case, do you allow the classical "omit the brackets" call syntax when the blockexpr is the only argument? And there is, of course, goto, which shouldn't be able to jump out of the blockexpr and into the scope of the variable that said blockexpr was supposed to initialize.

@JanSharp
Copy link
Owner Author

There is something important to consider: do you want do to become an expression statement, like the function call?

Yes to some degree, see next paragraph.

If yes, what sense does the return from blockexpr make in the statement form, especially when you can do the same with goto? Does it even fit Lua, considering that is one of your criteria?

A "blockexpr" in the statement form isn't a blockexpr, but a dostat. while they would both be using the same keywords do end, they would still be separate and the blockexprretstat would not have any meaning in a dostat making this a non-issue.

If no, what keyword do you want to start it with, blockexpr being the obvious choice?

As said above, it would also use do end. Technically it could use a differnet open_token, but I have thought it through and could not think of any possible currently valid Lua code that would break with do end suddenly being a valid expression. And in terms of terminology I don't see any issue with local foo = do print("hi") <ret> 100 end => "do this work, which means printing 'hi', then return/evaluate to 100 which ends the work, then assign the result to the new local foo".

In any case, do you allow the classical "omit the brackets" call syntax when the blockexpr is the only argument? And there is, of course, goto, which shouldn't be able to jump out of the blockexpr and into the scope of the variable that said blockexpr was supposed to initialize.

Nope. The fact that you can omit parenthesis for function calls is good for table constructors, but a curse for literal strings. Extending that to the blockexpr would just be spreading the curse.

goto jumping into blockexpr is most likely going to be forbidden, yes, because chances are high it would be a jump into unassigned tempraries. Jumping out of blockexprs however is perfectly valid, as long as you do not jump into the scope of an unassigned variable (or to the end of a block, just before new vars go out of scope). This matches how goto works in general.

@curiosity-a
Copy link

A "blockexpr" in the statement form isn't a blockexpr, but a dostat. while they would both be using the same keywords do end, they would still be separate and the blockexprretstat would not have any meaning in a dostat making this a non-issue.

This also makes the syntax inconsistent, the do and return from blockexpr keywords - context-dependent.

goto jumping into blockexpr is most likely going to be forbidden, yes, because chances are high it would be a jump into unassigned tempraries. Jumping out of blockexprs however is perfectly valid, as long as you do not jump into the scope of an unassigned variable (or to the end of a block, just before new vars go out of scope). This matches how goto works in general.

Just drawing attention to the fact that the variable being declared is unassigned until and unless said declaration statement is completely executed (as opposed to being interrupted by goto).

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

3 participants