ID:2195603
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
hasinterface(Obj, Type) should tell whether or not an object (instance or prototype) implements a given interface (prototype), meaning that the object has all the procs and vars of the given interface type.

http://www.byond.com/docs/notes/252.html

252 Release Notes:
Added hascall(Obj,ProcName) as a way of testing at run-time if a given object has a proc or verb by the specified name. You can then safely use call(src,procname). This feature is useful for library writers who want to adapt dynamically to the characteristics of user-defined objects. In the future there will also be a hasinterface(Obj,ObjType) which tests for all the vars and procs in one pass.

http://www.byond.com/forum/?post=138516#comment946409

Dan wrote:
By interface object, I just mean an object definition that provides stub definitions for all the public symbols that you intend to access. It could, of course, contain actual code that does stuff too, but from the point of view of the proposed hasinterface(Obj,Type) instruction, all that would matter is whether Obj defines all the vars and procs of Type. It would work a lot like istype() except it wouldn't care about inheritance.


Part 2 of my feature request is an as interface filter for lists used in for() and locate(), where the elements being iterated over/searched through are filtered based on hasinterface() instead of istype().
updatable/proc/Update()

// ...

// "components" is a list of various objects
// that may or may not have an "Update" proc defined
for(var/updatable/updatable as interface in components)
// "updatable" only ever refers to an element in "components"
// that satisfies "hasinterface(updatable, /updatable)".
updatable.Update()

interactor
// ...

interactable
proc
// ...not that the parameters actually matter here
Interact(interactor/interactor)

mob/player
ButtonPressed(button)
if(button == INTERACT_BUTTON)
// Get the first thing in the turf that satisfies hasinterface(thing, /interactable).
var interactable/interactable = locate() as interface in get_step(src, dir)
interactable.Interact(src)



Part 3 of my feature request is a compile-time type safety checker for types that explicitly implement interfaces.
It would be convenient for there to be a way to specify what interfaces should be implemented by a type.
If a type is specified to implement a particular interface, then that type should be required to have all the vars and procs of that interface; otherwise, you get a compile error.




I tried my hand at implementing some of these features myself.

hasinterface() requires that an object has all the vars and procs of a specific type. I needed a way to get all the vars and procs of a type without instantiating it.

Long story short, it's probably not possible to get the vars of a type without instantiating it. The usual trick with initial() fails at compile-time with a "cannot change constant value" error.
var datum/d = type
return initial(d.vars)

That aside, it is possible to get all the procs of a type (that are defined in DM, at least), using typesof("[type]/proc") on the type and all of its ancestors. The only problem there was getting the ancestors of a type, as trying to read the parent_type fails at compile-time with a "Cannot change constant value" error, and then fails at run-time with a "Cannot read [type] ([type]).parent_type" error (even though Dream Maker says it's a constant value).
var datum/d = type
return initial(d.parent_type) // compile error; I'm not trying to change any constants...
return d.parent_type // runtime error; ...But you said it was constant

I managed to write a "get direct children of type" proc, and then wrote a "get parent type" that checks everything in typesof(/datum) for the one where the type is a direct child.

Filtering a list based on a predicate is pretty straightforward, so I won't go into that.

As for declaring at whether a type should implement another type, I went with this:
#define IMPLEMENTS(INTERFACES...) Implements(interface) switch(interface) { if(INTERFACES) return hasinterface(src, interface); else return ..() }

datum/proc/Implements(interface) return ispath(type, interface)

// example 1:

startable/proc/Start()
vehicle/proc/Drive()
explodable/proc/Explode()

car
IMPLEMENTS(/startable, /vehicle)

// you can declare multiple IMPLEMENTS() lines since the same proc can be overridden multiple times
IMPLEMENTS(/explodable)

proc/Start()
proc/Drive()

mob/verb/test_car()
var car/car = new
ASSERT(car.Implements(/startable))
ASSERT(car.Implements(/vehicle))
ASSERT(car.Implements(/explodable)) // this fails as it should because /car doesn't have an Explode() proc

// example 2:

updatable/proc/Update()

dummy

live
IMPLEMENTS(/updatable)

proc/Update()
world << "Update!"

mob
verb
filter_test()
var list/things = newlist(/dummy, /dummy, /live, /dummy, /live)

var updatable/updatable
for(var/u in filter_by_interface(things, /updatable))
// u is guaranteed to implement /updatable
updatable = u
updatable.Update()

/*
for(var/updatable/updatable as interface in things)
updatable.Update()
*/
all my this
RIP dan