Not logged inOpenClonk Forum
Up Topic Development / Developer's Corner / Proplist literal syntax extension for Prototypes
- - By Günther [de] Date 2011-09-24 12:58
We already talked a bit about this. The proposal is to replace
var foo = { Prototype = bar, baz = 42 };
with extra syntax for the Prototype, and foo.Prototype with GetPrototype(foo), so that every member of the proplist is the same without any exception. The question is, what syntax?
var foo = bar <- { baz = 42 };
var foo = new bar { baz = 42 };
var foo = SetPrototype({ baz = 42 }, bar);
var foo = { baz = 42 } extends bar;
var foo = { bar; baz = 42 };
var foo = { extends bar; baz = 42 };
var foo = { baz = 42 } : bar;
var foo = bar : { baz = 42 };
var foo : bar { baz = 42 };
def foo : bar { baz = 42 };
var foo = new bar; foo.baz = 42;
var foo = CreateProplist(bar); foo.baz = 42;

or something else? Does anybody know of another language with prototype inheritance which has an object literal syntax which can specify the prototype?
Reply
Parent - - By Newton [de] Date 2011-09-24 18:20
Can you explain in a few sentences what the Prototype is for, what it enables?

I wonder why you didn't propose the most obvious possibility: var foo = bar { baz = 42 };
Parent - - By Günther [de] Date 2011-09-24 22:17 Edited 2011-09-24 22:30
The fact that CreateObject(Clonk).Prototype == Clonk is what makes CreateObject(Clonk).ActMap == Clonk.ActMap. In general, if proplist foo doesn't have the property bar, foo.bar returns foo.Prototype.bar, or foo.Prototype.Prototype.bar if foo.Prototype doesn't have bar either, and so forth. If nothing in the Prototype chain has the property, the result is nil.

