Creating a custom scripting language
Goto page Previous  1, 2, 3  Next
 
Post new topic   Reply to topic    mudlab.org Forum Index -> Coding
View previous topic :: View next topic  
Author Message
Teelf



Joined: 12 May 2005
Posts: 21
Location: Seattle, WA

PostPosted: Sun Dec 11, 2005 7:23 pm    Post subject: Reply with quote

shasarak wrote:
Multiple inheritance always makes me uncomfortable. It does have legitimate uses, but I find 90% of the times I initially find myself wishing I had access to multiple inheritance it subsenquently turns out that I've cocked up my single-inheritance class hierarchy.

There are two areas where people tend to wrongly think that MI is useful. The first is if they're used to working in C++. C++ is severely broken in terms of its type-checking: if you have a function into which you need to be able pass instances of two or three different classes, all of those classes have to have a common superclass so you can declare the argument as being of that type. This is not, IMO, a desirable feature, it's just a tiresome process one has to go through to compensate for compiler deficiencies. (Even if you favour static typing, a method like C# "interfaces" is much less clumsy).


You are talking about a certain implementation of multiple inheritance and basing your analysis on that. MI does not (or would not) work well in C++, Java, and C# because (among other reasons) they only support single dispatch. I would not recommend doing MI in C++ classes at all. That does not mean MI is a flawed concept all together. MI works quite gracefully in CLOS.

shasarak wrote:
Consider a sword that has a map etched into the blade, and can glow with light, and is alive and intelligent and can speak. People think "well, this object must inherit from classes Map, LightSource, Monster, and Weapon". That actually isn't the right way to do it. You can see why if you think about how the sword behaves when it isn't glowing: when it's "on" it is a light source, when "off" it isn't, and anything checking illumination levels can ignore it.


Your example does not seem to illustrate why inheriting from all those classes isn't "the right way to do it." What if I have a spell that destroys all potential light sources, on or off? How do I turn a my weapon "off"?

shasarak wrote:
The way to handle an object that is a light source is not to have it inherit from a LightSource class, but to create a separate object that is an instance of LightSource, and assign it as a property of the main object. (The LightSource instance not an object in terms of what a MUD player would perceive as a "game object", but in OO programming terms it is an object).


Why is that the way to do it? So then you end up passing a generic 'GameObject' class to all my functions and doing a ton of if/else checks? Can you provide an example?

shasarak wrote:
To a degree, multiple inheritance and this method (which, depending on the details, may be regarded as "containment" or "aggregation") are equivalent; the advantage is that creating new program-objects to model different aspects of a single MUD "object" is a) simpler, and b) dynamic. Languages may allow classes to change their position in the inheritance tree dynamically, but relatively few allow different objects to inherit or not inherit from different classes at different times, and this is something that's very useful in a MUD context.


There is a close similiarity, but the property method gets messy quickly with all the different game logic checks going on. Each method can be as equivalently simple to use (that's just a UI issue) and dynamic. I have a shell a player can log into that dynamically messes with the class hierarchy (adding new classes, chaning parents, etc). A GUI version could be made of it as well.

So if someone is making a custom scripting language, my vote it for including a prototype based OO design with MI and multiple dispatch :)
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address
Author Message
Kelson



Joined: 18 May 2005
Posts: 71
Location: SC

PostPosted: Sun Dec 11, 2005 10:37 pm    Post subject: Reply with quote

shasarak wrote:
Consider a sword that has a map etched into the blade, and can glow with light, and is alive and intelligent and can speak. People think "well, this object must inherit from classes Map, LightSource, Monster, and Weapon". That actually isn't the right way to do it. You can see why if you think about how the sword behaves when it isn't glowing: when it's "on" it is a light source, when "off" it isn't, and anything checking illumination levels can ignore it.


Teelf wrote:
Your example does not seem to illustrate why inheriting from all those classes isn't "the right way to do it." What if I have a spell that destroys all potential light sources, on or off? How do I turn a my weapon "off"?


Not to kill the discussion, but isn't this counter-example flawed from any perspective? Assume there is such a spell, wouldn't it destroy anything since - potentially - anything could be a light source (even in real life...). That isn't to say inheriting from LightSource is the proper way, but there probably isn't a 'best' way in the abstract we're speaking of. For example, if I have a powerful engine, perhaps it really is enough to just assign a property to an object (glows) and the engine handles the rest (this is identically equivalent to attaching a method or class to the object). It might even be best to create a brand new object and recreate all methods to account for the fact that it is now glowing - perhaps.

-Kelson
(I like magic...)
Back to top
View user's profile Send private message Send e-mail AIM Address
Author Message
Teelf



Joined: 12 May 2005
Posts: 21
Location: Seattle, WA

PostPosted: Mon Dec 12, 2005 1:36 am    Post subject: Reply with quote

