IF Authoring, Array Programming, and Fourth-Generation Programming

It can be useful, but in a complex application (not necessarily interactive fiction, but applications in general) it really has a way of becoming constraining when you need to expand sideways, rather than downward. I can explain in more depth if you like, but I have to run…there’s a driveway full of snow and a shovel with my name on it…

1 Like

Sorry for the naive question, but can you explain in a couple sentences how implementing an interface is a different thing from multiply inheriting from it? I haven’t worked with interfaces before, and I just read in a Java book that only single inheritance is allowed, but a class can also implement an interface. I don’t have the exposure yet to know how that is different from basically also “inheriting” from the interface class…

That is a good thought.

In my humble opinion (I have not completed as many projects as others here) TADS is the exception that proves the rule. Objects with multiple inheritance are great for modeling objects in a world that stands on code. But when I have tried to code solutions to real-world problems, I have literally never thought, “Wow! This is the sort of problem that multiple inheritance was made for!” And even when writing IF, OOP is not the only simple solution to the problem of creating objects amenable to hierarchical categorization. Coding in Dialog has really driven this point home for me.

1 Like

OK, but what are you modelling?

Suppose you have chosen Python. Congratulations! You now have the power of multiple inheritance.
The Innkeeper can inherit from both the Character class and the Mercantile class.
He can talk and charge you money at the same time.

But hang on. Later in the story, the Innkeeper is kicked out by bailiffs and is no longer an Innkeeper, but becomes a travelling Impressario.

Python doesn’t like that. It is very good at multiple inheritance, but objects can’t change their classes at run-time.

So, while Python multiple inheritance is a powerful enabler for a game engine, it’s not the best choice for a game world.

1 Like

With multiple inheritance and virtual methods, you can get some nasty scenarios.

Look up the “diamond problem”. That’s the reason C# does not support multiple inheritance (C++ does).

There’s also issues with the order in which fields are created, initialized, and what virtual methods due during object construction. It can be quite messy.

Programming to multiple interfaces can be much cleaner.

Is the fact that TADS can change an object’s superclass list at runtime a special feature then, compared to other programming languages?

But Python does support changing an object’s class at runtime!

