gkz / LiveScript

LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming.
http://livescript.net
MIT License
2.32k stars 155 forks source link

Smooth Operator Overloading #284

Open brainrake opened 11 years ago

brainrake commented 11 years ago

A long-time missing *script feature is operator overloading. We can get close, e.g.

class Vector extends Array
  (arr) -> @push ...arr
  '+' : -> new Vector zipWith (+), @, it
a = new Vector [1 2]
b = new Vector [-1 6]
console.log a\+ b'+' a  # => [1, 10, constructor: function, +: function]

but even disregarding the ugly output (and 'class' and 'new'), it's far from perfect:

a\+b         # a['+b']  
fn a\+ b, c  # fn(a['+'](b, c))
a\+ b && c  # a['+'](b && c)

I propose adding the syntax:

a.+ b        # a['+'](b)
a.+b         # a['+'](b)
fn a.+ b, n  # fn(a['+'](b), n)
a.* b.+ c    # (a['*'](b))['+'](c)
a.< b        # a['<'](b)
a.< b.< c    # a['<'](b) && b['<'](c)
# etc.  'xor', 'and', 'or' stay unmodifide

I'm not so sure about these, especially the last one:

a.+= b      # a['+='](b)
fn a.++, b  # ($ref = fn(a, b)), a['++'](), $ref
fn --.a, b  # fn(a['--'](), b)

These are currently syntax errors, so it shouldn't conflict with anything. This syntax would clearly differentiate overloaded operators while still looking similar to the plain variety. The overloaded operators should have the same arity, precedence and bracket rules as the real ones.

vendethiel commented 11 years ago

(Ref satyr/coco#60)

a\+ b and c compiles to a['+'](b) && c;, not your code : and closes implicit calls.

brainrake commented 11 years ago

thx, fixed example

vendethiel commented 11 years ago

I suppose we could use doLiteral in lexer to change tag to LITERAL if last is dot. not sure if that's worth it, considering you have to implem these yourself anyway

humanchimp commented 11 years ago

neat idea!

maxov commented 11 years ago

Maybe it's possible to just have some utility properties, that when defined, overload the operator without any need for more syntactic sugar(think python):

myObj =
    val: 5
    __plus: (that) -> that.val + @val

myObj + {val: 5}

Although, that would be a bit hard to implement at compile-time.

brainrake commented 11 years ago

@gratimax, that approach actually adds an unnecessary renaming, hides the fact that overloading is happening, and requires runtime support. obj.+ syntax overcomes these problems, and you can still use obj'+' or obj[\+] etc to perform a regular property lookup.

maxov commented 11 years ago

@brainrape I guess you're right, then. +1

craigyk commented 11 years ago

Trying to gauge the temperature in the room:

I would love a way to overload array indexing and attribute access... actually way more than even arithmetic operators.

Not sure if mapping to something like python's getattr and getitem methods are the way to go, but maybe something similar?

vendethiel commented 11 years ago

I would love a way to overload array indexing and attribute access

objects / length star / defineProperty :° ?

craigyk commented 11 years ago

@Nami-Doc

Not sure what you mean by that? Are you saying LiveScript already provides some ability to programmatically control attribute access? because as far as I can tell Javascript itself doesn't, and one would have to fake it. It would definitely be a lot slower than built in object attribute or array item access.

vendethiel commented 11 years ago
class Object
  abc:~ -> "abc"

console.log (new Object)abc
# => "abc"

using Object.defineProperty

craigyk commented 11 years ago

@Nami-Doc

yeah, that's not the same thing. I mean programmatic access for intercepting attribute and indexing accesses as keys.

A heavily contrived Python example:

class Dog(object):
    def __init__(self,name):
        self.name = name
        self.tricks = {}
    def teach(self,name,trick):
        self.tricks[name] = trick
    def __getattr__(self,name):
        if name in self.tricks:
            return self.tricks[name].__get__(self)
        return lambda : self.name + " tilts head"

dog = Dog()
dog.teach("sit", lambda x: x.name + " sits")
dog.teach("speak", lambda x: x.name + ": Woof")

print dog.speak()  # "fido: Woof"
print dog.sit()       # "fido sits"
print dog.lay()      # "fido tilts head"
vendethiel commented 11 years ago

Ah. Proxies then.

craigyk commented 11 years ago

@Nami-Doc

yeah, that looks about right... just wondering about how people feel about this in livescript since it can be thought of as a form of operator overloading. Though, come to think about it... can't see how implementing this as the Livescript level wouldn't slow all object accesses down, so....

vendethiel commented 11 years ago

I don't see how we could do that in LS, tbh

craigyk commented 11 years ago

Yeah, can't see how it wouldn't be horribly slow without some kind of runtime support.

Hmm, looks like a proxy object is slated for Harmony though...

brainrake commented 11 years ago

i wanted to keep it lightweight, that's why i didn't explore property access and indexing. although indexing could work with

obj.[3]  #=> obj['[]'](3)

but that might conflict with livescript semiautovivification

there's also https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/get (and set)