Playing with objects in Nim
Author: | Admin |
Title: | Nim playground, objects and stuff |
Language: | en-US |
Number of words: | 430 |
Created: | 22:10 on Monday, 13. June 2022 |
Modified: | 22:10 on Monday, 13. June 2022 |
Keywords: | nim, code, oop, objects |
Excerpt: | Some notes, I have taken while learning Nim. Right now, it’s fairly random and unorganized. This article deals with objects. Nim offers a richt feature set to support the OOP paradigm, including inheritance, run time binding and introspection. |
Tags: | nim |
Page layout: | no_sidebar |
Dynamically allocating objects on the heap
1
2
3
4
5
6
7
8
9
10
11
type Point = object
x, y: float64
name: string # everything needs a name
# dynamically alloc a non ref object
var an = cast[ptr Point](alloc0(sizeof(Point)))
an[].x = 10 # the dereference op [] is optional
an[].y = 10 # the compiler understands it's a pointer
an.name = "foobar" # dereference optional
echo an[]
reset(an.name) # free the string
dealloc(an) # and the object
Polymorphism (almost) like in C++
Lets assume 2 simple classes, Cat and Dog, both inheriting from a base class Animal
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Animal* = ref object of RootObj
name*: string
age*: int
method vocalize*(this: Animal): string {.base.} = "I do not know how to vocalize"
method ageHumanYrs*(this: Animal): int {.base.} = this.age
type Dog* = ref object of Animal
method vocalize*(this: Dog): string = "My name is " & $this.name & " and I woof"
method ageHumanYrs*(this: Dog): int = this.age * 7
type Cat* = ref object of Animal
method vocalize*(this: Cat): string = "My name is " & $this.name & " and I meow"
method ageHumanYrs*(this: Cat): int =
if this.age <= 2:
25
else:
((this.age - 2) * 4) + 25
The syntax is quite different from C++ but the idea should be clear. All animals have a name and a age,
obviously, but they sound differently and most animals age faster than humans. The methods vocalize()
and ageHumanYrs()
in the base class are unspecified without a real implementation. They are annotated
with the pragma .base.
which means methods with the same signature in a derived class will override
them. This is similar to the virtual
method in C++.
The derived classes Cat and Dog implement their own methods, because cats and dogs obviously do not sound
the same and they also age at different speeds compared to a human. In OOP lingo, we say, the derived
classes override the method in their base class, and in many languages, it is mandatory to indicate
this by declaring override methods with a special keyword or compiler annotation. In Nim, however, there
is no way nor need to explicitly annotate such methods. The compiler is clever enough to notify that
these methods are overriding base class methods. But care must be taken to use the keyword method
instead of proc
, because methods are treated differently and allow dynamic binding. Procs do not.
Polymorphism
In C++ I can instantiate a derived class and assign it to a variable of it’s base class type. This is possible because C++ can bind virtual methods at runtime and ensure that the right methods will be called. The same is possible in Nim, so I can safely do:
1
2
3
var myDog: Animal = Dog(name: "Jerry": age: 8)
echo myDog.vocalize()
echo "I am ", myDog.ageHumanYrs(), " human years old"
We use a variable of type Animal
and assign it a Dog
object. At runtime, the application will call
the right methods, in this case the overridden methods vocalize()
and ageHumanYrs()
in class Dog
instead of the base methods in Animal
. Like said above, it’s important do declare the methods with
method
and not proc
and to tag base methods with the .base.
pragma.
How to call a base method.
Given the above object hierarchy with a Dog
object inheriting from a base Animal
, how could it be
possible to call the base method of vocalize()
in a Dog object?
1
2
3
4
var myDog: Animal = Dog(name: "Jerry": age: 8)
echo myDog.vocalize()
echo "I am ", myDog.ageHumanYrs(), " human years old"
echo procCall myDog.Animal.vocalize()
The magic lies in the procCall
statement (see line 4). It allows to call methods of the base
object ignoring overriden methods in the inheriting object. While this is not a commonly needed
feature, it can be useful in some scenarios. Think about super
in other languages. procCall
is
similar.