This response comes a bit late, but you bring up so many interesting points that it has taken me a while to digest everything. First of all, of course, thank you very much for the delightfully specific praise! These design decisions are the result of much deliberation and iteration, and the language went through a lot of drastic transformations before going public. But none of that is described in the manual, which of course tries to describe what the language is, rather than what it isn’t. So it’s very satisfying when someone “gets it”, and provides accurate context for these design decisions in their own words.
This is an interesting idea, and not necessarily all that expensive to implement. The compiler could figure out statically which now-statements might get invoked during such a “dry run”, and add the necessary conditional instructions. But, well, this is not a feature to add carelessly. If it is possible to disable all side-effects dynamically, it might become tempting to put side-effects all over the library code, and expect the caller to be careful. I think that might lead to code that is harder to read and debug. But it’s an intriguing idea, and I’ll probably keep it at the back of my mind.
Closures provide some of the functionality of passing around prolog terms, but it’s not possible to build source-code structures from scratch, as data, and then execute them. That would have to be done using lists somehow. But I’d like to see a concrete example of where it would be useful in a story authoring context, before complicating the language any further.
This is a good point. Certainly, well-written I7 code (at least when it deals with concrete objects in the game world) can convey the purpose of the code to the reader in a very efficient, natural manner, even if the reader is new to the language. That is one of the big strengths of I7. But consider the question: Why are we reading the source code? Sometimes, indeed, we do it in order to glean the original intent of the programmer. But quite often, we read code because we’re trying to understand why it doesn’t work, or why it doesn’t behave in the way that we expected. In those situations, I find that it is immensely helpful to be able to see what the code actually does instead of what the programmer thought it would do. And the more we learn about what the programmer intended, the more we tend to see the code through the eyes of the programmer, and therefore miss the bug for the same reason that the programmer missed the bug. I’m not trying to say that we should design programming languages to be cryptic. Clarity is vital, but it should be clarity about what the program actually does, formally, when you run it, rather than clarity about what the programmer was attempting to do. In my personal, possibly controversial opinion.
This is a good point. Looking at @natecull’s subsequent suggestion, I don’t see a need to put predicates into lists. One could instead do:
(public (my public predicate $))
(public (my other public predicate $))
Or one could make everything public by default, and have a private
meta-declaration. But perhaps an even better approach would be to reserve a special syntax for private predicate names, e.g. a colon as the very first character. Such predicate names would be treated as local within the current source code file. Something like:
(my public predicate $X into $Y)
(:my private predicate $X accumulating [] into $Y)
Another file could then use (:my private predicate $ accumulating $ into $)
, and it would be a different predicate. Cluttering predicate names with special characters may look bad, but the benefit is that the information is present where it needs to be, at the call site, and there’s little risk of forgetting to add a public/private declaration elsewhere in the file. Well, these are just idle musings for now, but perhaps in the future…
The syntax for closures went through a lot of revisions before I settled on the current one. In fact, I’ve considered several of your suggestions. But consider this:
What you do instead, right now, is this:
(query { ($_ = [$A $B]) (do something to $A giving $C) (something else $C $B)} [#foo #bar])
I think the latter is quite readable as it is. Though, by all means, you could define the following helper predicate:
(query $Closure $X $Y)
(query $Closure [$X $Y])
Perhaps I misunderstand you here, but it is certainly possible to put closures in dynamic predicates, because closures are just lists. However, all variables must be bound.
Hmm. Perhaps. It takes the language in an object-oriented direction, and I’m quite happy to be rid of that baggage. The feature is already there, in a sense, but the syntax is cumbersome as you’ve noted. And that will dissuade programmers from thinking in terms of black-box objects and classes, and encourage white-box modelling with rules.