TADS3/adv3 object initializer pattern?

Another am-I-missing-something-or-does-this-not-exist question: is there a C/JavaScript-ish initializer pattern in T3? that is, something like:

     local obj = new SomeObjectClass(object { foo = 'foo', bar = 'bar' });

…or whatever, where the expected behavior is that obj.foo and obj.bar would be set appropriately.

I could obviously kludge something up in the constructor, but I mean some language/compiler construct.

2 Likes

All the times I’ve seen stuff like this, it’s usually been named optional arguments in a constructor.

2 Likes

Same. Which is a bit inconvenient when you’ve got like a dozen properties and you only want to twiddle, like, the seventh.

1 Like

Make the constructor accept an anonymous func ptr that will be executed in construction? And name the func param if necessary?

local obj = new Blob({: foo = 'foo', bar = 'bar'});
2 Likes

Oh my gawd, does that work??? :open_mouth: That’s wild!

1 Like

I didn’t test it, but if you have

construct(func) {  //OR construct(arg,arg,func:?)
   //stuff
   if(func) func(); 
   }

seems like it should work? Of course you have to make your class constructors look for it… the language won’t inherently take an anonfunc as a constructor argument…

1 Like

Unfortunately that’s assigning properties on the anonymous function, not the object.

2 Likes

@inventor200
My bad… I was away from a computer and just guessing. But it was almost right, because you can do this (I just tested it):

class Blob: Thing
    fizzed = nil
    flozzed = true
 	construct(func) {   // or (arg,arg, func:?)
		//stuff/inherited;
		if(func) func(self);
	}

someCode {
   local b = new Blob({x:x.fizzed = true, x.flozzed = nil});
   //use b
   }
3 Likes

That works, but at that point I don’t think you’re saving much typing over doing it the way you do it without a builtin initializer pattern, something like:

        local obj = new SomeObjectClass();
        obj.foo = 'foo';
        obj.bar = 'bar';

And stylistically using a function there is a little awkward because it makes it difficult to pass the configuration object to multiple functions/methods to twiddle individual properties, a la:

        local cfg = object {
                foo = 'default',
                bar = 0
        };

        // Maybe twiddles cfg.foo
        globalConfigFunction(cfg);

        // Each increments cfg.bar by some amount
        forEachInstance(ImportantClasses, function(o) {
                o.localConfigMethod(cfg);
        });

        local obj = new SomeObjectClass(cfg);

…where the idea is that you’re starting out with a default config value, applying some global constraints/checks/whatever, and then allowing individual objects/subscribers/whatever to tweak the config. That’s the sort of case that lead me here…a factory singleton that cobbles together a config based on various elements of game states and then coughs out a instance configured appropriately.

For that sort of thing I’m leaning toward allowing an anonymous object as an argument to the constructor, and the having the constructor iterate through the properties on the object and/or creating an instance of a dedicated config class that does so. In addition to allowing multiple callers to decorate the config object, that also has the advantage of encapsulating all the logic in one place.

2 Likes

Would a factory method/object work for these cases?

Yeah, like I mentioned above putting together a factory singleton (specifically to handle setting up in-game card games) is the reason why I started the thread.

1 Like

Ah! Forgive my poor memory, lol.

Ah, yup! Blanked this out. Probably should be on meds before entering coding discussions.

No worries.

I started out with a method on the factory object that just had a very long argument list. That started out working…adequately. But as the number of arguments and the number of test cases increased, it rapidly evolved into something where I had to constantly check my own notes to keep the usage straight. And even then I was routinely ending up spending time debugging things that turned out to be because I’d reversed the order of two arguments or whatever.

Anyway, the sort of thing I’m leaning toward using now is something like:

class FoozleCfg: object
        stringProp = 'default'
        intProp1 = 0
        intProp2 = 1

        construct(cfg?) {
                inherited();

                if(cfg == nil)
                        cfg = object {};

                cfg.getPropList().forEach(function(o) {
                        if(!cfg.propDefined(o, PropDefDirectly))
                                return;
                        if(!self.propDefined(o))
                                return;
                        self.(o) = cfg.(o);
                });
        }
;

That is, a dedicated config class that takes an arbitrary object as an argument to the constructor.

2 Likes

You can use:

local obj = object: SomeObjectClass { foo = 'foo'; bar = 'bar'; };

The object: syntax behaves like a new expression. See:

https://www.tads.org/t3doc/doc/sysman/inlineobj.htm

Note though that if you misspell a property (or method) name, you won’t get an error. Instead, a new property/method will be defined for the created object.

2 Likes

Inline objects seem to have a lot of promise, but I have remained gunshy of using them, because I tried using them early on and ended up with freakish errors that seemed to be in the VM or memory allocation. Since then I’ve just gone straight to finding ways to code things without them.
I’d like to believe they could be reliable, though…

1 Like

Ah, cool.

I think I might use the “complicated constructor” method instead anyway (just to be able to catch/flag cases where the config object wants to set a property that isn’t part of the config’d class’ definition) but that’s good to know.

Yeah, you need to think about it as dynamic object definition rather than just instantiation. Just like you can misspell an identifier in a normal, top-level static object definition, you can do the same here. But sometimes, that’s exactly what you want for stuff like:

local obj = object: Thing {
    name = 'thing'
    desc = "A thing. "

    dobjFor(Take) {
        verify() { /* ... */ }
        check() { /* ... */ }
        action() { /* ... */ }
    }
};

obj.initializeVocabWith('thing');
obj.moveInto(someRoom);
1 Like

Yeah. In my case I’m mostly worried about “pure” instantiation/initialization usage for testing purposes: creating a bunch of instances by starting with a “base” config and then iterating through a bunch of variations, to run tests on all of them and comparing the results.

1 Like