Sorry for such a basic OOP type question in advance.
How do I emulate Ruby's super() for a subclassed object's constructor?
BTW: I have no idea why RST is bolding the " When I create a ..." line above???
type
ParentObj* = ref object of RootObj
parentField: int
ChildObj* = ref object of ParentObj
childField: string
proc newParent*(value: int): ParentObj =
new result
result.parentField = value
# This works... but requires needless creation of a separate parent.
proc newChild*(value: string): ChildObj =
let parent = newParent 123 # *** How to eliminte this ***
new result
result.parentField = parent.parentField
result.childField = value
var child = newChild "ABC"
echo "child parentField: ", child.parentField
echo "child childField: ", child.childField
One way to do this would be to separate out the initialisation and object creation into to procs:
proc initParent(p: ParentObj, value: int) =
p.parentField = value
proc newParent*(value: int): ParentObj =
new result
result.initParent(value)
# This works... but requires needless creation of a separate parent.
proc newChild*(value: string): ChildObj =
new result
result.initParent 123
result.childField = value
Another way would be to use generics like this:
proc newParent*[T: ParentObj](value: int): T =
new result
result.parentField = value
# This works... but requires needless creation of a separate parent.
proc newChild*(value: string): ChildObj =
result = newParent[ChildObj](123)
result.childField = value
While this might seem cumbersome compared to languages with lots of inbuilt language features for OOP, I personally prefer this approach, because it makes this language simpler and more explicit. E.g. when working in C++ initialisation order etc. is a field of constant gotchas, because it is all implicit.
The direct way to separate the parent's initialization is not use methods since there's no need for dynamic dispatch, even though an OOP language would use super in such a case:
type
ParentObj* = ref object of RootObj
parentField: int
ChildObj* = ref object of ParentObj
childField: int
proc init*(parent: ParentObj, n: int) =
parent.parentField = n
proc init*(child: ChildObj, n: int) =
child.ParentObj.init(123)
child.childField = n
proc newChild*(value: int): ChildObj =
new result
result.init(value)
var child = newChild 567
echo "child parentField: ", child.childField
echo "child childField: ", child.parentField
What procCall is for, is these circumstances where some specific resolution is static and known, but where you're using methods because the resolution is usually dynamic. I can't think of a good example of that, so here's typical OO-example fare:
import std/sequtils
type
Animal = ref object of RootObj
noisiness: int
Dog = ref object of Animal
method bark(a: Animal) {.base.} =
inc a.noisiness
method bark(a: Dog) =
echo "A dog barks"
procCall bark(Animal(a))
proc thunder(area: var seq[Animal]) =
for animal in area:
animal.bark()
# late addition
type Cat = ref object of Animal
method bark(a: Cat) =
echo "A cat meows"
procCall bark(Animal(a))
var area: seq[Animal] = @[new Dog, new Dog, new Cat]
thunder area
thunder area
thunder area
echo "Noisiness: ", area.foldl(a+b.noisiness, 0)
Thanks @doofenstein... that works just fine and requires no magic. I like it 🙂
I also really appreciate the feedback on Obj suffix.
The resulting exemplar now looks like:
type
Parent* = ref object of RootObj
parentField: int
Child* = ref object of Parent
childField: string
# Split the initialization and object creation!
proc initParent(parent: Parent, value = 123) =
parent.parentField = value
proc newParent*(value: int): Parent =
new result
result.initParent value
proc newChild*(value: string): Child =
new result
result.initParent 321
result.childField = value
var child = newChild "ABC"
echo "child parentField: ", child.parentField
echo "child childField: ", child.childField
which generates the expected:
child parentField: 321
child childField: ABC
The generics approach seem a a little less straightforward and makes constructing a Parent a little more awkward (see below):
type
Parent = ref object of RootObj
parentField: int
Child = ref object of Parent
childField: string
block:
# Using generics
proc newParent[T: Parent](value = 123): T =
new result
result.parentField = value
proc newChild(value: string): Child =
result = newParent[Child]321
result.childField = value
var
parent = newParent[Parent]() # *** Too bad can't default Parent ***
child = newChild "ABC"
echo "parent parentField: ", parent.parentField
echo "child parentField : ", child.parentField
echo "child childField : ", child.childField
type
Parent = ref object of RootObj
parentField: int
Child = ref object of Parent
childField: string
# Using generics
proc newParent(T = typedesc[Parent], value = 123): T =
new result
result.parentField = value
proc newChild(value: string): Child =
result = newParent(Child, 321)
result.childField = value
var
parent = newParent()
child = newChild "ABC"
echo "parent parentField: ", parent.parentField
echo "child parentField : ", child.parentField
echo "child childField : ", child.childField