ID:109835
 
Keywords: design, interfaces
Disclaimer: The code in this DevTalk is untested and was written as I wrote the article, so it may contain bugs. Feel free to mention any you find in the comments, and I'll correct the code.

Interfaces are an OOP concept that BYOND has no notion of. If you're unaware of what an interface is, there are plenty of Google-able resources on the subject (Or for the lazy, see here).

The problem that interfaces are often there to solve, is this one:

What if you have a number of classes which carry no resemblance to eachother, but a different class wants to use any one of them in the same fashion.

For a specific example, in my game I have a /menu object and a /form object. A menu can link to another /menu, but it can also link to a /form. And yet, the /form has absolutely nothing to do with a /menu, and can exist just fine on its own. The /form is simply a series of questions the user is asked, where-as a menu is a static object that allows the user to walk through the menu, its children, and so on.

So how do you solve a dilemma like this? As with most things within programming, there are numerous methods to achieve the above. Some of which are pretty sketchy and not very robust, some of which are.

Lets simplify things and say we have the following scenario:

apple
proc/describe()
world << "Apples are yummy!"

car
proc/describe()
world << "Car goes wrooom!"

describe_item
var/datum/D
proc/setItem(datum/D)
src.D = D

proc/Go()
D.describe() // Uh oh! We can't do this


Ignore for the sake of this example that the above could use a variable to describe the object - The example is kept simple in order to be easier to grasp.

The above obviously isn't possible, as /datum has no describe() procedure. And if we don't want /apple and /car to descend from the same parent, we've got an issue we need to solve.

The smart reader might go, 'You can use istype() for this!', and they'd be right. But lets see how that looks, for a moment:

apple
proc/describe()
world << "Apples are yummy!"

car
proc/describe()
world << "Car goes wrooom!"

describe_item
var/datum/D
proc/setItem(datum/D)
if(istype(D, /apple) || istype(D, /car))
src.D = D

proc/Go()
if(istype(D, /apple))
var/apple/A = D
A.describe()
else if(istype(D, /car))
var/car/C = D
C.describe()


Woohoo! Home safe! Except.. the above really looks like we've duplicated a lot of code. And what if we want to add another object? More istype()'s. If we want to remove an object, we have to adjust both procedures too.

The smart reader might now go, 'You can use the : operator, and write a separate procedure to check the type of the datum!', and they'd be correct again. And this IS one solution. However, it still requires you to maintain the procedure doing the type-checking, and still feels pretty ugly to me.

I have a different suggestion: Emulate the spirit of interfaces, and joy will be had by all.

DM has a call()() procedure, which allows you to call a proc, or a proc belonging to an object. We could wrap this functionality in an object and maintain a list of legal functions to call. This way we get around having to check types, instead we can check whether the function we want exists with hascall() - And this can be done dynamically and all nice.

For example:

datum/proc/publicFunctions()
return new/list()

callWrapper
var
datum/__obj
list/__func=new()

New(datum/__obj, list/__func)
if(!istype(__obj)) del src

if(!istype(__func))
__func = __obj.publicFunctions()

for(var/a in __func)
if(!hascall(__obj, a))
__func -= a

if(!length(__func)) del src // No functions to call

src.__obj = __obj

src.__func = __func

proc
Call(f)
if(!f || !(f in __func)) return

if(length(args) > 1)
var/list/L=new()
L = args.Copy(2)
call(__obj, f)(arglist(L))
else
call(__obj, f)()


So that was a lot of code. What does it do?

The idea behind the callWrapper object is more or less what it sounds like. It wraps calls in an object. You supply it with an object, and optionally a list of functions that can be called, and it does the rest. In order to actually call that function, you use callWrapper.Call(function_name).

The callWrapper object will make sure that any function specified in the __func list in New() actually can be called, and that if there are no 'legal' functions left, or you didn't supply a datum as __obj, it'll delete itself.

Lets try to apply the callWrapper object to our initial case:

apple
proc/describe()
world << "Apples are yummy!"

car
publicFunctions()
return new/list("describe")

proc/describe()
world << "Car goes wrooom!"

describe_item
var/callWrapper/W
proc/setItem(callWrapper/W)
src.W = W

proc/Go()
W.Call("describe")

var/apple/A = new()
var/car/C = new()
var/describe_item/describe_apple = new()
var/describe_item/describe_car = new()

describe_apple.setItem(new/callWrapper(A,list("describe"))
describe_car.setItem(new/callWrapper(C))


The good thing about this, is that if you wanted to be able to describe trees, or a menu, or some internal object, or something else totally unrelated to all the rest, all you'd have to do was give it a describe() procedure. The above also shows the two different ways to deal with the callWrapper object - By overwriting publicFunctions(), or passing any functions that you want to call as a list to its constructor.

This is in fact closer to automatically inherited interfaces, which Google Go implements. And automatically inherited interfaces are awesome, for a variety of different reasons.

This callWrapper is also great any time you want to export some functionality in a library, and you want the API to be very simple. Lets say you have an object and you want to let the user define how it is displayed in a chat panel. You might in that objects constructor give the user the ability to pass it a callWrapper, and then in the descriptive function for the object, you'd use the callWrapper if it existed. That might look like this:

menu
var/name = "Some Menu"
proc/describeMenu()
if(__wrapper) return __wrapper.Call("describeMenu")
else return __defaultDescribe()

proc/__defaultDescribe()
return src.name


I may release a small library with this functionality (and some extra functionality), if I can convince myself its not just a small snippet but actually a library ;)
I went ahead and bottled this functionality up into a library called Callwrapper. I'd appreciate any comments you have on the API / putting it to use.

Beyond what is discussed in this article, the library provides functionality where it automagically figures out what procedures a given object type has. It will filter out functions considered private (By default, any proc that is prefixed by __), allowing you to use this to handily wrap API as well. Its a bit of a gimmick compared to an actual 'private' keyword, but its better than nothing.

As the demo.dm file shows, there is very little overhead in all of this. I haven't been able to run this on Windows yet, so I don't have any actual DS profiler stats - I'd appreciate if someone else could provide a profile of demo.dm, specifically the 10000 call test.

The library is currently in a state where the API can potentially change with updates, nothing has been locked yet. As things are tested and the library is used, I will begin locking features.

Remember! If you like DevTalk articles, follow us :)
It's too bad we can't just have multiple inheritance.
Vermolius wrote:
It's too bad we can't just have multiple inheritance.

I've never been much of a fan of multiple inheritance. Its often too strong of a contract to be making, I feel.
An alternative to this method is to compose your functionality into an object as a variable.

evil
var/factor = 1
proc/spreadEvilAura()
...

mob/person
var/evil/evil = new /evil
proc/hurtPeople()
src.evil.spreadEvilAura()
src.evil.factor++

obj/item
var/evil/evil = new /evil
proc/use(mob/player)
if(src.evil.factor > 10)
player.evil.factor++
player << "You have been cursed!"


The advantage of this method is that you don't need any quirky syntax, and you are also not bounded by the limitation of inheritance, which enforces an 'is a' relationship between objects, when in reality, a 'has a' relationship is more accurate. (A player 'is' evil vs a player has evilness).

I find the issue with interfaces conceptually is that although it enables a 'has a' relationship, you cannot easily represent a 'has many' or 'has and belongs to many' relationship without resorting to a delegation pattern.
The library I've uploaded provides various methods to access the functionality. Interfaces can be sub-typed, if you only want specific functions from an object to be available (Mimicking what most people do with interfaces). The default callInterface object will expose every public function the object has by default. It will also let you specify what functions should be available, preventing you from needing to do a compile-time subtype.

These two methods of encapsulation are actually quite similar. The primary difference is that one is generic and thus requires the use of call()(), where-as the other prefers to define an object type for specific needs.

More-over so, the two methods are not mutually exclusive. There are many places where encapsulating variable access into objects is a very good idea.

Likewise, I find that there are places where the more generic approach gives you more power and flexibility, at no real cost to readability / error-resolution. The difference between evil.spreadEvilAura() and evil.Run("spreadEvilAura") are minor, I feel. Mostly, I find this generic approach appropriate when you're solely dealing with functions.

Thanks for the comment :)
I don't think that many developers would go through the hassle of defining the publicFunctions proc, keeping its return value in sync with the actual procs it contains, or use the call_wrapper function to make the proc call.

Using the : operator gives you a simple syntax without having to typecast. You just need to be sure that the object contains what you're referencing after the colon. To check we can use typesof to enumerate procs. Like this:

datum/proc/has(p)
var/looking_for = "[type]/proc/[p]"

