leafo / moonscript

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

Chain method calls across line breaks #347

Open hlship opened 6 years ago

hlship commented 6 years ago

I'd like to be able to chain method calls across line breaks. e.g.

flux.to(cell, 0.5, {dy: 0}) 
\ease("quintin") 
\delay(0.05 * (cell.column - 8))

But nothing I've found works. I'd be open to indenting the second and third lines.

Depending on indentation, I get Compile error: Short-dot syntax must be called within a with block or Failed to parse: [2] >> \ease("quintin").

It works as expected if it is all one line, and there are no spaces before the backslashes.

RyanSquared commented 6 years ago

If you don't need to worry about keeping the value, I think you can do something like:

with flux.to cell, 0.5, dy: 0
  \ease "quintin"
  \delay 0.05 * (cell.column - 8)

This might not work as expected because it stores the flux.to value as a variable then applies the method calls to that rather than literally chaining them.

hlship commented 6 years ago

Is that a temporary work around, or a statement that the syntax I want will not / can not be implemented?

leafo commented 6 years ago

The syntax you suggested with \ at the beginning of the next line would conflict with the with block syntax. We could implement this by having the \ be a trailing character on the proceeding line

RyanSquared commented 6 years ago

@hlship It's not a workaround, it's how the language was designed. The syntax you were using is already implemented, but with something else. It might work for your use case though depending on what \delay() affects - if it affects a value returned from \ease() which is not a flux.to value, then it won't work.

hlship commented 6 years ago

I'm just used to Clojure where everything is chaining operations (because all data is immutable). But I do like MoonScript, far more fluid, concise, and readable than standard Lua.

I did a bit of work in CoffeeScript a couple of years back, so this is familiar territory.

ghost commented 6 years ago

We could implement this by having the \ be a trailing character on the proceeding line

Suppose we're using some 'chainy' API like LuaLinq's one. This is an artifical example:

from(array)\ -- I know 'from' is a reserved word, but this is just an example
where((item) ->
  item.startdate > date1 and item.startdate < date2)\
select((item) -> item.taskname)\
toArray!

Without these parentheses around arguments it could look weird. With them it looks more 'heavy' than Moonscript's parentheses-less style, but still more comfortable than Lua syntax. Would be a nice feature in Moonscript. Also, what should happen to indentation with each method call? There is no difference between 1st call and any other call, so it looks like it should either stay the same (then it's easy to not notice \ at the end of previous line) or increase at each call...

leafo commented 6 years ago

That's a good point @Penguinum about not being able to leave out parens with that style. Regarding white-space, since there's a character signifying the next line there doesn't need to be strict white-space, so I would expect people to indent

vendethiel commented 6 years ago

If that's of any interest, this had been an open topic in CoffeeScript 6 years ago ( https://github.com/jashkenas/coffeescript/issues/1407 ) though its fork Coco (https://github.com/satyr/coco/issues/64) and LiveScript added a space-before rule where a 'b' .c is a('b').c. This was also added to CoffeeScript a few years back: https://github.com/jashkenas/coffeescript/pull/3263.

lifeisafractal commented 6 years ago

Any update on this one? I hit on this when using argparse (https://github.com/mpeterv/argparse). That makes use of function chaining much like the python argpase. You can very quickly end up with cumbersome lines out of this when you add many options to a single argument. Would love an elegant solution to this one.

RyanSquared commented 6 years ago

@lifeisafractal can you provide an example? Unfortunately chaining after-line is too close to the with format but it'd be interesting to see if I could redo it with the with syntax.

lifeisafractal commented 6 years ago

I'm pretty new to Moonscript, so it turns out in the case of argparse, with is exactly what I wanted. This works because the argparse objects are mutable. This long line:

install\option("-f --file", "file to install")\args(1)\count(1)\argname('UPDATE_FILE')\convert(io.open)

can become this using the with statement which is arguably much more readable.

with install\option("-f --file", "file to install")
    \args(1)
    \count(1)
    \argname('UPDATE_FILE')
    \convert(io.open)

Where this falls down is when working on immutable objects like strings.

my_str = my_str\trim()\split('\n')

Is not functionally equivalent to

my_str = with my_str
    \trim()
    \split('\n')

Not really a big deal I guess because you can use more lines. IMHO this is a bit more verbose, but not the end of the world by any stretch.

my_str = my_str\trim()
my_str = my_str\split()

Just thinking out loud here, it might be cool to have another variant of with where rather than applying each line in the block to the with argument, it truly chains them. For the sake of argument let's call this chain. so:

my_str = " fOO\nbAr  "
my_str = chain my_str
    \lrim()
    \rtrim()
    \lower()
    \split('n')

would emit the following Lua

local my_str = " fOO\nbAr  "
do
    local _chain_0 = my_str
    _chain_0 = _chain_0:ltrim()
    _chain_0 = _chain_0:rtrim()
    _chain_0 = _chain_0:lower()
    _chain_0 = _chain_0:split('\n')
    my_str = _chain_0
end

This would replace the following (in my opinion) more verbose code.

my_str = " fOO\nbAr  "
my_str = my_str\ltrim()
my_str = my_str\rtrim()
my_str = my_str\lower()
my_str = my_str\split('n')

This is just a half-baked thought so don't take it too seriously. Overall it's likely better to keep the language simpler and not add mountains of syntax sugar.

ghost commented 6 years ago

BTW chaining on multiple lines is possible if you leave method call on the line it should [currently] be but move arguments and closing parentheses on new line:

some_string = another_string\gsub(
  "asdf", "qwer"
)\gsub(
  "qwer", "asdf"
)\upper!

Kind of more ugly, but works.

ajusa commented 4 years ago

Found this issue today when I was trying to use underscore.lua. Anyone else have any workarounds two years later? Normal lua supports multi-line chains, so it would be really nice if I could pull the same thing off in moonscript without temporary variables.

natnat-mc commented 4 years ago

@ajusa told me to put it here, so here goes (discord thread)

wrap = =>
    setmetatable {_val: @},
        __index: (k) =>
            (_, ...) ->
                @_val=@_val[k] @_val, ...

unwrap = =>
    @_val

_ = require 'underscore'

with wrap _{1, 2, 3, 4, -2, 3}
    \chain!
    \map => 2*@
    \filter => @>3
    \each => print @

a=unwrap with wrap _{1, 2, 3, 4, -2, 3}
    \chain!
    \map => 2*@
    \filter => @>3
    \value!

print table.concat a, '\t'

the wrap function creates an object that automatically wraps methods and mutates its internal state. unwrap just undoes it so you can collect it after the with call without having to add another line.

note that this is an ugly hack and another way to do this would be way cleaner