leafo / moonscript

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

Allow __inherited to replace child class #93

Open eloff opened 11 years ago

eloff commented 11 years ago

I overloaded __inherited, but what I really want to do is return a new class from the method and have that replace the calling child class.

It's easily done with full backward compatibility with a minor tweak to the code gen. Replace:

if _parent_0 and _parent_0.__inherited then
    _parent_0.__inherited(_parent_0, _class_0)
end

with

if _parent_0 and _parent_0.__inherited then
    _class_0 = _parent_0.__inherited(_parent_0, _class_0) or _class_0
end

Thoughts?

exists-forall commented 11 years ago

Seems like a useful feature, but runs into the same subtle issue mentioned when discussing new returning a new self (I had no idea that inline code looked so cool in a link!). The problem is that implicit return values can cause you to override the class without meaning to.

eloff commented 11 years ago

Good point. Both cases could be solved by requiring a slightly more complex return value before overriding the class/self. E.g. return (value, True) or {replace: value} or {self: value} etc. Then it would be very hard to do by accident.

A "cleaner" solution would be to allow replacing self/class with some kind of assignment from within the method, but I'm not sure if that can be easily done without special help from the parser.

exists-forall commented 11 years ago

I like the idea of assignment from within the method. To do this we'd need some sort of pass-by-reference semantics, but that's easily accomplished with a table, which is implicitly a reference type. In fact, we don't even need to add a second (or third, if you're contain self) parameter to __inherited - we can just use the table that's already being passed in by detecting if it's had a certain field set:

if _parent_0 and _parent_0.__inherited then
    _parent_0.__inherited(_parent_0, _class_0)
    if _class_0.__override then
        _class_0 = _class_0.__override
    end
end

pass-by-reference should have a more general support in the language, however. I'm starting up a new issue right now.

eloff commented 11 years ago

I like that solution, it's clean and simple.

On Tue, Apr 2, 2013 at 11:33 PM, SelectricSimian notifications@github.comwrote:

I like the idea of assignment from within the method. To do this we'd need some sort of pass-by-reference semantics, but that's easily accomplished with a table, which is implicitly a reference type. In fact, we don't even need to add a second (or third, if you're contain self) parameter to __inherited - we can just use the table that's already being passed in by detecting if it's had a certain field set:

if _parent_0 and _parent_0.inherited then _parent_0.inherited(_parent_0, _class_0) if _class_0.override then _class_0 = _class_0.override endend

pass-by-reference should have a more general support in the language, however. I'm starting up a new issue right now.

— Reply to this email directly or view it on GitHubhttps://github.com/leafo/moonscript/issues/93#issuecomment-15817506 .

exists-forall commented 11 years ago

Actually, I was going to start an issue about pass-by-reference, behaving similarly to C++ reference params, such as

void swap(int& a, int& b) {
    int temp = b;
    b = a;
    a = temp;
}

but then realized that most use cases were just to provide a work-around in the absence of multiple return values. All the other use cases were just to prevent an assignment. Things like

void drop(immutableLinkedListRepresentingStack& stk) {
    stk = stk.next;
}

which could theoretically be useful in moonscript too

drop = (stk_ref_of_some_sort)->
    stk_ref_of_some_sort\set stk_ref_of_some_sort\get!.next

used like this

