I have a type which contains instances of itself. I would like to inherit from this type but when I try to access the contained instances, it seems that the subtype is changed to the parent type.. The code below yields (children: ...) instead of (ext: false, children: ...) as I had hoped.
Is there a way to achieve this?
type
Node = ref object of RootObj
children: seq[Node]
ExtNode = ref object of Node
ext: bool
var
n0 = ExtNode()
n1 = ExtNode()
n0.children.add(n1)
echo n0.children[0]
Yes, inheritance gives you what your parent type provides, there is no magical conversion. So it is not surprising. But your dynamic runtime type is ExtNode in your example, you can test with of keyword for it:
type
Node = ref object of RootObj
children: seq[Node]
ExtNode = ref object of Node
ext: bool
var
n0 = ExtNode()
n1 = ExtNode()
n0.children.add(n1)
echo n0.children[0][]
if n0.children[0] of ExtNode:
echo ExtNode(n0.children[0])[]
$ ./t
(children: ...)
(ext: false, children: ...)
Yes, inheritance gives you what your parent type provides, there is no magical conversion.
Although it seems there's a conversion from ExtNode to Node when adding n1 ...
So it is not surprising.
... and I found this quite surprising. ;-)
This still seems unintuitive, even after trying the code in the playground and trying to understand the output. (I'm not saying Nim's behavior is "wrong", only not at all what I would have expected. :-) )
I don’t find this surprising either. There is no reason for children of ExtNodes to be restricted to ExtNode. An ExtNode is a Node and, so, its children are Nodes which may be but are not required to be ExtNodes.
Node is defined to contains instances of Nodes, not of instance of itself. We would need a special syntax to express itself.
... and I found this quite surprising. ;-)
I'm guessing you come from a dynamic-languages background (Python, Ruby, JS...) What you're running into here is pretty fundamental to statically-typed OO languages — the difference between the static type of an object reference, vs. the dynamic type of the instance it points to at runtime. It occurs in Java, C++, Rust, Go, etc.
I have used Pascal and C++ in the past, but it's a while ago.
Good point about the static type and the runtime type, thank you. Still, I had expected that echo - at runtime - would know/use the dynamic type. That said, my interpretation now is that when the first echo call is compiled, it only knows about children being Node s, so the compiled code only considers this generic case. I hope that makes sense. :-)
Still, I had expected that echo - at runtime - would know/use the dynamic type.
Yeah, on second thought that's surprising. Objects must contain some hidden type metadata, for a check like n0.children[0] of ExtNode to work. So I would expect echo to look up the dynamic type of the object when inspecting its type and contents. Maybe this is just a limitation of echo (or whatever introspection function it calls.)
It doesn't seem to me that it is just a limitation of echo.
This fails with Error: attempting to call undeclared routine: 'ext=':
n0.children[0].ext = true
Whereas this works:
ExtNode(n0.children[0]).ext = true
echo n1[]
Outputting (ext: true, children: ...)
Thanks everyone for the discussion on this!
The code below yields (children: ...) instead of (ext: false, children: ...) as I had hoped.
The output I get on nim 1.2.0 is
(children: ...) (ext: false, children: ...)
Your object has a dynamic type, but it doesn't have a vtable, because there's no such thing in Nim. echo is calling the $ proc defined on Node, so that's what you get. If you want runtime polymorphism then you can use methods:
type
Node = ref object of RootObj
children: seq[Node]
ExtNode = ref object of Node
ext: bool
var
n0 = ExtNode()
n1 = ExtNode()
method `$`(n: Node): string =
"no ext here"
method `$`(n: ExtNode): string =
"ext=" & $n.ExtNode.ext
n0.children.add(n1)
n0.children.add(Node())
echo n0.children[0]
echo n0.children[1]
prints
ext=false no ext here