After many many hours of trying to make this work, I finally got it to go. I suspect I'm not the only person to struggle with this, so I figured I'd document the answer here.
Rather than getting into theoreticals, let's just jump into code.
Nim supports inheritance for objects. An example:
# Parent object
type
Animal = ref object of RootObj
id: int
method sayFeet(a: Animal): string =
result = "[" & $a.id & "] has an unknown number of feet"
# Child object
type
Dog = ref object of Animal
hairLength: float
legCount: int
method sayFeet(d: Dog): string =
result = "Dog [" & $d.id & "] has " & $d.legCount & " feet."
# Child object
type
Cat = ref object of Animal
whiskerCount: int
pawCount: int
method sayFeet(c: Cat): string =
result = "Cat [" & $c.id & "] has " & $c.pawCount & " feet."
# generic proc
proc describe(foo: Animal) =
echo "animal detail: " & sayFeet(foo)
# use it
var
sparky = Dog(id: 1, legCount: 4)
mittens = Cat(id: 2, pawCount: 4)
sparky.describe() # "animal detail: Dog [1] has 4 feet."
mittens.describe() # "animal detail: Cat [2] has 4 feet."
Yes, this and all the examples are silly. This more about proof-of-concept.
Nim also supports generics in object types. Among other things, this allows for compile-time optimization.
type
Domestication = enum
Wild
Feral
Domestic
type
Animal[wildness: static[Domestication]] = ref object of RootObj
id: int
proc sayFeet(a: Animal): string =
result = "[" & $a.id & "] has an unknown number of feet"
proc describe(foo: Animal) =
when foo.wildness == Wild:
echo "Wild animal detail: " & sayFeet(foo)
elif foo.wildness == Feral:
echo "Feral animal detail: " & sayFeet(foo)
else:
echo "Tame animal detail: " & sayFeet(foo)
var
sparky = Animal[Feral](id: 1)
mittens = Animal[Domestic](id: 2)
sparky.describe() # "Feral animal detail: [1] has an unknown number of feet."
mittens.describe() # "Tame animal detail: [2] has an unknonw number of feet."
But, can you put both ideas together? Yes, but there are apparently a few things to keep in mind.
The example of both:
# Parent object
type
Domestication = enum
Wild
Feral
Domestic
type
Animal[wildness: static[Domestication]] = ref object of RootObj
id: int
proc sayFeet(a: Animal): string =
result = "[" & $a.id & "] has an unknown number of feet"
# Child object
type
Dog[wildness: static[Domestication]] = ref object of Animal[wildness]
hairLength: float
legCount: int
proc sayFeet(d: Dog): string =
result = "Dog [" & $d.id & "] has " & $d.legCount & " feet."
# Child object
type
Cat[wildness: static[Domestication]] = ref object of Animal[wildness]
whiskerCount: int
pawCount: int
proc sayFeet(c: Cat): string =
result = "Cat [" & $c.id & "] has " & $c.pawCount & " feet."
# generic proc
proc describe(foo: Animal) =
when foo.wildness == Wild:
echo "Wild animal detail: " & sayFeet(foo)
elif foo.wildness == Feral:
echo "Feral animal detail: " & sayFeet(foo)
else:
echo "Tame animal detail: " & sayFeet(foo)
# use it
var
sparky = Dog[Feral](id: 1, legCount: 4)
mittens = Cat[Domestic](id: 2, pawCount: 4)
sparky.describe() # "Feral animal detail: Dog [1] has 4 feet."
mittens.describe() # "Tame animal detail: Cat [2] has 4 feet."
Some things to watch out for:
Hopefully this is helpful to somebody. :-)
Off-topic: is there the equivalent of a "when case"?
I like that nim checks for missing scenarios when writing "case" statements.
I like that nim allows for compile-time code decisions with "when" directives.
Putting those together would be spiffy. Not a top-tier feature by any means, but it would be nice.
I've been fighting with inheritance + generics in the past to implement layers of neural networks.
I think your example wouldn't work if a Dog[Domestic] and a Cat[Domestic] are stored in the same datastructure or if you use the value through a proc declared in another module.
In the end, what I do is have the generic inherited objects carry their "handler" in a field to avoid methods.
Inheritance WITHOUT generics: use "method" not "proc" or it will fail. Inheritance WITH generics, use "proc" not "method" or it will fail. And no, I don't know why.
Not sure whether I understand that correctly: does it mean that the third code example wouldn't work with method sayFeet? Because for me, it does work with Nim stable and nim c -r --multimethods:on.