drop ^my_stack
-- or possibly
drop &my_stack
-- or
drop `my_stack

however, this is made infinitely clearer by the (slightly) more verbose, cutting edge technology of return values!

my_stack = drop my_stack

maybe what we need is something like Arc's zap macro (do any other lisps have this?), but as a language feature. How about a zap-with construct (keyword reuse: resourceful or unreadable?), to generalize update assignment

zap my_stack with drop

extra operands in the zap-with syntax could by compiling code like this

zap foo with bar baz, qux

into this

foo = bar(foo, baz, qux)

although this is ambiguous with

foo = bar(baz, qux)(foo)

since every function is just an expression.

Maybe a more classic update assignment is in order

my_stack drop= -- pretty weird not having anything on this side of the equals sign...

this looks a lot better when you have another operand, which is the case with all the existing assignment operators

my_stack push= io.read! -- this is better

although, this would probably just be done with an overloaded ..

my_stack ..= io.read! -- simpler?

thoughts?

exists-forall commented 11 years ago

Lisp also has a form of pass-by-reference, actually, which is macros, since a macro can insert an assignment into the generated code. However, lisp references come with an extra bonus feature that could be useful for moonscript libraries, which is the ability to see the name of the reference. For example, a Lisp class system might use something like this

(class Hello
    (defmethod (whatever x y z)
        foo)
    (defmethod (something a b c)
        bar))

with the class macro using Hello both as the symbol to assign the new class too, and a field inside the class, so if we created an instance

(set my-hello (new Hello))
(print (send my-hello get-class-name))

it would know the name of its class. This is like what moonscript already does in its class construct by assigning to __name as well as using that string as the name of the variable to put the class in. This is because Moonscript is sort of an ad-hoc set of macros implemented for Lua. However, it would be useful to give everyone the ability to write these name-grabbing functions, and not just limit them to being language-level. For example, if I'm using love2D, I have to write the name of every image twice in my code

player_running_right_1 = love.graphics.newImage "player_running_right_1.png"
player_running_right_2 = love.graphics.newImage "player_running_right_2.png"
-- etc...

however, in a lispy language I could write a load-image macro and use it like so

(load-image player-running-right-1)
(load-image player-running-right-2)

we don't have to have lisp envy, though. There's no reason you need macros to accomplish any of these tasks - they're just how they've been implemented in the past.

Imagine a (unary) ^ operator (following the same ambiguity rules as unary minus), where

^foo

compiles to

{
    get = function()
        return foo
    end;
    set = function(newFoo)
        foo = newFoo
        return foo
    end;
    name = "foo";
}

now we can write our load_image function like this

load_image = (img_ref)->
    img_ref.set love.graphics.newImage img_ref.name .. ".png"

and use it like this

load_image ^player_running_right_1
load_image ^player_running_right_2
-- etc...

I can think of maybe 10 or so other times I've wanted this in the past, mostly for implementing my own class systems though. Useful?

exists-forall commented 11 years ago

This unary ^ operator would also be useful for self-referential immutable data structures. I was writing a PEG (parsing expression grammar, which happens to be what moonscript uses for all of its parsing) library the other day, and I kept running into the issue of my Pattern objects, being immutable, not being able to be initialized with themselves as parameters. I eventually settled on using closures, like this

nested_parens = pattern.TwoPossibilityPattern (pattern.CompoundPattern '(', pattern.LazyPattern(->nested_parens), ')'), '()'

however, this seems like a good use for the get field of references

nested_parens = pattern.TwoPossibilityPattern (pattern.CompoundPattern '(', pattern.LazyPattern(^nested_parens), ')'), '()'

It literally only saves one character, but I think it makes the intent clearer.

exists-forall commented 11 years ago

</incoherent-rant belongs-on-this-issue=false>

exists-forall commented 11 years ago

Just one more potential use case! Properties in closure-based object systems. For example, if we have a closure-based class like mentioned here, it's common to have to write accessor methods (which are far better than raw fields, because they're polymorphic), like this

Person = (name)->
    with {}
        .get_name = -> name
        .set_name = (new_name)-> name = new_name
        -- various other methods...

^ is perfect here. Consider an Object base class

Object = ->
    with self = {}
        .property = (ref)->
            self["get_" .. ref.name] = ref.get
            self["set_" .. ref.name] = ref.set

and a rewritten Person

Person = (name)->
    with Object!
        .property ^name
        -- various other methods
eloff commented 11 years ago

I'm going to work up a PR soon for the solution suggested by SelectricSimian. If there's any objections or alternative proposals please let me know.