Kelson wrote:
Not to kill the discussion, but isn't this counter-example flawed from any perspective? Assume there is such a spell, wouldn't it destroy anything since - potentially - anything could be a light source (even in real life...). That isn't to say inheriting from LightSource is the proper way, but there probably isn't a 'best' way in the abstract we're speaking of.


My only point was that the object still possessed the property of being a light source. Not trying to get into game semantics.

Kelson wrote:
For example, if I have a powerful engine, perhaps it really is enough to just assign a property to an object (glows) and the engine handles the rest (this is identically equivalent to attaching a method or class to the object). It might even be best to create a brand new object and recreate all methods to account for the fact that it is now glowing - perhaps.


How the powerful engine 'handles it' is exactly what we are talking about. The interface to the user can be made nearly identical with either the property method or the inheritance method, but my argument is that MI with mutliple dispatch is superior (I never used the word 'best') to setting properties. I am working on some meaningful examples and hopefully shasarak will do the same and we can compare.
Back to top
View user's profile Send private message Send e-mail Visit poster's website AIM Address
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Mon Dec 12, 2005 7:06 am    Post subject: Reply with quote

Okay, a quick example of my suggested mechanism works for light sources, as I obviously didn't explain it very well.

Let's say we have a (rather basic) function to calculate the light level in the room by adding together the total brightness of all the light sources. Using multiple inheritance, anything that is a light source can be identified as such by asking it if it is a light source. A method defined on class Object (ultimate superclass of everything) returns false, a method defined on classes that inherit from LightSource returns true. Such classes also implement a brightness method which returns how bright they are at the moment.

So, to add them all up, we might have a method defined on class Room or Container:

Code:

illuminationLevel

    | totalLight |

    totalLight := 0.
   
    self contents do:
        [:eachObject|
        eachObject isLightSource ifTrue:
            [totalLight := totalLight + eachObject brightness]].

    ^totalLight


(Apologies for the Smalltalk syntax. Smile The caret sign in Smalltalk - i.e. ^ - means "exit the method and return the following value" - the rest is hopefully reasonably self-explanatory).

Now, what are the problems with this? Well, first, we have to define the method isLightSource on class Object. This is a very nasty breaking of encapsulation - it means all objects, regardless of whether they are light sources or not, have to have some basic awareness of the existence of light sources to be able to report that they aren't one.

Worse, there's a big loss of flexibility: if a given class doesn't inherit from LightSource then any instance of that class cannot ever be a source of light. That might not be an issue, but suppose you want to introduce a light spell which, instead of simply generating a floating globe of light, can be cast on any object and make it glow. How can we do that?

Using the inheritance-only model, the only way to do it is to make all game objects inherit from LightSource. Now that's just crazy: 99% of the objects in the MUD aren't sources of light, but they have to inherit from LightSource anyway. Any properties associated with LightSource (variables like brightness, fuel level, fuel used per tick) have to be defined and stored for all objects in the game. How much storage space will that use up? Worse still, properties like how much fuel is used per tick have to be included in the class definition, and cannot therefore vary according to which light spell is used.

Clearly, therefore, if we want light spells to work like this, you cannot code the operation of the spell (or the resulting light source properties) into every possible target. Instead, you have a separate object whose function it is to handle the light-source behaviour of any given game object, and the main game object code shouldn't have to know anything about it.

