pigpigyyy / Yuescript

A Moonscript dialect compiles to Lua.
http://yuescript.org
MIT License
424 stars 35 forks source link

[feature request] self return for chaining (fat arrow variant) #147

Closed kbtz closed 4 months ago

kbtz commented 10 months ago

Is there a better solution to implementing chainable "methods" other than adding @ as the last statement? Adding a variant to the fat arrow would be a useful syntax sugar for those writing libraries like Rx.

It would also facilitate custom operators since they usually return the left-hand side:

  <unm>: =>
    @value = false
    @
  -- becomes
  <unm>: ==> @value = false

btw, thanks for moving moonscript forward..

pigpigyyy commented 10 months ago

Not really get the usefulness of this new syntax. It is not saving much typing to offer a coding practice to me. And this will cause conflict with current syntax.

f ==> print 1 -- is currently a function declaration

With your proposal this will be a function call with param of an anonymous function f(==>).

It is just my personal opinion. More discussion is welcomed.

kbtz commented 10 months ago

Well I think the main point of adopting more flexible languages is to have better expressiveness/readability, not saving keystrokes. The pain point I'm trying to address is for chainable functions to become first-class citizens, that is, having dedicated syntax to avoid repeated code/boilerplate. Useful if one codebase decides to use that style.

I've tried macros but it didn't improve the situation. While I agree that a new operator could be overkill due to low demand, I think that if it were available more people could benefit from multiline chaining which is already implemented.

This NFR is specific to chaining code style but a more useful, general solution would more powerful macros or other meta-programming features. Since I'm not familiar with the parser/compiler internals I might be dreaming here, but how difficult would it be to have a feature that allow code transformes?

Something like: I register the pattern ==> to be treated as => along with a hook that receives the current code context (a function declaration in this case) and use the hook's return value as a replacement if any. It would be nice to have the context received tokenized but a simple string slice would be already powerful engough to solve use cases like mine.

Just some ideas. For now I will try resolve my situation by injecting function wrappers to tables with chainablle methods as I keep forgetting to add that last @ and get nil returns in very odd places :sweat_smile:

pigpigyyy commented 10 months ago

Understood your use cases. Currently we can use macro for a syntax simulation.

macro c = (fun)->
  "#{fun}
    @"

tb = <unm>: $c =>
  @value = false

tb =
  new: $c (x, y, z) =>
    print x, y, z
  chain: $c =>
    print "OK"

tb
  \new(1,2,3)
  \chain!

compiles to:

local tb = setmetatable({ }, { -- 5
  __unm = function(self) -- 5
    self.value = false -- 5
    return self -- 5
  end -- 5
}) -- 5
tb = { -- 9
  new = function(self, x, y, z) -- 9
    print(x, y, z) -- 9
    return self -- 9
  end, -- 9
  chain = function(self) -- 11
    print("OK") -- 11
    return self -- 11
  end -- 11
} -- 8
return tb:new(1, 2, 3):chain() -- 16

And to add it for a new syntax, maybe we can choose other symbols for it to prevent syntax conflict.

kbtz commented 10 months ago

I didn't realized we could capture whole blocks as a macro argument, cool! From the docs I had the impression that I would need to use multiline strings for that.

Tried this macro pattern for operators but there still some situations where it is not clear why it breaks:

macro op = (name, fn) ->
  "<#{name}>: (value) =>
    #{fn\sub 3}
    @"

-- this indentation works
t1 = $op add, ->
  if something
    @value += value

-- even this one
t2 =
  a: 
    b: $op unm, ->
      if something
        @value = false

-- but this does not
t3 = 
  $op add, ->
    if something
      @value += value
  $op unm, ->
    if something
      @value = false

And to add it for a new syntax, maybe we can choose other symbols for it to prevent syntax conflict.

I think =>> doesn't conflict with valid code (plus its font ligature looks even better :sunglasses:).

kbtz commented 10 months ago

On the error above, adding bracers to t3 = { ... } compiles, which is odd because writting the macro output manually doesn't trigger the error:

t4 =
  <add>: (value) =>
    if something
      @value += value
    @
  <unm>: (value) =>
    if something
      @value = false
    @

Maybe some identation issue on the macro result? Is there a way to have more verbose diagnostics for macros when using the lua module?

pigpigyyy commented 10 months ago
t3 = 
  $op add, ->
    if something
      @value += value
  $op unm, ->
    if something
      @value = false

The $op add, -> is seen as a complete expression instead of key value pair for a table to the compiler. So this code is the same as:

t3 =
  ($op add, ->
    if something
      @value += value)

  ($op unm, ->
    if something
      @value = false)

and this won't compile. Macro in Yuescript currently can only be an expression or a code block. You can't replace other AST such as key value pair with it.

pigpigyyy commented 4 months ago

Just implement a new feature for adding default return expression for a function. And the chaining function you mentioned could be written as:

tb = <unm>: =>
  @value = false
  @

-- becomes
tb  = <unm>: (): @ =>
  @value = false

And this syntax prevent the implicit return behavior for a function.

tb  = <unm>: (): @ =>
  @value = false
  if @value
    -- no longer returning the last line to prevent unintended behavior.
    doSomethingWith @

When you have too many nested levels of indents in a function body, you can make use of the syntax to append a default return at the end.

f = (): result ->
  result = false
  if checkOne!
    if checkTwo!
      result = true

compiles to:

local f
f = function()
  local result = false
  if checkOne() then
    if checkTwo() then
      result = true
    end
  end
  return result
end
kbtz commented 4 months ago

Just played with it on the website and I see it will make my functions more readable to me. Thank you!

When you have too many nested levels of indents in a function body, you can make use of the syntax to append a default return at the end.

This will be very useful too.