for(var/t in typesof("[type]/proc"))
if("[t]" == looking_for)
return 1

return 0


Instead of all that stuff with call wrappers you can simply do this:

if(d.has("describe"))
d:describe()


The only problem I have with this is that you reference the proc name inside a string, so if you spell "describe" wrong or change the proc name the error might be hard to catch since you're used to seeing compiler errors for things like that.

The has() proc shown above is simple. It doesn't check parent types or built-in procs (typesof doesn't enumerate those). My Interface library defines a "procs" proc for all datums that enumerates all procs defined for a type and its parent types.
Forum_account wrote:
I don't think that many developers would go through the hassle of defining the publicFunctions proc, keeping its return value in sync with the actual procs it contains, or use the call_wrapper function to make the proc call.

If all you want is to be able to call a procedure across a set of objects, and you're okay with manually checking for its existance every time, and okay with potentially typo'ing stuff, then yes your solution is fine. It is a lightweight way of solving the exact problem posed in the article - But it doesn't solve a variety of others. And it requires you to be doing this checking every time for every situation.

The point of the article and associated library is to provide more power and robustness than that. If you have no need for the behavior, naturally it will seem bloated. However, the actual API is quite slim and what it does is quite simple. However, it does a variety of stuff behind-the-scenes which are a solid help.

For example, it provides a tight coupling between an interface and the object that it provides an interface to. If the interface is deleted, so is the object. Its also possible to define an interface in such a way that you can just call interface.procname(), and it will call the right procedure behind the scenes. This provides a more natural approach to interfacing with something.

Alathon wrote:
If all you want is to be able to call a procedure across a set of objects, and you're okay with manually checking for its existance every time

We're specifically dealing with situations where you're not sure of the proc's existence. If you were sure the proc existed you'd simply use the : operator and call apple:publicFunc(). Manually checking isn't a problem because you need to check so you can handle the case where the proc doesn't exist.

and okay with potentially typo'ing stuff

That risk also exists when you type one.Go("publicFunc").

For example, it provides a tight coupling between an interface and the object that it provides an interface to. If the interface is deleted, so is the object. Its also possible to define an interface in such a way that you can just call interface.procname(), and it will call the right procedure behind the scenes. This provides a more natural approach to interfacing with something.

If the library does any of that, the demo is enough of a mess that it's hard to tell. It seems like the library is ok (a lot better than using the publicFunctions() proc you defined in the article) but the /calltest object makes the demo overly complex. This is supposed to be an alternative to type casting, but with the calltest and callInterface objects and the setTest and Go procs it makes type casting look less cumbersome.

With the "has" proc there's only one proc to remember. In any application it still lets you use syntax that looks like a function call (o:test() instead of o.Go("test"), not a huge difference but when you have arguments it makes more of a difference). You can also use it to create more elaborate things. If you want to abstract out details and define an interface, you can do this:

// defining the interface
describable
var
datum/object

New(o)
object = o

proc
describe()
ASSERT(object.has("describe"))
return object:describe()

// using it
proc/test(datum/object)
var/describable/d = new(object)
d.describe()
What this comes down to is whether you want to use call()() or the : operator.

The library prefers call()(), because wrapping the call in an object provides you with more flexibility. Amongst other things, it allows you to do things before/after the call, deal gracefully with attempts to call non-exposed functions and similar.

About the article: There is some confusion here. I wrote the article initially on a whim, and then later decided to actually write a library utilizing some of the concepts.

This means that the demo code in the article doesn't match the API of the library. Thats my fault, its provided a lot of confusion it seems. Apologies :)
The library prefers call()(), because wrapping the call in an object provides you with more flexibility. Amongst other things, it allows you to do things before/after the call, deal gracefully with attempts to call non-exposed functions and similar.

The demo doesn't show this. I'd have to look at the library again to see how you handle this, but by hiding the call to the call proc inside another object you would lose that ability to do anything before or after the call, or the ability to handle invalid calls - the object that contains the call handles that for you.

Using the "has" proc, you can handle non-exposed functions easily:

if(d.has("describe"))
d:describe()
else
world << "I don't know how to describe [d]."


I'm not sure how your library or demo handle this. I don't have the code in front of me, but I remember you called one.Go("procName"), which checks that the proc exists, calls it, and returns its return value. With your library I'm not sure how you'd handle the case where the proc doesn't exist or isn't exposed.