var foo = bar { ... } is imho too close to var foo = bar; { ... }. It'd work, but I fear the error message in code like this would be frustrating:
if (foo + bar < baz
{
  bla = CreateObject(Clonk);
  blub = 42;
}
GameOver();

It'd complain that it expected a ), but found an identifier instead on line 6, which isn't where the real error is. I already don't like the fact that a { at the beginning of a statement has a different meaning than at the beginning of an expression. If there where something useful you could do with a proplist without storing or passing it somewhere, everybody would notice that {}.foo = bar; doesn't work like ({}.foo = bar);. Making the { do a third thing after an expression is just too much.
Reply
Parent - - By Newton [de] Date 2011-09-24 23:24
I see. Apart from the syntax of which most of the choices you presented would be OK and mostly a matter of taste, I want to mention something different about the prototype system which I find irritating:

var bar = { Width = 10 };
var foo = { Prototype = bar };


This should work right?
Log("%d",foo.Width); //logs 10
foo.Width = 15;
Log("%d",foo.Width); //logs 15


But if Actions within the ActMap are attemptred to be modified in the same manner (someClonk.ActMap.Scale.Speed = 10), IIRC an error is logged that a read-only property is attempted to be modified. So the above doesn't work for nested properties.
Parent - - By Günther [de] Date 2011-09-25 01:33
It does work, you just have to use writable proplists. See for example PushActionSpeed() in the Clonk.
Reply
Parent - - By Newton [de] Date 2011-09-25 21:51 Edited 2011-09-25 22:01
Actually, this example is exactly the one that I mean:

  if (ActMap == this.Prototype.ActMap)
    ActMap = { Prototype = this.Prototype.ActMap };
  ActMap[action] = { Prototype = ActMap[action], Speed = n };
  if (this.Action == ActMap[action].Prototype)
    this.Action = ActMap[action];


This is freaking incomprehensible! IMO it should be as simple as this, a one-liner:

ActMap[action].Speed = n;

The user shouldn't have to worry about where the values in his proplist come from - from actual values in the proplist or from the prototype of the proplist. The concept of read-only proplists only complicates things for the user, instead if an element of a proplist is assigned with a value which comes from the prototype of that proplist, the element should be copied from the prototype to the actual proplist automatically (internally) and then the value of the element changed.

In the above case, this.ActMap should look like this after the operation:

{ action= { Speed = n} }

Replacing only the changes made to the ActMap from the Prototype (definition, in this case).
Parent - - By Günther [de] Date 2011-09-26 01:10
Consider this:
func Manipulator(proplist p) { p.foo = bar; }
func Baz() { Manipulator(CreateObject(Clonk).ActMap.Walk); }

It is totally unexpected for an expression like foo.bar.baz to change foo, which it would have to do to make the language consistent under your proposal. It would really only improve the situation with the ActMaps, and the problem there is really with the ActMap design. In retrospect, I really should have redesigned that piece after we broke backwards-compatibility. The problems essentially come from me trying to make GetAction() continue to return strings while at the same time making SetAction(GetAction()) restart the action currently running. GetAction() should instead return the proplist, SetAction take proplists, and scripts that interpret action names should instead interpret some property of the action. (I'm not yet sure what to do with NextAction and friends.) This way, changed actions wouldn't be required to be reachable via .ActMap.ActionName, and could just be stored wherever.

In the meantime, we should probably add some way to loop over all properties of a proplist and write a MakeActMapWritable(). Objects which need to change their actions could then call that in their Construction functions.
Reply
Parent - - By PeterW [gb] Date 2011-09-26 13:33
I must side with Newton here - if we have read-only proplists, auto-prototyping makes a lot of sense (that's going from reference semantics to value semantics, isn't it?). That we don't have an obvious syntax for how to call your "Manipulator" with a write-able prop list is a separate problem. I'm almost tempted to call for "->" meaning "auto-prototype this" ;)
Parent - - By Günther [de] Date 2011-09-26 16:56
This doesn't have anything to do with read-only - if the ActMap were writable, you'd just change the ActMap of all the Clonks, and wondering why everything works fine as long as only one Clonk scales simultaneously, but two Clonks scaling at the same time would behave strange.

As far as I know, ActMaps are the only case where this is a problem. With a better Action design, the scaling code would just do SetAction(new WalkAction { Speed = 9000 }) and nobody would complain. Hm, thinking about it again, it probably could already do this.Action = { Prototype = this.Action, Speed = 9000 };
Reply
Parent - - By Sven2 [de] Date 2011-09-26 17:34
ActMaps are the only problem right now because they are the only property that is also a proplist. But wouldn't the same problem arise if other aspects of objects are tranlsated into members of proplist type?

I'm thinking of stuff like menus, overlays, vertices, SolidMasks, etc.
Parent - By Günther [de] Date 2011-09-26 21:36
Not at all! There are plenty of properties that contain proplists. Most have definitions or objects. And while a proplist with a definition as the prototype is useful, you wouldn't want to do that with objects.

In any case, I refuse to make "foo.bar" alone modify foo in a way that is visible to script. I think that leaves the option of doing such things when the child proplist is created. I'm not sure, but I think doing that in the engine would create situations at least as surprising as the ActMap case. Which leaves the MakeActMapWritable() utility function that can be called by objects that need it.

I think that is also the right answers for things like menus: If you create a new menu inheriting from a template, you want to recreate the menu structure, but not things like the pointers to decorative images. For example, if the scenario exchanges the menu decoration at dawn and dusk, open menus should also change the decoration. For that, the menu structure can't contain proplists inheriting from the old decoration, it must leave the properties free so that they do not obscure the properties containing the new decoration.

You have to know which properties contain proplists that "belong" to the new proplist itself, and which are merely used as pointers to other stuff. The engine doesn't know that, in general. Perhaps CreateObject should create a new ActMap for the new object, but I think most objects wouldn't use it, we need the script infrastructure for MakeActMapWritable() in any case, so I think CreateObject shouldn't.
Reply
Parent - By Sven2 [de] Date 2011-09-24 18:52
I like the

var foo : bar { baz = 42 };
Parent - By Zapper [de] Date 2011-09-24 19:31
From a first glance I am in favor of the following two:
var foo = { baz = 42 } extends bar;
var foo = bar : { baz = 42 };


Even though I have to admit I never really used that feature until now - so I can't really say what I missed/expected when I needed it :)
Parent - - By PeterW [jp] Date 2011-09-24 21:39 Edited 2011-09-24 21:44
I like the "new bar { baz = 42 }" thing the most. The arrow doesn't convey the right message for me, postfixes and everything inside the braces make it too easy to miss that you're dealing with a prototype. And I don't want anything that you can't use as an expression.

Oh yeah: And could we make the instantiating-thing overloadable? Idea being that we could make

var obj = new Clonk { x = 200, y = 300 };

work as expected by pointing the instantiation to an engine function introducing the "magic". Could just be a string that proplists automatically cache the function for. Extra points if we allow to pass parameters to it ;)
Parent - By Günther [de] Date 2011-09-24 22:46

> The arrow doesn't convey the right message for me


Well, the new proplist does point to the prototype proplist, in the direction the arrow indicates.

> postfixes and everything inside the braces make it too easy to miss that you're dealing with a prototype. And I don't want anything that you can't use as an expression.


Yeah.

>And could we make the instantiating-thing overloadable?


We could. I think that's for after function pointers and proplists as this. I'm alternating between writing those and fixing release blockers at the moment.

(By the way, today I realized that in definition calls, this doesn't return the definition, despite the fact that it would have been an one-line-change as soon as I made definitions proplists. And even earlier it could have just returned the id of the definition. I hope we can still change that and won't have to keep the old behavior around.)
Reply
Parent - - By Günther [de] Date 2012-02-17 20:07
So I've implemented the "new" option, and am now thinking again about this.

We really need both "create a new instance, potentially with engine magic" and "create a new prototype". The former could be a two-stop process of the latter followed by a function call, though the necessary engine magic to make that work for objects doesn't taste good. (Creating a C4Object instance, moving the properties over, replacing all references to the proplist to references to the object. Or making the whole engine work with the new "passive" objects. Maybe Status=0 already does, but I have doubts.) "new" is used for the former in too many other languages to use use it for the latter.

So we need some other syntax. I'm somewhat tempted to simply use SetPrototype, because the only user of raw proplists with Prototypes are the Actions, which do not need a constructor with side-effects. But I also want to introduce a new set of prototypes for the effects, so we need a syntax that works at the top level of scripts. And those probably should in turn have a common prototype with some effect-specific functions in it, but they shouldn't be effects themselves, so the "new" syntax that calls a constructor is probably not the right choice.

"class foo: bar { ... }"? That'd save us the "static const", and nested function literals are a long way off in any case, so we don't really need a syntax that works as an expression.
Reply
Parent - - By Günther [de] Date 2013-03-23 01:08
Sven2 just asked some questions that pointed out that the current prototype code is buggy in addition to being badly designed. Isilkor and Sven2 came up with these syntaxes for deriving without calling a constructor:
var foo = extend bar { baz = 42 };
var foo = new proplist : bar { baz = 42 };


I kinda like the "extend" one. Enough that I'll probably implement it unless somebody argues for an alternative.
Reply
Parent - - By Zapper [de] Date 2013-03-23 09:33
What is the problem with the current approach? :o
Parent - By Günther [de] Date 2013-03-23 13:18
See the grand-grand-parent of this post for the syntax problems. The bugs in the code are crashbugs when when a prototype gets deleted via RemoveObject or setting the prototype property to a non-proplist (which erroneously doesn't set the internal prototype) and dropping all other references to the former prototype.
Reply
Parent - - By Sven2 [de] Date 2013-03-23 13:29 Edited 2013-03-23 13:32
If proplist derivation were less verbose, it could also be used to define map script algos. E.g.:

Draw(new AlgoEllipsis { x=10, y=10 } );

instead of the current

Draw({ Algo=MAPALGO_Ellipsis, x=10, y=10 } );

I like the "extend" approach, but I don't like the word "extend" so much. I would somehow expect it to do an #appendto.
Parent - - By Günther [de] Date 2013-03-23 15:52

>> Enough that I'll probably implement it unless somebody argues for an alternative.
> I like the "extend" approach, but I don't like the word "extend" so much. I would somehow expect it to do an #appendto.


Feel free to argue for an alternative. (I don't like "new proplist : bar {}" because it looks too much like "new proplist {}".)
Reply
Parent - By Sven2 [de] Date 2013-03-23 15:57
I'd say "derive". I don't have a very strong opinion about it though.
Parent - - By Sven2 [de] Date 2013-03-23 13:35
Also, do we need a keyword at all?

Wouldn't

var foo = bar { baz=42};

work as well?
Parent - - By Isilkor Date 2013-03-23 13:43
Having a keyword means we need less look-ahead in the parser.
Reply
Parent - - By Sven2 [de] Date 2013-03-23 14:20
And look-ahead is a bad thing? Why?
Parent - By Isilkor Date 2013-03-23 18:29
The more look-ahead you have, the more complicated a parser becomes. The less complex a parser is, the easier a language (generally) is to understand (because not only the parser, but also a human has to think about less possible alternatives while reading the code).
Reply
Parent - By Günther [de] Date 2013-03-23 15:53
It would, but see http://forum.openclonk.org/topic_show.pl?pid=14283#pid14283 for the reason I'm against it.
Reply
Parent - - By Caesar [de] Date 2013-03-23 14:12
extend looks fine, imho. Is there a reason against new bar {} though? It doesn't do what it does in other languages?
Parent - - By Sven2 [de] Date 2013-03-23 14:20
As Guenther wrote above, he might want to reserve "new" e.g. to create a new object from a definition.
Parent - - By Zapper [de] Date 2013-03-23 15:11
But isn't that exactly the same in that context?
var twonky = new Clonk {name="Twonky"};

Both "twonky" and "Clonk" are proplists etc.
Parent - By Sven2 [de] Date 2013-03-23 15:50
No, it's not. new should create an object, extend should derive a new definition

var Twonky = new Clonk { name="Twonky"}

but

var MagiClonk = extend Clonk { func IsMagician() { return true; } };
Parent - By Günther [de] Date 2013-03-23 15:27
As currently planned, new Foo in C4Script is about the same as new Foo in C++, whereas extend Foo is the equivalent of class Bar: public Foo. In particular, new Clonk will eventually replace CreateObject(Clonk), while extend Clonk will just create a C4PropList, not a C4Object.
Reply
Parent - By Günther [de] Date 2011-10-31 00:36

> var foo = bar <- { baz = 42 };


This is almost what's been proposed for Javascript, except that they're using | instead of -: var foo = bar <| { baz = 42 }; Not sure whether we should copy this - as a mere proposal it hasn't the advantages reusing syntax from other languages normally has. It's especially not an argument against using the new keyword, since Javascript has already used up that one for something else.
Reply
Up Topic Development / Developer's Corner / Proplist literal syntax extension for Prototypes

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill