OK, I did a quick setup of an environment where [new] does not accept -dash initialised data, but with a separate [new-wish] that has a final argument, a script evaluated inside the object, which can do any pre-initialisation before the constructor is called. This solution works for me now and I'm not having issues with the couple of other XOTcl libraries I'm using.
I think this is a pretty decent solution and the resulting instantiation code actually looks pretty nice and readable. I'm up to other names for "new-with". Maybe something like <new ?args? with ?initscript?.There's a mini-example at the bottom.
Enjoy.
Class SafeInit
SafeInit instproc new {args} { return [next [concat [list -init] $args]] }
SafeInit instproc new-with {args} { set script [lindex $args end] set args [lrange $args 0 end-1] set name [my autoname ::xotcl::stk__#] return [my create $name [list -eval $script] [concat [list -init] $args]] }
# Mix safer [new] and [new-with] into every class Class instmixin SafeInit
# Test:
Class Foo -parameter { {animal horse} }
Foo instproc init {arg1 arg2} { puts "Constructor args: $arg1 $arg2" }
Foo instproc hello {} { puts jou }
# We do not override [create] or the auto-create from [unknown]. Ie. # class creation still works and other stuff :-) puts "Should say 'jou':" Foo ob 1 2 -hello puts "Animal: [ob animal]"
# Instantiate with -dash args set ob [Foo new -hellou world] puts "Animal: [$ob animal]"
# Instantiate with some presets puts "Show say 'jou':" set ob [Foo new-with 1 2 { my hello my animal cat }] puts "Animal: [$ob animal]"
I've now also documented the issue and the solution I'm using on Tcl's Wiki, for reference:
Feel free to disagree but I wanted that security in my code :-)
Hi Kristoffer,
i just saw a buch of mails from you, i guess, you have answered by now most of your questions. Your example is perfectly fine. The reader should be aware, that you traded in "new" object parameterization against arguments to init, which is (in my opinion) bad.
In the example with just "new"
Class SafeInit SafeInit instproc new {args} { return [next [concat [list -init] $args]] }
# Mix safer [new] and [new-with] into every class Class instmixin SafeInit
# Test: Class Foo -parameter { {animal horse} }
you have lost all means to pass a value for "animal", except when you pass it to init (i know "new-with" is different). One can can certainly say, the first argument passed to init is the "animal", then default handling is ugly (in the general case) and you have to deal with ugly positional arguments. This technique does not scale: What, if one inherits additional parameters from a superclass (of Foo), or when the superclass is extended? If every argument to init corresponds to a parameter, one has to extend the signature of the involved init methods. Adding arguments is not really an option when the parameters are provided via mixins, or when the object-class and class-class relationships can changed dynamically. Another issue is passing arguments of init to superclasses via next. If the inits of the superclasses have different signatures, the code becomes error prone.
My hypothesis is, that arguments for init are the poor men's approach for object parameterization, if one has nothing better. Arguments to contructors come more often into discussion when porting c++ style programs to xotcl, rather than from intrinsics needs when coding in xotcl.
Guess, you will like the next xotcl release. The new object system has scripted initialization (quite similar to your "new-with" example) while preserving parameterization (highly orthogonal object and method parameterization, same code for scripted methods and c-implemented methods).
Below is an example to define a classical stack in the new syntax...
all the best -gustaf neumann
*Class create* Stack {
*:method init* {} { *set* :things "" }
*:method* push {thing} { *set* :things [*linsert* ${:things} 0 $thing] *return* $thing }
*:method* pop {} { *set* top [*lindex* ${:things} 0] *set* :things [*lrange* ${:things} 1 end] *return* $top } }
On 5 Aug 2010, at 20:59, Gustaf Neumann wrote:
you have lost all means to pass a value for "animal", except when you pass it to init (i know "new-with" is different). One can can certainly say, the first argument passed to init is the "animal", then default handling is ugly (in the general case) and you have to deal with ugly positional arguments. This technique does not scale: What, if one inherits additional parameters from a superclass (of Foo), or when the superclass is extended? If every argument to init corresponds to a parameter, one has to extend the signature of the involved init methods. Adding arguments is not really an option when the parameters are provided via mixins, or when the object-class and class-class relationships can changed dynamically. Another issue is passing arguments of init to superclasses via next. If the inits of the superclasses have different signatures, the code becomes error prone.
Gustaf, I'm not sure what you're getting at as that is exactly what [new-with] is for. With that I can do parameterisation outside of [init]. The idea is that then it is completely explicit that that is what I wish to do, and it has a fixed argument within which that takes place. So I use [new] when I do not need parameterisation (so I don't have to give an empty argument for [new-with]), and then [new-with] when I want parameterisation. Thus both cases are safe and explicit.
To reiterate: [new-with] is a script executed within the Object's environment (with [eval]) before [init] is called. Its job is to configure the object. It thus does mostly the same job as the normal parameterisation, but with a different approach.
The only reason for [new-with] is that I could not come up with another way to make absolutely explicit what the intention of an argument is, without bad data causing issues. That is why I was rambling with the list of suggestions, with different solutions. Each option could be made to work, so just a matter of taste.
I ended up with [new] and [new-with] for my own code for now, but offered the other options in my mind for discussion.
My hypothesis is, that arguments for init are the poor men's approach for object parameterization, if one has nothing better.
Indeed, which is why I did not want to take them away. Perhaps this experiment of mine won't work well in practise. In particular, passing values from variables into the [new-with] script might end up being a nuisance. But for now it gave me peace of mind.
Arguments to contructors come more often into discussion when porting c++ style programs to xotcl, rather than from intrinsics needs when coding in xotcl.
You may be right and that I should be using parameterisation more. Of course even then you do need to be careful what you pass.
*Class create* Stack {
*:method init* {} { *set* :things "" }
*:method* push {thing} { *set* :things [*linsert* ${:things} 0 $thing] *return* $thing }
*:method* pop {} { *set* top [*lindex* ${:things} 0] *set* :things [*lrange* ${:things} 1 end] *return* $top } }
Hm, am I right in assuming the *s are just something funny when you copy-pasted from your editor, or is the plan actually to have it look like that? I can feel a few eyebrows being raised if so! :-)
Am 05.08.10 23:28, schrieb Kristoffer Lawson:
On 5 Aug 2010, at 20:59, Gustaf Neumann wrote:
you have lost all means to pass a value for "animal", except when you pass it to init (i know "new-with" is different). One can can certainly say, the first argument passed to init is the "animal", then default handling is ugly (in the general case) and you have to deal with ugly positional arguments. This technique does not scale: What, if one inherits additional parameters from a superclass (of Foo), or when the superclass is extended? If every argument to init corresponds to a parameter, one has to extend the signature of the involved init methods. Adding arguments is not really an option when the parameters are provided via mixins, or when the object-class and class-class relationships can changed dynamically. Another issue is passing arguments of init to superclasses via next. If the inits of the superclasses have different signatures, the code becomes error prone.
Gustaf, I'm not sure what you're getting at as that is exactly what [new-with] is for. With that I can do parameterisation outside of [init].
please read my mailer slower. i have excplicitely refered to the consequences of "new", not "new-with".
So I use [new] when I do not need parameterisation
well, some other might wonder how to achieve it.
(so I don't have to give an empty argument for [new-with]),
in the new syntax of xotcl, the scripted initialization is optional, so there is not need for an empty argument.
*Class create* Stack { ... }
Hm, am I right in assuming the *s are just something funny when you copy-pasted from your editor, or is the plan actually to have it look like that? I can feel a few eyebrows being raised if so! :-)
This was a cut&paste from ff4b2 to thunderbird, from an example of the migration guide. The pasted text was still ok, but it seems that upon send thunderbird used *...* to indicate bold...
-gustaf neumann