Thinking more about my IF variable-binding issue, it was to do with the quest to allow the user to represent knowledge in as simple a form as possible, with as little boilerplate repetition as possible. Using per-predicate (or per-function) variables seemed to require lots of repetition of those variables. Doing “the right thing” in terms of modern API design, violated “don’t repeat yourself” in a way I didn’t like.
For example, an action in an IF game often boils down to a bunch of context-specific variables: “actor” (the NPC who is doing the action), “verb” (the thing being done), “noun” (the object the verb is being done to), “second” (possibly another object involved in the action), etc.
To anchor this to the thread’s title: an “action” in an IF game might be a little like an “effect”. At least, in a very oldschool language like Inform 6, actions (if they’re run) pretty much do immediately change the state of the simulation, so they are super side-effectful.
If you define an action as a pure function or even a Prolog predicate - which is a nice declarative way to do it, and if you’re trying to use actions in AI type code where the NPC will want to reason about them without immediately causing changes in game state, you’ll want to be as pure-functional as possible - then you need to pass all of that context in on every invocation. And define those context variables every time. If as well as an “actor” you also want to pass in and return a “world” (a data structure representing either the world, or changes to it, for speculative AI-style planning), then it gets more and more complex.
Inform 6, being extremely 1970s-style old-school in its approach, just used global variables for things like “actor”, “verb”, “noun”. That turned out to be really terse and easy to understand! You don’t need to pass “actor” in as a parameter on every call. That doesn’t work if you’re trying to indirect an action for AI, but it suggests the part that’s intuitively missing: “implicit” parameters which don’t need to be defined when you’re defining the function, but only when you’re calling it. Since you’d be defining many times but only calling once, and the definitions would be user-facing (needing to be as simple as possble) while the call would be in library code.
Emacs, I think, has a similar problem / solution, where its extensions use brute-force old-school global variables or even dynamic binding.
But of course dynamic binding is super scary and prehistoric today - and probably has some really bad security properties.
That’s where my thinking stopped because it was a wall. But it is relevant I think to the problem of extensions. How do we get the upsides of dynamic binding without the security risks?
One possibility is maybe to pass one “context” parameter in on every function/predicate call, that contains an object with all those implicit variables.
Another way maybe is to use FEXPRs/vaus/macros, ie functions which take the entire calling environment as a parameter. That might though expose much more local/private context information than is safe, and the function definition still has to define that parameter.
Another approach - and perhaps the most terse, the least DRY-violating - is to avoid variables altogether, and instead pass around higher-order functions or (in a Prolog) higher-order predicates. These functions/predicates would have to take just one parameter, which could be a list/stack (as in Forth/Joy/Factor/Uxn) but might instead be an object/dictionary like structure. A “context” might be such a structure (it would have named components, so it would be more dictionary-like than stack-like). So eg for defining preconditions and effects of an action in a simulation (or for controlling a robot or a personal desktop agent of some kind), you might have a rule that takes and returns not quite a set of literal states and changes of the world, but rather functions/predicates over that world and/or context. (Where a context might include details about the action that the robot/agent/NPC is planning to do, not just the simulation/world itself). Then all the functions/predicates - the parts that a user might write - would have their name-binding happen at a very late stage, at the point where an AI doing some planning whips up a speculative imagined context, and passes that context to the planning function.
This last idea might be the sort of thing that Alan Kay is speculating about in his recent writing about objects. The extreme late-binding that Kay likes, is what appears when you start looking at AI-style planning like this.
However, I haven’t seen any actually-existing OOP environment, even Smalltalk, which makes using objects for knowledge representation simple and pleasant. There’s always (to my eye) still a lot of awkward redundant domain-irrelevant boilerplate in writing the method calls, which to me says we haven’t got to the core of the good idea yet.