If you have an object innkeeper, and innkeeper is initially of class Innkeeper, then that relationship is stored in a special attribute of the innkeeper object, which is innkeeper.__class__. That attribute just points at the Innkeeper class object itself (after all, everything, including classes that serve as factories for class instances, is an object), and it can be dynamically reassigned at runtime as simply as innkeeper.__class__ = Impressario. (Note that in this theoretical example, there are no parentheses after the new class; it’s not innkeeper.__class__ = Impressario(). You’re not reassigning a new object of class Impressario to innkeeper's __class__ attribute; you’re referring to the class object itself.)

This is arguably black magic and/or the heinous practice of monkey-patching, and there are a lot of reasons to think it’s a bad idea (at least, unless you’re really, really careful about how you’re doing it). It can lead to hard-to-track-down bugs. In particular, you’d want to make sure that the Impressario class implements, directly or by inheritance, all of the same methods that the Innkeeper class does, or at least all of the methods that are going to be invoked on it for the rest of the game. If your code depends on accessing non-callable class attributes, that can get kind of hairy too. There are probably other gotchas and edge cases. But it’s doable.

I’ve got a partially written piece of parser IF that takes advantage of this (and which I’ve shelved until I’ve finished working on this Inform piece, though it might see the light of day … someday …) that’s at about 18k lines of Python, and the problem isn’t reassigning class at runtime; nor is it trying to deal with the diamond inheritance pattern (which you can in any case resolve in Python at runtime with something along the lines of innkeeper.__mro__; I will be the first to say that the full details of exactly how Python resolves diamond inheritance patterns are pretty mind-bending when you start taking metaclasses and descriptors into account and I hope if I ever get onto a quiz show that I don’t get any abstruse questions about that topic). The problems writing parser IF in Python are the classic problems of writing IF in a non-IF language: you have to reinvent the standard library; and you wind up assembling output strings in awkward ways. Python adds the additional awkwardness of needing every object to be the instance of a particular class, and having to do

class Innkeeper(Human):
    _sex = "male"
    _name = "Happy Harry, the Heartwarming Host"

    def __init__(self, **kwargs):
        # initialization code ...

    def ask(self, topic):
        # handle asking about TOPIC ...

    # more methods ...

before you can create an instance of the object with innkeeper = Innkeeper() starts to feel awkward when most of the objects in the game world are, after all, singletons.

But it’s doable, and it’s totally possible to reassign object class at runtime in Python.

An interface is just a formal contract that promises that the class using it implements a known set of methods, with (generally) agreed upon outcomes. The classes that use that interface don’t have to have any other relationship to each other, so one class doesn’t need to be derived from another.
eg. (a silly example, in no particular language)

interface Patible {
	function pat()
}

class Cat implements Patible {
	function pat() {
		/* some outcome relating to patting a Cat */
	}
}

class CoffeeTable implements Patible {
	function pat() {
		/* some outcome relating to patting a Coffee Table */
	}
}

note: Depending on the language an class can have multiple interfaces assigned to it.

1 Like

If every class that implements an interface has to define its own version of all the interface’s methods, is the interface actually doing anything? Is it just like a marker so that other code will only ask a class to do such and such if it implements SomeInterface?

1 Like

Yes, I’m aware of this technique. It’s ill-advised, in my opinion. I wouldn’t encourage anyone to use such a hack, really.

1 Like

Exactly. Interfaces guarantee that an object can perform some action. In many programming languages, an interface contains no logic itself, just a list of function names and signatures. Then any function that accepts an argument of that type of interface can be passed an instance of any object that implements that interface.

That makes sense… thanks for the clarification…

For an interpreted language like JS or Python, interfaces are pretty much just documentation. Indeed, for a long time Python APIs would say stuff like “pass in anything that acts like a list, it’ll be fine” rather than having a formal “list” interface. (This eventually got a little more structured!)

For a compiled language that runs in a runtime, like C# or Java, interfaces are documentation that can be automatically checked at compile time. The compiler can tell you when your code doesn’t match its formal interfaces. This is great if you’re in the mood for that sort of thing. (When I’m writing Python, I’m in a different mood…)

For a fully compiled language like Go or Rust, interfaces are far more important. The compiler needs to be able to generate binary code that manipulates objects that haven’t been defined yet – all the compiler knows what interfaces they will support. This turns out to be extremely tricky. The idea of interfaces appeared in the early OO era (Java had them from the beginning), but it took fifteen or twenty years before people thought it was practical for a fully-compiled language to really use them. (Rust/Go/Dart all launched around 2010.)

(ObjC was a bit of an intermediate step. It’s almost fully compiled; there’s a sort of micro-runtime which just handles reference counting and flinging method calls into objects. So its object model is a lot like C# – flexible and dynamically dispatched – but outside of that, the compiler is generating native code. This turned out to be a terrific compromise; it was one of the reasons people picked up early iPhones and said “How the heck are you getting this performance on a crappy ARM chip?”)

(Now it’s one of the reasons that people pick up M1 Macs and say “How the heck are you getting this performance on a desktop machine?” Apple’s ARM chips are reputedly designed to optimize that “reference counting and flinging methods” microcore – which also exists in Swift. So M1 Macs are actually engineered from the silicon level to run Swift/ObjC apps faster.)

3 Likes

Thinking about it further, and given Python lets you create classes on the fly with the type function, some sort of morphing copy-constructor approach might work. This kind of thing maybe:

#!/bin/env python3
# encoding: utf-8

from types import SimpleNamespace


class Base(SimpleNamespace):

    classes = {}

    def become(self, name, *args, **kwargs):
        self.classes[name] = type(name, args, {})
        data = self.__dict__.copy()
        data.update(kwargs)
        return self.classes[name](**data)

class Character(Base):

    def say_name(self):
        return "Hi, I'm {0}.".format(self.name)

    def say_type(self):
        return "I'm a {0}.".format(self.__class__.__name__)

class Mercantile:

    def charge_money(self):
        return "{0} pennies please".format(self.price)

class Host:

    def offer_service(self):
        return "What can I get for you?"

class Entertainer:

    def offer_service(self):
        return "Would you like to hear a story?"


bill = Character(name="Bill")
print(bill.say_name())
print(bill.say_type())

innkeeper = bill.become("Innkeeper", Character, Host, Mercantile, price=12)
print(innkeeper.say_name())
print(innkeeper.say_type())
print(innkeeper.offer_service())
print(innkeeper.charge_money())

impresario = innkeeper.become("Impresario", Character, Entertainer, Mercantile, price=4)
print(impresario.say_name())
print(impresario.say_type())
print(impresario.offer_service())
print(impresario.charge_money())

This gives the output:

Hi, I'm Bill.
I'm a Character.
Hi, I'm Bill.
I'm a Innkeeper.
What can I get for you?
12 pennies please
Hi, I'm Bill.
I'm a Impresario.
Would you like to hear a story?
4 pennies please
1 Like

This is turning into a bit of a tangent – the one question the original poster didn’t ask was “What features would you like in your IF language?” Nonetheless!

When it comes to mutating objects on the fly, my preference is to discard the notion of “class” (or “kind”) entirely. Classes are notionally, if not always practically, a construct of immutable identity.

I’d rather talk about properties and logical implication. In I7 terms, rather than saying

[This is almost verbatim from the I7 Standard rules. I added the default description for this example.]
A room is a kind of object.
A room has a text called the description. The description of a room is usually "You see nothing here."
A room can be visited or unvisited. A room is usually unvisited. 

…we should instead be able to say:

An object can be a room.
A room [object] has a text called the description. The description of a room is usually "You see nothing here."
A room [object] can be visited or unvisited. A room [object] is usually unvisited. 

…and then beef up the implication system so that it can handle Now foo is not a room; now foo is a container and get to the right answers.

I7 is tantalizingly close to this. It has properties; properties can logically imply other properties; you can condition rules with properties just like you can with kinds. It’s just a bit too compile-time about it. And then you need an inference model which actually stands up under the load, which is the hard part.

5 Likes

All very true. However, the problems with creating new class on the fly with type or another metaclass-like technique is that it complicates file-saving in several different ways (you now need to store not just a reference to a hardcoded class, but the information that was used to create the class in the first place); the problem with creating a new object instead of mutating the type of the old one is that you then have to track down all references to the old object and replace them with references to the new one. (Is Bob the Impresario “familiar” in the sense of Eric Eve’s Epistemology extension if Bob the Innkeeper was?) All that being the case, monkey-patching an object’s class, dark magic or no, seems easier provided that (a) only one programmer is working on the project, so there are no unexpected surprises; and (b) that programmer is willing to think carefully about the logic involved in attribute searches, which can be kept substantially simpler by not using descriptors and by not depending in any way on inheritance of metaclass attributes.

But I stand by my original claim: Python’s a wonderful language in a lot of ways, but it’s not ideal for writing parser IF.

1 Like

Karona,

  1. Most of the time, programs (I prefer Java) do not need to handle large amounts of text. I like the way IF7 does it. I like how they handle pages of text as objects or scenery. I like how easy it is to establish synonyms with Understand clauses.
  2. IF7 is severely handicapped because it does not have an IF-THEN construct. There are way too many conditionals that IF7 does not or cannot handle; or the workarounds are inconvenient or at best, awkward.
  3. If you mean functional programming when you ask about fourth-generation programming, then Java 9 supports that. I find it not that useful. If it is used to sprinkle in lambda expressions occasionally, then it is convenient but it blurs the theoretical line between object-oriented and functional programming. Barne Stroustrup, inventor of C++, has toured the US warning of the problems inherent in mutli-paradigmmed languages. Makes writing the whole program harder to maintain the design integrity.

If hope you are asking this question looking for what to keep and what to add to IF7: if so, please add support for IF-THEN statements.
-Falsoon

1 Like

Are you looking for the “if” statement described in the manual chapter 11.6 to 11.8?

2 Likes

“Fourth-generation language” has never been used to describe functional programming, as far as I know. Functional programming is twenty years older than that.

As for being multi-paradigm, I’m not sure what that even means. “Functional programming” isn’t a theory; it’s more like a genre. It’s a set of assumptions about what’s important. But genres steal ideas from each other all the time; ideological purity is a phantom.

1 Like

Thank you for your observations about text in Inform 7!

As flattered as I am that you would associate me with one of the most beloved authoring systems of all time, I am sorry to inform (tee-hee!) you that I am not involved in its development.

If you will excuse a question asked to sate my curiosity: If Inform 7 does not offer the kind of if-then functionality you require, have you considered handling this via extensions coded in Inform 6?

1 Like