Twitter Email
subspace (/ˈsʌbspɛɪs/)
A Jekyll playground site

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
Last modified: , 430 Words
22:10 on Monday, 13. June 2022 | by Admin in Nim

Dynamically allocating objects on the heap

Code: (click to select all)
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.

Code: (click to select all)
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.

nim