Nim supports object-oriented programming (OOP) principles, including member functions (methods), inheritance, and polymorphism. This guide will help you understand how to use these features effectively.

Member Functions (Methods)

Member functions, or methods, in Nim can be implemented using procedures (procs). Thanks to the Uniform Function Call Syntax (UFCS), you can call procs as if they were methods.

  type Animal = object
  name: string
  age: int

proc speak(self: Animal, msg: string) =
  echo self.name & " says: " & msg

var sparky = Animal(name: "Sparky", age: 10)
sparky.speak("Hi")  # Method call syntax
speak(sparky, "Hi") # Traditional proc call syntax
  

UFCS allows calling procs with dot notation, making the code more readable and object-oriented.

  proc double(num: int): int =
  return num * 2

echo double(10)     # Traditional call
echo 10.double()    # UFCS call
echo 10.double      # UFCS call without parentheses
  

Mutability in Methods

When using UFCS and procs as member functions, it’s crucial to handle mutability correctly. By default, proc arguments are immutable.

  type Animal = object
  name: string
  age: int

proc incAge(self: Animal) =
  self.age += 1  # Error: self.age is immutable

proc setName(self: Animal, name: string) =
  self.name = name  # Error: self.name cannot be assigned to
  

To modify the object’s state, mark the argument as mutable using var.

  proc incAge(self: var Animal) =
  self.age += 1

proc setName(self: var Animal, name: string) =
  self.name = name

var sparky = Animal(name: "Sparky", age: 3)
sparky.incAge()
sparky.setName("Spark")
  

For ref objects, mutability is managed through pointers, making them inherently mutable.

  type Animal = ref object
  name: string
  age: int

proc incAge(self: Animal) =
  self.age += 1

var sparky = Animal(name: "Sparky", age: 3)
sparky.incAge()
  

Inheritance

Nim supports inheritance, allowing you to create subtypes and override methods. Use the of keyword to create a subtype and method to define dynamically dispatched methods.

  type Animal = ref object of RootObj
  name: string
  age: int

method vocalize(self: Animal): string {.base.} = "..."
method ageHumanYrs(self: Animal): int {.base.} = self.age

type Dog = ref object of Animal
method vocalize(self: Dog): string = "woof"
method ageHumanYrs(self: Dog): int = self.age * 7

type Cat = ref object of Animal
method vocalize(self: Cat): string = "meow"

var animals: seq[Animal] = @[]
animals.add(Dog(name: "Sparky", age: 10))
animals.add(Cat(name: "Mitten", age: 10))

for a in animals:
  echo a.vocalize()
  echo a.ageHumanYrs()
  

Output:

  woof
70
meow
10
  

Procs vs. Methods

Procs are statically dispatched, making them more performant than dynamically dispatched methods. Use methods only when you need dynamic dispatch.

Testing Subtypes

You can check if an object is of a given subtype using the of keyword.

  echo(animals[0] of Dog)    # true
echo(animals[0] of Cat)    # false
echo(animals[0] of Animal) # true
  

Summary

  • Procs: Use for static dispatch, more performant.
  • Methods: Use for dynamic dispatch, useful for polymorphism.
  • UFCS: Allows calling procs with dot notation for a more OOP-like syntax.
  • Mutability: Mark arguments as var for mutable access in procs.
  • Inheritance: Use of keyword for creating subtypes and overriding methods.

By following these guidelines, you can leverage Nim’s OOP features to write clean, efficient, and maintainable code.

Learn How To Build AI Projects

Now, if you are interested in upskilling in 2024 with AI development, check out this 6 AI advanced projects with Golang where you will learn about building with AI and getting the best knowledge there is currently. Here’s the link.

Last updated 17 Aug 2024, 12:31 +0200 . history