You don't, of course, have a specific named lightSource property on all objects (which may or may not be set to something) - that's bad encapsulation. Instead each game object would have a collection of properties, stored as a Dictionary (that's a bit like a mapping or a linked list). The object doesn't have to know anything about what its properties are - other objects will query it to see if it has a property of a given sort as necessary.

You therefore have a single generic method defined on Object:

Code:

propertyNamed: aSymbol

    ^self properties at: aSymbol ifAbsent: [nil]


So, if we ask any object for its light source property by asking for
Code:
anObject propertyNamed: #lightSource

that will return either a light source object, or nil (undefined, a null pointer).

So, the illumination level example now looks like this:

Code:

illuminationLevel

    | totalLight |
   
    totalLight := 0.

    self contents do:
        [:eachObject|
        | lightSource |
        lightSource := eachSource propertyNamed: #lightSource.
        lightSource isNil ifFalse:
             [totalLight := totalLight + lightSource brightness]].

    ^totalLight



Teelf wrote:
Your example does not seem to illustrate why inheriting from all those classes isn't "the right way to do it." What if I have a spell that destroys all potential light sources, on or off? How do I turn a my weapon "off"?

Do you mean extinguish all lights, or actually destroy the associated object? Either way the essential mechanism is the same. The light source destroyer might have a method like this:

Code:

extinguishLights

    | totalLight |
   
    self allAccessibleObjects shallowCopy do:
        [:eachObject|
        | lightSource |
        lightSource := eachSource propertyNamed: #lightSource.
        lightSource isNil ifFalse:
             [eachObject destroyYourself]].

(the #destroyYourself method defined on class Object would, among other things, destroy all the object's properties as well).

Similarly, the code to switch the sword "off". The player types:

switch sword off

The game then scans over each object in the player's inventory to see if there is an object that can be described as "sword" and if it or any of its properties understands the command "switch". (Or, if you want a more hard-coded approach, you could code the switch command into some short list of standardised player commands). So there will be some code which (conceptually!) says something like:

Code:
verb: verbName target: itemID argument: arg

    self allAccessibleObjects do:
        [:each |
        (each canBeDescribedAs: itemID) ifTrue:
            [(each respondsToAction: verbName)
            ifTrue:
                [^each perform: verbName with: arg]
            ifFalse:
                [each allPropertiesDo:
                    [:prop |
                    (prop respondsToAction: verbName) ifTrue:
                        [^each perform: verbName with: arg]]]]].

The above is merely intended to illustrate what I'm talking about, not to be a practical piece of code!!!! Smile

Then on the light source object you have a method called #switch:

Code:
switch: arg

    arg == #off ifTrue:
        [^self turnOff].

    arg == #on ifTrue:
        [^self turnOn].

    ^self dodgyArgumentError.


eiz wrote:
Which works fine until you want to have an object (let's call it a 'behavior' object since emitting light is basically such a thing) which significantly alters the function of the object that it's mixed into. Then you're going to end up with all sorts of hooks and manual delegation flying around everywhere.

I don't think so. There's no reason why adding a property to an object cannot dramatically change the main object's state (for example, a disease property on a player might modify the player's stats when created and restore them when a timer expires). And if any specific effect is likely to be handled differently depending on the main object's state then that is precisely the reason why the main game object should not be expected to handle anything that pertains to that effect.

In this example, the "sword" object knows absolutely nothing about light sources at all - it's not even aware that such a thing exists. It's not the case that you are adding an object that modifies the sword's light source behaviour, the sword doesn't have any light source behaviour - all behaviour pertaining to light sources is handled by a separate class that the sword class isn't aware of. So you shouldn't be trying to change the properties of an object by changing its inheritance in the first place - you should be creating or removing other, separate objects to handle completely that aspect of object behaviour while the main object remains blissfully unaware.

Another example: suppose a mage can see invisible objects, but his fighter colleague cannot. The mage might cast a light spell on an invisible enemy making it glow, and thus making it visible, even though the invisibility spell remains in effect.

This is not handled by making the light and the invisibility effects interact with each other - in other words, you don't say:
Code:
enemy isVisible ifTrue: ["whatever"]

Instead you have a separate class which handles visibility determinations. For example, in the player's code:
Code:

(self canSee: enemy) ifTrue: ["whatever"]

And elsewhere:
Code:
canSee: anObject

    | invisProp |

    invisProp := anObject propertyNamed: #invisibility.
    invisProp isNil
    ifTrue:
        [^true]
    ifFalse:
        [| lightProp |
        lightProp := anObject propertyNamed: #lightSource.
        lightSource isNil
            ifTrue: [^false]
            ifFalse: [^lightSource brightness >= self visibilityThreshold]].

Or whatever.

You shouldn't ever have a situation where a property affects its owning object as strongly as I think you're suggesting: if you do, it means the decision as to what happens shouldn't be being taken by the object in question, but elsewhere. In this case, we aren't allowing the invisible-or-glowing monster to decide whether it is visible or not - something else does that. So we never have to change behaviour that radically.


eiz wrote:
Well, if we're going to talk about "the right way" as if there were a single right way
Mr. Green
Quote:
(now if you want to see religious conviction, talk to an APLer), I'd have to say that prototypes and delegation are a better fit to MUDs than classes and instances any day. And of course any proper prototype based language supports multiple, dynamic delegates...

I wouldn't disagree with that - so long as the behaviour can be changed dynamically on an object-by-object basis, and not merely class-by-class, we are effectively talking about the same thing. Smile But "inheritance" is a term that I associate strongly with classes and instances.



One final note: this principle can be extended to many things besides light sources. For example, many MUDs might have a Weapon class, from which classes such as Sword and Dagger inherit. But is that actually the best way to do it? Maybe. But I'd prefer to see a game where any object can, under certain circumstances, be used as a weapon: where a player can pick up a chair and smash it over someone's head.

This could (quite legitimately) be handled up in some general MUDObject superclass, but you could also have a class whose purpose is to handle all of an object's weapon behaviour, and nothing else. Swords and Daggers don't inherit from it, instead they have a #weapon property, which deals with anything weapon-related, while an entirely separate object deals with non-weapon behaviour. Sword and Dagger very likely would have a common superclass - but their behaviour as weapons doesn't necessarily have to be controlled by that class.

The advantage of this approach is that it really does keep everything as decoupled as possible: an object whose function is to handle (say) bulk or encumbrance doesn't have to know anything about how weapons work, and an object modelling weapon behaviour shouldn't need to know about bulk (although it might need to know about weight and length). A sword will have both encumbrance and weapon attributes - but both can be handled by classes that don't have to know anything about each other.
Back to top
View user's profile Send private message
Author Message
eiz



Joined: 11 May 2005
Posts: 152
Location: Florida

PostPosted: Mon Dec 12, 2005 10:08 am    Post subject: Reply with quote

shasarak wrote:
Using the inheritance-only model, the only way to do it is to make all game objects inherit from LightSource. Now that's just crazy: 99% of the objects in the MUD aren't sources of light, but they have to inherit from LightSource anyway.


Your single dispatch message passing myopia has blinded you. Welcome to the future:

Code:

(defmethod lightsourcep (x) nil)
(defmethod lightsourcep ((x light-source)) t)


shasarak wrote:
Worse, there's a big loss of flexibility: if a given class doesn't inherit from LightSource then any instance of that class cannot ever be a source of light. That might not be an issue, but suppose you want to introduce a light spell which, instead of simply generating a floating globe of light, can be cast on any object and make it glow. How can we do that?


Frankly, "light source" is a pretty poor example in the first place, but this sort of thing is exactly why I prefer prototype based systems where changing an object's delegates is a simple matter of changing its slots. Usually when I see MI suggested, it's not in the abstract like "LightSource" but more concrete ("Torch") which makes a lot more sense in a class based system. Using dictionaries of properties to make up for a lack of dynamism in the language doesn't seem like the best solution to me.

shasarak wrote:
I wouldn't disagree with that - so long as the behaviour can be changed dynamically on an object-by-object basis, and not merely class-by-class, we are effectively talking about the same thing. But "inheritance" is a term that I associate strongly with classes and instances.


The difference is in the programming interface. A prototype-based language handles the details of delegation transparently. As for the terminology, well, some call it inheritance and some call it delegation, type theorists call it subsumption and who knows what else... it all boils down to being able to treat an object as some other kind of object.
Back to top
View user's profile Send private message Visit poster's website
Author Message
Tyche



Joined: 13 May 2005
Posts: 176
Location: Ohio, USA

PostPosted: Mon Dec 12, 2005 10:56 am    Post subject: Reply with quote

shasarak wrote:

So, to add them all up, we might have a method defined on class Room or Container:

Code:

illuminationLevel

    | totalLight |

    totalLight := 0.
   
    self contents do:
        [:eachObject|
        eachObject isLightSource ifTrue:
            [totalLight := totalLight + eachObject brightness]].

    ^totalLight



Why not just call the method? Isn't isLightSource redundant with the notion that you have a class LightSources?

For example in ColdC which has MI...

Code:

public method .light_level() {
    var obj, total;
    total = 0;
    for obj in (contents) {
        catch ~methodnf {
           total += obj.lumens;
        }
    }
    return total;
};


Assume .light_level method defined on $room and .lumens is a method defined on $light_sources.

Alternatively in Ruby which has SI, but behavioral MI can be reasonably done with Module includes.

Code:

def light_level
    total = 0
    contents.each do |obj|
        total += obj.lumens if obj.respond_to? :lumens
    end
    total
end
Back to top
View user's profile Send private message Visit poster's website
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Tue Dec 13, 2005 10:40 am    Post subject: Reply with quote

Tyche wrote:

Code:

illuminationLevel

    | totalLight |

    totalLight := 0.
   
    self contents do:
        [:eachObject|
        eachObject isLightSource ifTrue:
            [totalLight := totalLight + eachObject brightness]].

    ^totalLight


Why not just call the method? Isn't isLightSource redundant with the notion that you have a class LightSources?

For example in ColdC which has MI...

Code:

public method .light_level() {
    var obj, total;
    total = 0;
    for obj in (contents) {
        catch ~methodnf {
           total += obj.lumens;
        }
    }
    return total;
};


Assume .light_level method defined on $room and .lumens is a method defined on $light_sources.

Alternatively in Ruby which has SI, but behavioral MI can be reasonably done with Module includes.

Code:

def light_level
    total = 0
    contents.each do |obj|
        total += obj.lumens if obj.respond_to? :lumens
    end
    total
end

Well, the first piece of code was (obviously, I hope!) presented as something to be avoided at all costs. Smile However, I don't feel either of your suggested alternatives is an improvement.

If I understand you correctly, the Smalltalk equivalent of the second case is:

Code:
lightLevel

    | total |

    total := 0.
    self contents do:
        [:each |
        (each respondsTo: #brightness) ifTrue:
            [total := total + each brightness]].
    ^total

That's not very nice. You're asking each object if it has a method named #brightness when you ought to be asking if it is a light source - those two questions might happen to have the same answer in this example, but if you want to know if something is a light source, that's the question you should ask.

In addition, if there is more than one message send in a row like this, there are potential performance problems. You don't want to end up doing something like:

Code:
(anObject respondsTo: #rhythm) ifTrue:
    ["do something about rhythm"].

(anObject respondsTo: #tempo) ifTrue:
    ["do something about tempo"].

(anObject respondsTo: #volume) ifTrue:
    ["do something about volume"].

(anObject respondsTo: #timbre) ifTrue:
    ["do something about timbre"].

Instead you want to do:

Code:
anObject isSoundSource ifTrue:
    ["do something about rhythm"

    "do something about tempo"

    "do something about volume"

    "do something about timbre"].

Or, preferably:

Code:
| soundSource |

soundsource := anObject propertyNamed: #soundSource.
aoundSource isNil ifFalse:
    ["do something about rhythm"

    "do something about tempo"

    "do something about volume"

    "do something about timbre"].

Another issue in passing is that I have a method named #volume here. It's quite likely that something other than a sound source might implement a #volume method, for example, a container might have a volume measured in cubic feet rather than decibels. Obviously one can come up with naming conventions to avoid this, but keeping all sound-related methods on one property object and all volumetric methods on a separate property object neatly avoids the risk without effort.



Your first example (if I understand correctly) is the equivalent of this piece of Smalltalk:

Code:
lightLevel

    | total |

    total := 0.
    self contents do:
        [:each |
            [total := total + each brightness]
                on: MessageNotUnderstood
                do: [].
        ].

    ^total

That's just horrible. Smile You're actually deliberately waiting for the code to throw an exception and then trapping it, rather than performing the test necessary to avoid the exception being raised in the first place.

That's roughly on a par with summing the elements of a collection like this:

Code:
someMethod: anArray

    | index total errored |

    total := 0.
    errored := false.
    index := 1.
    [errored] whileFalse:
        [
            [index := index + 1.
            total := total + (anArray at: index)]
        on: Error do:
            [errored := true]
        ]

instead of like this:

Code:
someMethod: anArray

    | index total s|

    total := 0.
    s := anArray size.
    index := 1.
    [index <= s] whileTrue:
        [index := index + 1.
        total := total + (anArray at: index)]

The first version may work, but it's not the sensible or efficient way to do it.
Back to top
View user's profile Send private message
Author Message
eiz



Joined: 11 May 2005
Posts: 152
Location: Florida

PostPosted: Tue Dec 13, 2005 11:08 am    Post subject: Reply with quote

shasarak wrote:

That's roughly on a par with summing the elements of a collection like this:


Ah, innocence... this is actually how summing the elements of a collection works in at least a couple of collection libraries and languages I know (Python, OCaml's extlib, etc). =/

While I agree that using exceptions like this is probably not such a fantastic idea, any language that separates method dispatch from the notion of ownership by a class (basically, any language based on the CLOS model) can provide a default version for objects which don't provide their own, without modifying any base classes. I believe the original system mentioned in this thread was Bartle's, whose primary component is indeed multiple dispatch generic functions.
Back to top
View user's profile Send private message Visit poster's website
Author Message
Tyche



Joined: 13 May 2005
Posts: 176
Location: Ohio, USA

PostPosted: Tue Dec 13, 2005 5:11 pm    Post subject: Reply with quote

shasarak wrote:

Well, the first piece of code was (obviously, I hope!) presented as something to be avoided at all costs. Smile However, I don't feel either of your suggested alternatives is an improvement.


No. Why?

shasarak wrote:

If I understand you correctly, the Smalltalk equivalent of the second case is:
...


Yes.

shasarak wrote:

That's not very nice. You're asking each object if it has a method named #brightness when you ought to be asking if it is a light source - those two questions might happen to have the same answer in this example, but if you want to know if something is a light source, that's the question you should ask.


Duck-typing happens to be a ruby idiom. It solves the problem posed. But we certainly can be quite direct about it too:

Code:

def light_level
    total = 0
    contents.each do |obj|
        total += obj.lumens if obj.kind_of? LightSource
    end
    total
end


shasarak wrote:

In addition, if there is more than one message send in a row like this, there are potential performance problems. You don't want to end up doing something like:
...


Of course you wouldn't code that. You only need to identify the object once and whether you do it by identifying a method or it's ancestry makes no difference.

shasarak wrote:

Another issue in passing is that I have a method named #volume here. It's quite likely that something other than a sound source might implement a #volume method, for example, a container might have a volume measured in cubic feet rather than decibels. Obviously one can come up with naming conventions to avoid this, but keeping all sound-related methods on one property object and all volumetric methods on a separate property object neatly avoids the risk without effort.


And the problem is present in your property system as well. Is your solution ugly? Wink

shasarak wrote:

Your first example (if I understand correctly) is the equivalent of this piece of Smalltalk:
...


Yes, I think.

shasarak wrote:

That's just horrible. Smile You're actually deliberately waiting for the code to throw an exception and then trapping it, rather than performing the test necessary to avoid the exception being raised in the first place.


If you have something against execeptions then do this instead:

Code:

public method .light_level() {
    var obj, total;
    total = 0;
    for obj in (contents) {
        if obj.has_ancestor($light_source) {
           total += obj.lumens;
        }
    }
    return total;

};


I don't recall if there is a .has_method() builtin in ColdC.

Performance is not the problem you raised. I don't really believe in entertaining it or assuming the same characteristics across languages.

The problem you raised with MI is requiring every object to have the same methods defined on it (i.e. isLightSource). Obviously this isn't the case, as I presented three solutions.

EDIT:
shasarak wrote:

So you shouldn't be trying to change the properties of an object by changing its inheritance in the first place - you should be creating or removing other, separate objects to handle completely that aspect of object behaviour while the main object remains blissfully unaware.


Actually some Object proto languages like ColdC make it very easy to change the inheritance of an object on the fly. Using delegation through aggregation is not nearly as transparent.
Back to top
View user's profile Send private message Visit poster's website
Author Message
Kaz



Joined: 05 Jun 2005
Posts: 24
Location: Hampshire, UK

PostPosted: Wed Dec 14, 2005 3:05 pm    Post subject: Reply with quote

BobTHJ wrote:
I know I'm joining this topic rather late but...


It's ok. So am I.

In the development of my codebase, I had to make a decision about how to actually build the world. Having come from a Diku background, I always remember being annoyed at how much trouble it was to actually for a builder to introduce something innovative (We ended up with spec_procs, mobprogs (triggered scripts), mobscripts (continuous scripts), dynamic descriptions, and so on and so forth.

So I made the decision to introduce a scripting language to my codebase; a lot of the rule-set would be in code, of course, but the actual world design and most of the interactions would be performed in this scripting language.

I have designed it with one thing in mind, though: builders are not necessarily good coders. In fact, the most imaginitive builder I've met so far has little to no knowledge as to how code works. (Which is one reason I plan to write a GUI tool on top, but that's another story).

So, at its basic level, the language must be able to do what Diku does: create templates of objects, which can be replicated over and over again. I call these templates "archetypes":

Code:
archetype diku_longsword inherits longsword is
    function diku_longsword() returns none is
        my$keywords = "sword longsword"
        my$description = "A Diku longsword lies here."
        my$short_description = "A Diku longsword"
        my$long_description = "A longsword with 'DIKU' etched in the blade"
    end
end


So there we have a longsword with a couple of modifications. Some new strings, in fact. An advantage of this is that in order to create "unique" items (in the Diku sense of the word), its a simple cut-and-paste, search-and-replace job. And also less work if your create categories of archetypes (such as longsword above), where you just override those bits that need overriding.

So, we've got what Diku does. I want more. I want to be able to create objects that do unique things. So I give them the abilities to have commands assigned to them. Consider a lever which, when pulled, opens or closes a secret door:

Code:

# For now, it doesn't matter where these come from. 
# It only matters that we know they're there.
declare object my_secret_room
declare function send_to_room(text : string) returns none

archetype lever is
    function lever() returns none is
        my$keywords = "lever"
        my%state = 0
    end

    # Somewhere after creating the object, it is put in a room,
    # which is where its "room" property is set.

    on pull do
        if my%state = 0
            send_to_room("A secret exit opens to the north.")
            my.room.exits.north = my_secret_room
        else
            send_to_room("The secret exit to the north slides closed.")
            my.room.exits.north = nil
        end
    end
end


And, apart from a few odds and ends, that's pretty much the language I've designed in a nutshell. Since it compiles down to a bytecode, I've been working on the tools from the bottom up. The runtime environment, linker and assembler are all in place; I'm hoping to work on the compiler over Christmas.

After that comes the wondrous task of actually implementing the foundations of the game world so that the building can start. I'm looking forward to it.
Back to top
View user's profile Send private message
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Wed Dec 14, 2005 4:57 pm    Post subject: Reply with quote

Tyche wrote:
shasarak wrote:

Another issue in passing is that I have a method named #volume here. It's quite likely that something other than a sound source might implement a #volume method, for example, a container might have a volume measured in cubic feet rather than decibels. Obviously one can come up with naming conventions to avoid this, but keeping all sound-related methods on one property object and all volumetric methods on a separate property object neatly avoids the risk without effort.


And the problem is present in your property system as well. Is your solution ugly? Wink

Well, not really. Consider:

Code:
someMethod: anObject

    | decibels cubicFeet soundSource bulkManager |

    bulkManager := anObject propertyNamed: #bulkManager.
    bulkManager isNil ifFalse:
        [cubicFeet := bulkManager volume].

    soundSource := anObject propertyNamed: #soundSource.
    soundSource isNil ifFalse:
        [decibels := soundSource volume].

In this case, both a SoundSource and a BulkManager have a method called #volume, but because the thing that it is calling either method knows whether it's asking about a decibel level or a volume in cubic feet, there's no danger of ambiguity. By contrast, if you did something like this:

Code:
someMethod: anObject

    | decibels |

    (anObject respondsTo: #volume) ifTrue:
        [decibels := anObject volume].

using the inheritance model, then you'd end up interpreting the decibels of somethat inherits from SoundSource as cubic footage.

Obviously, if you're inventing your own scripting language, there are ways around this, because you can invoke a method as inherited from a specific superclass rather than simply by name. Or, failing that, you can define a name space that makes everything unambiguous.


Tyche wrote:
Performance is not the problem you raised. I don't really believe in entertaining it or assuming the same characteristics across languages.

The problem you raised with MI is requiring every object to have the same methods defined on it (i.e. isLightSource).

That's one of the problems I raised. The inability for an object to inherit from (or behave like) a light source at one moment and not at another is a much more serious problem: you need a system of inheritance (or aggregation) that is dynamic, and that works at the object (rather than class) level.

Tyche wrote:
shasarak wrote:

So you shouldn't be trying to change the properties of an object by changing its inheritance in the first place - you should be creating or removing other, separate objects to handle completely that aspect of object behaviour while the main object remains blissfully unaware.


Actually some Object proto languages like ColdC make it very easy to change the inheritance of an object on the fly. Using delegation through aggregation is not nearly as transparent.

How so?

A system based on aggregation and one based on dynamic, object-by-object inheritance are functionally identical, I would have thought. If we take an object that is not currently a light source (like our Sword) and give it a #lightSource property, how is that less transparent than giving the sword a new LightSource superclass? The effect is exactly the same.
Back to top
View user's profile Send private message
Author Message
eiz



Joined: 11 May 2005
Posts: 152
Location: Florida

PostPosted: Wed Dec 14, 2005 5:51 pm    Post subject: Reply with quote

shasarak wrote:
A system based on aggregation and one based on dynamic, object-by-object inheritance are functionally identical, I would have thought.


They are, more or less. What you've basically done is implement the slots and delegates of a prototype based system in Smalltalk using dictionaries (an implementation of doesNotUnderstand could get you even closer to the real thing). Delegation, which is the same as inheritance in a prototype object system, is a form of aggregation. Keeping in mind that the thread is titled "Creating a custom scripting language," I don't think the idea of integrating this functionality directly into the language is all that radical. It's less transparent because you're implementing it by hand on top of a language that doesn't directly support it, with far less functionality than a modern prototype-based language.
Back to top
View user's profile Send private message Visit poster's website
Author Message
shasarak



Joined: 29 Jun 2005
Posts: 134
Location: Emily's Shop

PostPosted: Thu Dec 15, 2005 10:42 am    Post subject: Reply with quote

eiz wrote:
shasarak wrote:
A system based on aggregation and one based on dynamic, object-by-object inheritance are functionally identical, I would have thought.

They are, more or less. What you've basically done is implement the slots and delegates of a prototype based system in Smalltalk using dictionaries (an implementation of doesNotUnderstand could get you even closer to the real thing). Delegation, which is the same as inheritance in a prototype object system, is a form of aggregation. Keeping in mind that the thread is titled "Creating a custom scripting language," I don't think the idea of integrating this functionality directly into the language is all that radical. It's less transparent because you're implementing it by hand on top of a language that doesn't directly support it, with far less functionality than a modern prototype-based language.

Well, clearly, if there's any sort of operation that is going to be done an awful lot, it makes sense to optimise it at the lowest level possible (e.g. with dedicated virtual machine primitives), and equally clearly, it's nice if the language allows you to do it in a clear, convenient, compact and efficient way. Smile

If I were writing my own Smalltalk-based scripting language, I would certainly want to optimise this sort of operation beyond the normal Dictionary mechanism, I think - preferably at the VM level.

Incidentally, this is a minor point, but the system I'm describing here actually isn't quite what I would regard as "delegation", although it's similar. To go back to the light source example: in a "delegation" system, after we create the LightSource and assign it to the sword, the sword now implements a method it didn't implement before. Previously, if you evaluated:
Code:
sword brightness
you would have got a "message not understood" error, but now you don't.

The way my proposed system works, the sword still doesn't implement a #brightness method: evaluating
Code:
sword brightness
will still error. The method #brightness is only ever defined on the LightSource class (or its subclasses). Anything that wishes to invoke that method does so by checking for the existence of a #lightSource property and, if it's there, invoking the method on that. The sword itself never knows anything about light sources at all.

I think this strategy is useful because it helps to keep things separate when they should be: it's a way of thinking that helps to keep the class hierarchy well organised. If you think in terms of "a sword that is a light source" then there's a tendency for less experiences programmers to mix the two things up too intimately. But if you begin by thinking "okay, here's a class that handles light emission, here's a class that handles the in-combat behaviour of a weapon, here's a class that deals with weight and bulk, here's a class that acts as a holder for all of these distinct properties" that helps keep things organised.

If you wanted to use an actual delegation system, i.e. the sword relaying a message send to its #lightSource, then that, I agree, ought to be done via something that is "inheritance" rather than "containtment" or "aggregation". But, as I said, I would prefer not to do it like that.

Multiple inheritance (in my experience) is a very dangerous tool, in that people have a tendency to say "oh, I want an object that's both a sword and a map" so they inherit directly from one class that's a sword and one that's a map, and expect that to work. This sort of thing results in the most unholy mess when you start trying to figure out which version of a method that is defined in more than one superclass you actually want to end up using. Anything that encourages people to think in terms of "one class: one functional area" is (IMO) a good thing.


On a side note: it occurred to me last night that this implementation of light-handling isn't quite adequate, because it assumes that you can only ever have one light source associated with any one MUD object. This isn't sufficient: what happens when someone casts a light spell on a burning torch? The LightSpellSource shouldn't replace the BurningTorchSource as the sword's #lightSource property. Instead, you need a two-level system: the sword needs a #lightManager property, and the manager manages a collection of light sources. LightManager probably implements a #brightness method which is the sum of all of individual LightSource brightnesses.

This is a situation where pure inheritance would pose a problem, I think: even if the sword inherits from both LightSpellSource and BurningTorchSource, you still aren't combining the behaviour of the two: in any situation one of the two will be responding, and the other won't be. Obviously you could make the sword inherit from LightManager, and this manage a collection of LightSources itself, but there still has to be some element of aggregation going on, even if it's only at the LightManager level.

eiz wrote:
Your single dispatch message passing myopia has blinded you. Welcome to the future:

Code:

(defmethod lightsourcep (x) nil)
(defmethod lightsourcep ((x light-source)) t)

This sort of syntax reminds me of a line from "Red Dwarf":

Rob Grant and Doug Naylor wrote:
I'm some kind of robot who's fighting this virus, and none of this exists, it's all in a fever, except for you guys, who really do exist, only you're not really here, you're really on some space ship in the future. Hell, if that's got to make sense I don't want to be sober!

Smile

Could you translate that for me?
Back to top
View user's profile Send private message
Author Message
Kaz



Joined: 05 Jun 2005
Posts: 24
Location: Hampshire, UK

PostPosted: Thu Dec 15, 2005 11:06 am    Post subject: Reply with quote

shasarak wrote:
Multiple inheritance (in my experience) is a very dangerous tool, in that people have a tendency to say "oh, I want an object that's both a sword and a map" so they inherit directly from one class that's a sword and one that's a map, and expect that to work. This sort of thing results in the most unholy mess when you start trying to figure out which version of a method that is defined in more than one superclass you actually want to end up using. Anything that encourages people to think in terms of "one class: one functional area" is (IMO) a good thing.


Actually, this is something I've designed into my language:

Code:
archetype mapsword inherits map, sword is
...
end


(The rule to disambiguate is that the inherited archetype that is the most to the right has priority. Hence, if there are any overlaps between swords and maps above, sword has precedence). My experience of building is that people sometimes do want to mix things like this. The sword/lightsource example, for instance. It's not too fantasy (for a fantasy setting) to have a glowing sword. It's also my experience of C++ that these kind of deadly situations of multiple inheritence that are oft-quoted just don't happen. Plus, should it actually happen, it's shouldn't be too difficult to design and use tools that find the overlaps and point them out to you as warnings.

All that said, I'm interested to see how this turns out in practice.
Back to top
View user's profile Send private message
Author Message
BobTHJ



Joined: 19 Nov 2005
Posts: 31

PostPosted: Thu Dec 15, 2005 3:14 pm    Post subject: Reply with quote

I'm using a property method for my Mud Engine in VB.Net similar to what shasarak describes, except it's all in compiled bytecode instead of a scripting language. The key factors I would like to point out about it are:

1. Each property can be attached to an object (object being Area, Room, Mob, Item, or Exit). The object can hold multiple properties of the same type (to solve the 2 light source problem).

2. Each property implements a common interface, which allows the engine to update/respond to a property without knowing what that property is or does. Properties provide 2 methods for receiving notice of messages (one before the action, and one after).

3. The properties are much more generalized than LightSource. For instance, if you want to make a glowing sword, you would add a 'ChangeStat' property to the sword. This would alter the "Luminosity" stat of the sword by increasing it as long as the property remained attached to the object. An object's stats are stored in a hashtable, and are only created if they are required for use. So a sword would not have a "Luminosity" stat until something changes it from the default of 0.

I have found that by adding combinations of properties to objects I can create almost any behavior I desire. So far, I've had to write about 90% less scripts for custom object behavior than I have on previous muds, all by just using combinations of a dozen or so property objects. I find the system to be most effective.
Back to top
View user's profile Send private message Send e-mail
Display posts from previous:   
Post new topic   Reply to topic    mudlab.org Forum Index -> Coding All times are GMT
Goto page Previous  1, 2, 3  Next
Page 2 of 3

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2001, 2002 phpBB Group
BBTech Template by © 2003-04 MDesign