leafo / moonscript

:crescent_moon: A language that compiles to Lua
https://moonscript.org
3.21k stars 191 forks source link

Proposal for multiple selfs on method call #221

Open leafo opened 8 years ago

leafo commented 8 years ago

Here's something I'm considering, feel free to leave any feedback:

The \ operator is used to control the receiver (first argument) of a function that you're calling on a table. This is how we work with methods. Currently:

hello\world 1,2,3

Is equivalent to:

hello.world(hello,1,2,3)

What if the \ operator could be chained to accumulate multiple receivers. The proposed addition would look like this

foo\bar\word 1,2,3

Would compile to

foo.bar.world(foo,bar, 1,2,3)

Each name that appears before a \ would be a receiver.

Additionally, bound functions will also work:

x = foo\bar\world

Compiles to (about):

local x = function(...)
  foo.bar.world(foo, bar, ...)
end

With @ and a class:

class MyThing
  extra: {
    write_title: (e, title) =>
      print "title:", title
  }

  some_method: =>
    @\extra\write_name "hello"

Note we have to use @\ since @extra\write_name is already common syntax.

conflicts

No conflicts, this syntax currently throws a compiler error (oops)

The @\ approach for calling a method could be considered a minor conflict.

use case

I see this as a different way to think about mixins. Typically I've encouraged copying functions into classes/tables in order to bring in more functionality. Issues with this approach is expensive setup when copying many things and dealing with name conflicts. The other approach to mixins is to set up a multiplexing __index metamethod but there are performance penalties

This will effectively create a new way to extend the functionality of objects while keeping the new functions and properties namespaced. In order to install the mixin only one copy is necessary, and if the mixin is changed later those changes will be available in that module since we are working with a reference to that table.

downsides

The order of the receivers might not be ideal in some situations, for example:

class CounterMixin
  count: 0

  increment: (parent) =>
    @count += parent.value

class MyThing
  value: 2
  new: =>
    @counter = CounterMixin!

  do_something: =>
    -- Although this would be nice, the wrong receiver is sent
    -- the instance of MyThing is sent first
    @\counter\increment!

An alternative way to write the mixin method that does work:

class CounterMixin
  count: 1

  increment: (parent, self) ->
    @count += parent.value

Another approach would be to reverse the order in which the receivers are sent to the function, with the closest on to the right being the first argument.

The distinction between @hello and @\hello might make things confusing to new users.

extensions

We could also add some variations, like mixing . and \:

something\hello.world 1,2,3

compiles to

something.hello.world(something, 1,2,3)

Or...

something\hello.world\okay 5,4,3

To:

something.hello.world.okay(something, world, 5,4,3)
Idyllei commented 8 years ago

I feel that adding this special-case syntax would make Moonscript code less readable and harder to maintain. (If we get into a habit of adding such special-case syntax, Moonscript might end up looking quite a bit like C++.)

The '@\counter\increment!' syntax looks erroneous, and seems to me to be calling a function on 'self' such as

self.increment counter

or

@counter\increment!

Right now, one would expect the function call to relate (mostly) to the item which directly references it, not the items three or four steps up the call chain:

something\hello.world\okay 5, 4, 3

Personally, I would (mistakenly) expect the above to be the same as:

something\hello!.world\okay 5, 4, 3

And adding this new syntax opens up some really-hard-to-find bugs such as if I meant to type the above, but forgot to put in the (!).

In my opinion, this potential addition would make reading code too complicated. I am a strong supporter of the Pythonic style: simple and clear without lots of special cases.

I propose a syntax that is dissimilar to the current chain syntax:

class MyThing
  extra: {
    write_title: (e, title) =>
      print "title:", title
  }

  some_method: =>
    @->extra->write_name "hello"
-- or even a different syntax that uses '::' since ':' in Lua passes the 'self' param.
-- so '::' could be read as meaning 'keep passing down ____'
  some_method2: =>
    @::extra::write_name "hello"   -- @.extra.write_name(@, extra, 'hello')

because the member to the left of -> is being 'passed' down (to the right) to the function call. This new syntax would help prevent any I-forgot-to-press-* mistakes., and I think it looks nicer than having a bunch of already-used symbols next to each other.