User:Colby Russell/Static initializers

From triplescripts.org wiki

Since static initializers aren't allowed in the triple script dialect, what about making way to use selective reification as an easy substitute?

One example is in ZodLib, where we don't pre-compute the CRC-32 table, which almost certainly leads to performance degradation that wouldn't be a problem if we had static initializers.

On the other hand, people go crazy with every affordance being used in the most ill-advised ways, and static initialization is a top recurring problem. Maybe we can finesse the dialect/ecosystem in such a way that people perceive selective reification to be too heavyweight to be used as casually as it is static initialization shows up in, say, Java, JS, Oberon, Go, etc.

Highly relevant to some notes I took yesterday:

Right now, we say that there is a prohibition on global shared state, but at the same time, we allow the browser and NodeJS layers to cheat—through guarded access following a successful typeof check, but it's still cheating. This seems to be privileging the host platforms over well-behaved modules. I can never write an orinary module, for example, to do "global" mutations on shared state. Maybe we do need a way to allow for this, but still mandate that this only occur in the host layers. (Do we need some provision in the dialect to annotate this stuff?) Or maybe we can just defer to treason + selective reification? (Is that just hand waving things away?) In my notes, I have been floating the idea of doing namespaces by fixing them up into classes and autogenerating a constructor. Maybe there's a good anchor point there for this, too. XXX Gilad keeps calling these "aliens" in the Channel 9 video about Newspeak.


2020 July 06: Okay, so thinking about this more, it seems like there's a pretty clean way to do this with selective reification, by establishing an activation protocol. This can wait for the work on treason first, but in principle, different runtimes can establish their own activation protocol.

(Side note: Yesterday I came to the conclusion that the kind of annotation mentioned above is essential. Viz $unsafe$.)

Here's one sketch of how an activation protocol can work:

Lifecycle includes with two phases:

  • Awaken
  • Explicit activation (if requested)

The runtime manages the awaken phase, with the shunting block detecting/responding to some "ambient" symbol, which can be used to establish communication. If explicit activation is desired, the bundle can use this channel to send a message requesting such activation, which the environment will dispatch to the bundle at the appropriate time. Request for activation can also be accompanied by the name of the bundle's own symbol that will handle activation (defaulting to an activate function otherwise). The activation handler when called will itself get a direct reference to some messaging facilitator as its argument (called post by convention).

The initial specially-defined symbol can be something as simple as a Treason global (which the shunting block finds by way of an explicit typeof check) followed by ambient instantiation of Treason.Post objects. (The post facilitator that the activation handler receives will just be a fancy wrapper for these Post ("Message"?) instances.)

In the general case, this can also be used to control things like lifetimes, module persistence, whether or not (e.g. in the case of sepsis-inspector) the viewer would like to be initialized in a new context. This would be a general solution to the suppliers-and-synth-blocs and module.singleton hack that I originally implemented in sepsis-inspector's RequireJS/CommonJS-like module loader. What's really nice is the _same_ activation protocol can be used for viewer registration—we don't need such things as bindings to the XPCOM category manager (and of course we've already obviated the dubious JSON schema-based design I originally came up with). For example, to install a new viewer, you either drag its bundle into the window, or point the Inspector at its URL, and it does a similar dance to awaken the bundle and the bundle begins sending messages to control/facilitate installation.

I particularly like the ambient Post instantiations during awakening. It hews closely to Alan Kay's biological metaphor of objects as cells. The bundle's concern is only with manufacturing the appropriate products, in response to some stimulus, but then otherwise sets them adrift without worrying about where to send them. (The overall environment is responsible for making sure they end up at the right place, and in our case of course we make sure that this process is deterministic rather than a crapshoot—after all, it wouldn't be very attractive if we were only promising a chance that the thing the user wants to happen actually ends up happening.)


2020 July 08: There are also restrictions on simple static assignments' righthand form. I eventually want static asserts, for example

There are also restrictions on other static contexts. We'll eventually want static assertions, for example, but they're of limited utility if they can't encode semantics. (I originally assumed that static assertions would be restricted to the same kinds of constructs that are permitted in static assignments' righthand form.)

A use case: in the new lexer, I want to be able to assert that the constants selected for Scanner "enums" don't collide with the constants selected for Token. Even with the convention that Token's constants are no less than, say, 101, it sure would be nice to both communicate the relationship between carefully selected constants to other programmers and getting machine verification at the same time.

NB: There are also ordering concerns, similar to those we ran into for extends, based on TC39's decision to make class hoisting differ from the way function hoisting works. This means we have some difficult choices:

  1. forbid cross-module references for top-level scope (no Scanner.WHITESPACE = Token.WHITESPACE, for example; we could make an exception for where we're already handling ordering issues, such as if A extends B, then A.FOO = B.BAR is permitted, but that seems sketchy)
  2. forbid them as above, but give static assertions special powers and by necessity sequester parsing issues with the mandate that they appear only as a triple slash directives
  3. allow everything forbidden in (1), so long as there's a way to do re-ordering that eliminates the problem, and then have the compiler do it

Of these choices, I like (3) the least, because it also compromises our principle of "you can keep all the rules in your head, and whatever the compiler does you could do yourself by hand if you wanted to" and it leads to code that isn't top-down. And I don't especially look forward to having to implement a satisfiability solver even in the compiler.

2020 July 26: Another way of describing state, especially to alien programmers: the runtime (more specifically, the thread[XXX] of execution kicked off by `main` [or what-have-you]), combined with the filesystem is your state, and you don't have arbitrary write access to the filesystem for state persistence, so it's best to keep this in mind while designing your programs. Unlike JS or, say, Modula, there is no module state, because module state implies top-level state (and indeed, the entire notion of module state is just another way to avoid saying "global state" by trying to cast it in a way that people won't notice), and top-level state is prohibited by the declarative nature of the t-block file format.

2021 January 18: The simple way I've chosen to deal with this is to instantiate aliens and then make them accessible as a property of the caller's `statics` property. For example, in triickl 0.11.0 primary module's constructor, we have:

   this.statics = Triickl.initializeForeignModules();

... and then elsewhere:

   const { sha1 } = this.statics;

... which we can use within that method as `sha1(foo)`. I like this a lot better than the magical awaken/activate pattern detailed earlier. Perhaps better still would be if these were initialized in the shunting block and accessible from the `statics` property of the system interface? This does introduce a disconnect between the code that actually uses it and the place where the module is imported, however... So maybe the system layer should allow a QueryInterface-like call? The module that uses it would import the constructor, which would be expected to conform to a certain contract (a no-arg constructor), and the system layer can lazily initialize a given constructor if we check against a weakmap and find no entry.

2021 January 21: reified "global" states should probably be called something like "habitats". They're created somewhere around main during program initialization and then passed explicitly. To get habitats on the cheap, maybe the convention should be to pass the habit to the host support modules during initialization of the system layers, and application code can access its facilities via `system.habitat`. I considered "environment", but it seems too verbose.

Cookies help us deliver our services. By using our services, you agree to our use of cookies.