ID:2476939
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
Interfaces are a very useful tool having non linear inherited shared API across types. I'm going to assume the reader knows what interfaces are conceptually. If you do not know what they are go search up a guide on what they are in a language of choice like C# or java.

Currently dm for all purely functional purposes supports using interfaces. As an example, this works exactly as intended:
/interface/explodable/proc/explode()
// This space intentionally left blank

/obj/bomb/proc/explode()
// Do explosion things

/proc/blow_up_turf_with_something(turf/place, bombtype=/obj/bomb)
var/interface/explodable/explosive = new bombtype(place)
explosive.explode()


You could take the above and make a subtype of mob that's explodable and use it in blow_up_turf_with_something(place, /mob/bomber) too, it's a nice pattern that allows types that share no internal logic but do share capabilities.

There's a problem here though, this is not compile time enforced at all and unintuitive when reading. Using this pattern currently would be a recipe for disaster if anyone ever wants to try adding functionality to the interface. You also can't easily check in user code if something actually properly implements everything needed for the interface.

Fixing this is fairly simple for syntax, and hopefully also fairly simple in the internal compiler code since I don't *think* it's doing anything particularly new. We need a few things:

* A way to specify what interfaces a type is implementing
* (Optional) A way to check in runtime code if a type/instance implements a particular interface
* (Optional) An interface type which can not be instanced or have any defined behavior or assigned values

--- Implementation syntax
The syntax for specifying which interfaces are being implemented should be able to accept more than one interface. We have two main options I see there:

Piggybacking off the existing type values and just making an interfaces variable
/obj/bomb
interfaces = list(/interface/explodable)


Or we could add a new syntax entirely more similar to how other languages do it
/obj/bomb #(/interface/explodable)


The point is though the compiler needs to know what interface a type is implementing so it know to error when that type does not implement all the expected procs and variables required by that interface.

--- Runtime checks
This is fairly simple, interface versions of istype() and ispath() would be just fine. Perhaps named isimplementor()?

--- An interface type
Very optional. An interface type which can't be instanced or have any default logic and behavior makes it a bit easier avoid strange occurrences.
Having an "interfaces" list (which is read-only) makes sense, but it would have to merge with the parent type's interfaces rather than overwrite it the way variable overrides normally do.

There is an existing "plan" for a hasinterface() proc, but it's ancient:

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.
(see The Grey Book for more of these)
Runtime checks for that kind of thing is nice but full support on the compiler side makes it a lot cleaner and maintainable in the long run when large codebases are involved
You can sort of accomplish this through the preprocessor, but it's a huge, hacky pain in the ass and doesn't cooperate with inheritance at all. A real implementation would certainly be nice.