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

Allow super.method() in classes #1075

Closed ceremcem closed 5 years ago

ceremcem commented 5 years ago

Sometimes we need chain reaction instead of simply overwriting existing methods in class inheritance. I'd expect to have a super.method() ability but it seems it doesn't exist at the moment. Consider the following example:

class X 
  load: (data) -> 
    alert "data (#{data}) is loaded in X (id: #{@id})"

class Y extends X 
  (@id) -> 
  load: (data) -> 
    # should call X.load first 
    #super.load ... <- expected this to work
    X::load.bind(this) ...
    alert "data (#{data}) is loaded in Y (id: #{@id})"

hello = new Y 5
  ..load \something

there = new Y 6
  ..load \something

Here we want every class that extends X (or extends Class which extends X under the hood) have a .load() method that will both run its superclass' .load() method and then perform its custom actions. Here we need super.load() method.

Workaround

Either use a _super() function that brings the superclass method with appropriate context:

_super = (ctx, method) ->
    Object.getPrototypeOf(Object.getPrototypeOf ctx)[method].bind(ctx)

# Usage:
class Y extends X
  load: -> 
    (_super this, 'load') ...
    # do extra work here

...or make your class implement _super method:

_super = 
  _super: (method) ->
    Object.getPrototypeOf(Object.getPrototypeOf this)[method].bind(this)

class Y extends X implements _super
  load: (data) -> 
    (@_super \load) ...
    # do your extra work here
rhendric commented 5 years ago

The super keyword refers to the current instance and method; see the documentation. So you can write:

class Y extends X 
  (@id) -> 
  load: (data) -> 
    super ... # basically shorthand for X::load ...
    alert "data (#{data}) is loaded in Y (id: #{@id})"
ceremcem commented 5 years ago

Wow! Yes, that was what I meant. I've read exactly that part in the documentation just right before opening the issue but it turns out I didn't understand what I read. Thanks!

ceremcem commented 5 years ago

Is there a way to point current class' methods? Considering the following example:

class Z
  load: (data) -> 
    alert "Z loads data: #{data}"

class X extends Z
  -> 
    super ... 
    SOMEHOW.load 'hello'  # will alert "data (hello) is loaded in X (id: 5)"

  load: (data) -> 
    alert "data (#{data}) is loaded in X (id: #{@id})"

class Y extends X 
  (@id) -> 
    super ...

  load: (data) -> 
    # should call X.load first 
    #super ...
    unless data is \something
        alert "NOT EXPECTED: data (#{data}) is loaded in Y (id: #{@id})"

class T extends Y 
  (@id) -> 
    super ...

  load: (data) -> 
    alert "data loaded in T: #{data}"

hello = new T 5
  ..load \something

I need to display data (hello) is loaded in X (id: 5) by calling SOMEHOW.load 'hello'.

rhendric commented 5 years ago

To do that exact thing, I would probably write ::load.call this, 'hello', although this strikes me as an unusual pattern for object-oriented code, and if possible I think I would instead pull code in X::load out into a different method which is called by X::load and the X constructor, and call that method the normal way.