Closed determin1st closed 6 years ago
Why?
Why not?
It looks ugly and long to type.
@
stands for this
, why not some @@
stands for this.__proto__
, they are related semanticly.
btw, __proto__
is defined in ECMAScript® 2015, and we like to move forward, right?
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
@@
already stands for constructor
.
I wouldn't want to add baked-in support for a deprecated language feature anyway.
No problem, why not add @@
=> Object.getPrototypeOf this
? constructor may not exist at all.
Because, again, @@
already stands for constructor
. Come up with a different syntax that doesn't conflict with existing syntax (and ideally doesn't evoke something unrelated) and we can discuss.
There is already the ::
syntax which translates to either:
::
=> prototype
left::
=> left.prototype
::right
=> prototype.right
left::right
=> left.prototype.right
::
and ::right
work well in LiveScript class definition blocks since there is an implicit variable named prototype
, and actually corresponding to the object being set as the prototype of the defined class.
::
and ::right
also work well anywhere else if you defined yourself such a variable with the proper scope.
left::
and left::right
work well when left
is a class generated by LiveScript since this prototype
variable mentioned above is assigned to the constructor. So for a class A
you could enhance the prototype later on with A::my_property = ...
. On instances of A, you can even do instance@@::my_property
, since as said in other comments @@
translates to constructor
(works the exact same way as ::
).
I'm telling this for two reasons:
::
Object.getPrototypeOf
, I'm wondering if reusing this ::
syntax or something close would be something good? Like: we keep the translation to prototype
inside LiveScript class blocks, but translate to Object.getPrototypeOf
outside? That would be semantically acceptable I think. I'm telling that but I must admit that it would break my very own code since I'm using the A::my_property
syntax :joy:Just to clarify, ClassName::instance-method
works even if ClassName
isn't a LiveScript class.
It's also worth noting that ::
inside class blocks refers statically to the enclosing class prototype, not dynamically to the instance prototype. So, for example,
class Foo
set-default-steve: (value) ->
::steve = value
class Bar extends Foo
f = new Foo
b = new Bar
b.set-default-steve 1
console.log f.steve == 1 # true
console.log b.steve == 1 # true
class Foo2
set-default-steve: (value) ->
Object.getPrototypeOf this .steve = value
class Bar2 extends Foo2
f = new Foo2
b = new Bar2
b.set-default-steve 1
console.log f.steve == 1 # false
console.log b.steve == 1 # true
@rhendric Yes, it works anywhere since ::
= prototype
to sum it up, and that's all the user should remember. Everything else then just follows JavaScript rules regarding scoping and so on. When I said it works in LiveScript classes, I meant: in LiveScript class blocks, there's an implicit prototype
variable created at the top, so you can access it. Nice example regarding the difference between using prototype
and Object.getprototypeOf
in such a use case!
:: needs a name, also, it works with classes sugar over JS. people often use prototype to change it (why? if you can wrap). what i thought (about __proto__
) was an access to down-layer object and it's code reuse. btw, there is a super
construct in classes, tried it, but in livescript it translates to superclass
which makes things not working staight-forward.
@determin1st, I'm having a lot of trouble understanding that last comment. Are you saying that you're actually interested in the superclass's prototype and not the instance's prototype? What are you trying with super
and what isn't straightforward about it?
I will post some modified code example (from super
docs, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super ):
class Polygon {
init(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
console.log('Polygon init');
}
}
class Square extends Polygon {
init(length) {
this.height; // ReferenceError
super.init(length, length);
console.log('Square init');
}
}
this looks (in my interpretation) in livescript like this:
Polygon =
init: (height, width) !->
@name = 'Polygon'
@height = height
@width = width
console.log 'Polygon init'
Square = ^^Polygon <<< {
init: (length) !->
@height # ReferenceError
@__proto__.init length, length # might have been @@init
console.log 'Square init'
}
so, see, __proto__
looks ugly but it does the same function. if there is no need to change prototype, one could .call base function (@__proto__.init.call @
)
we could vote for "did you use @@ in your code?" and then make a back-incompatible change. lets vote, why not.
Why not use classes in the rewrite?
class Polygon
init: (height, width) !->
@name = 'Polygon'
@height = height
@width = width
console.log 'Polygon init'
class Square extends Polygon
init: (length) !->
@height # ReferenceError
super length, length
console.log 'Square init'
Well,
maybe, there are more restrictions on class syntax. so, dont like to use it.
Refusing to use the features LiveScript already has to make this sort of thing easier isn't an argument for adding more features to accomplish the same thing a different way.
Also, there's a big problem with using prototypes without classes in the way that your example shows. Suppose we have:
Polygon =
init: (height, width) !->
@name = 'Polygon'
@height = height
@width = width
console.log 'Polygon init'
Square = ^^Polygon <<<
init: (length) !->
@__proto__.init.call this, length, length
console.log 'Square init'
UnitSquare = ^^Square <<<
init: !->
@__proto__.init.call this, 1
console.log 'UnitSquare init'
(I changed the __proto__
calls to use .call
to keep from mutating Polygon's prototype when initing Square and UnitSquare.)
If you try to run UnitSquare.init!
, you'll fall into infinite recursion. Referencing this.__proto__
is a bad way to call methods on a base object because it doesn't statically reference the ‘next’ object in the inheritance chain; it dynamically references whatever object is one level down in the inheritance chain from the instance itself.
If you use classes and super
, you don't have this problem. You also don't have to write .call this
. If you still don't want to use classes, though, the correct way to write this pattern would be:
Polygon =
init: (height, width) !->
@name = 'Polygon'
@height = height
@width = width
console.log 'Polygon init'
Square = ^^Polygon <<<
init: (length) !->
Polygon.init.call this, length, length
console.log 'Square init'
UnitSquare = ^^Square <<<
init: !->
Square.init.call this, 1
console.log 'UnitSquare init'
Which makes no reference of __proto__
at all. Either way, whether you use classes or prototypical inheritance, accessing the instance's prototype isn't necessary.
That was unexpected. But, imo, you enforced the example, i didn't ever thought about multiple inheritance.. objects go 1+1 (in my code) and there is some clue to continue leveling (through some api).
Polygon =
init: (name) !->
@__proto__.init.call @, name
Square =
init: (name) !->
@plugin = @[name]!
...
# interface
Unit: do ->
# static
...
# constructor
return ->
# dynamic
...
...
instance = ^^Square <<< Polygon
instance.init 'Unit'
...
a kind of reverse logic... the object of interest is one, prepended with ^^ and extended with base methods <<<.
googled some info about it, seems to be not just a like/dislike code style - https://en.wikipedia.org/wiki/Composition_over_inheritance
You seem to be describing mixins, not composition. Mixins are a variation on inheritance.
That example is starting to clarify some of your earlier comments for me. Can you construct a more complete, working example, or point me to code in a public repository somewhere? I strongly suspect that LiveScript classes are very useful for the kind of code that you're describing, but I probably can't convince you of that without showing you how I would use them to solve your actual problems.
I'm trying to design widget library, it's not ready yet, so I can't show working code for now. Here is some schematic code with parts, where __proto__
is used:
w3ui = do ->
...
WIDGET =
construct: (name) -> (selector, opts) -> # {{{
# check
if not (name of WIDGET.store)
console.log 'w3ui: widget «'+name+'» is not loaded'
return null
# get widget prototype
widget = WIDGET.store[name]
# query DOM
if not (node = QUERY selector)
console.log 'w3ui: DOM query failed for «'+selector+'»'
return null
# create new widget object
widget = ^^widget <<< WIDGET.baseMethods <<< {
# base properties
name: name
node: node
}
# initialize api and sub-objects
widget.init!
# create
if not widget.create opts
widget.log 'failed to create'
return null
# done
return widget.api
# }}}
baseMethods:
init: !-> ... # here__proto__ is used
create: (opts) -> ... # here__proto__ is used
setup: (key, val) -> ... # here __proto__ is used
attach: !-> ...
log: (msg) !-> ...
...
...
return new Proxy QUERY, {
set: (obj, key, val) -> # load widget prototype {{{
# check
if key of WIDGET.store
console.log 'w3ui: widget «'+key+'» already exist, check your code'
return true
# store
WIDGET.store[key] = val
return true
# }}}
get: (obj, key) -> # w3ui accessor {{{
# stand-alone function
return DEP[key] if key of DEP
# widget constructor
return WIDGET.construct key
# }}}
}
later on, i declare widget:
w3ui and w3ui.accordion = {.../* widget definition */...}
and then, it is used (widget instance is created) like this :
a = w3ui.accordion '#someCssSelector', {.../* accordion widget options */...}
I will revise code and post link to working example later... if it's not enough.
So, let me describe what I see here before I propose a solution:
Your w3ui
object is a proxy that intercepts assignments and treats them as widget type declarations, storing the assigned value for later use as a prototype when a widget of that name is requested.
At instantiation time, you create a new widget object first by making a prototypical clone of the widget type declaration (^^widget
) and then assigning methods and properties to it. Behind the scenes, the prototypical clone is creating a new function object (basically an anonymous class), assigning the widget type declaration as its prototype, and then creating an instance with that function.
The assigned methods are using the __proto__
property to access properties on the widget type declaration. This is fragile, for the reasons I laid out in my earlier comment: if anyone ever makes a prototypical clone of your widget objects, the baseMethods
methods that use __proto__
are likely to break and/or loop forever. This code works by a kind of coincidence: __proto__
refers to ‘this object's prototype, whatever this object happens to be and however many layers of prototyping have been applied to it’, whereas you seem to always want it to refer to ‘the widget type definition used to create this widget’, and those two things are only the same because you happen to be using inheritance the way you are and because you're assuming that nothing else is going to use inheritance on the objects you create.
If that assumption is good enough for you, well, it's your project. But to anyone considering using __proto__
in this way (and by extension, anyone who might want syntax sugar to assist them in using __proto__
in this way), I would argue that there is a simple and more direct way to say what you mean and avoid future bugs: make baseMethods
a function that accepts a widget type definition, and replace all instances of @__proto__
with that argument.
# create new widget object
widget = ^^widget <<< WIDGET.baseMethods widget <<< {
# base properties
name: name
node: node
}
...
baseMethods: (base) ->
init: !->
# replace `@__proto__.init.call this, other, args` with
base.init.call this, other, args
...
(FYI, the widget creation can take advantage of several LiveScript idioms to be more succinct, if that's of interest to you:)
# create new widget object
widget = widget with { name, node, ...WIDGET.baseMethods widget }
An alternate solution (because I promised classes!) hinges on observing that the clone operation throws away an identical anonymous class each time it's invoked, and that class could be moved to the widget declaration function and preserved. Here I'll use explicit class syntax so as to also take advantage of the super
keyword:
set: (obj, key, val) -> # load widget prototype {{{
# check
if key of WIDGET.store
console.log 'w3ui: widget «'+key+'» already exist, check your code'
return true
# store
WIDGET.store[key] = class extends ((->) <<< prototype: val)
name: key
# move all WIDGET.baseMethods here
init: !->
# replace `@__proto__.init.call this, other, args` with
super other, args
...
return true
# }}}
And now widget creation is simply creating a new instance of that class and assigning any per-instance properties:
# create new widget object
widget = new widget <<< { node }
Yes, you see all correct, except:
if anyone ever makes a prototypical clone of your widget objects, the baseMethods methods that use
__proto__
are likely to break and/or loop forever... If that assumption...
this is not an assumption, because widget instance is not returned, only the widget.api
.
Thanks for your help, i will use that widget with {...}
syntax.
Ah yes, you're right!
Okay, happy to help. I'm closing this issue, but if you have a different use case for accessing an object's prototype that you think is common enough to merit its own syntax and isn't covered by existing LiveScript features, let's reopen.
so it may be used to access base object methods, like:
LS:
@@init!
=====> JS:this.__proto__.init();