An Introduction

Abhishek

Engineer @ Netflix

 

Twitter/Github: @cabhishek

San Francisco

What is Nim?

Nim is an incredibly fast systems and applications programming language that is expressive, extensible and fun to use.

echo "hello, 世界"

Who develops Nim?

  • 300+ Commiters
  • 2000+ PR's merged
  • 3000+ Issues fixed
  • 500+ Packages
  • 12,000+ Commits

Volunteers

  • Actively developed ~2008 
  • Nim is free software; MIT License
  • Created by Andreas Rumpf

Language Goals

  • as fast as C

  • as expressive as Python

  • as extensible as Lisp

Why Nim?

Efficient

Expressive

Metaprogramming

Cross Platform

Expressive

Clean syntax

proc search(a: seq[int], key: int): bool =
  # type inferred
  var
    low = 0
    high = len(a) - 1

  while low <= high:
    let mid: int = (low + high) div 2 # integer division
    if key < a[mid]:
      high = mid - 1
    elif key > a[mid]:
      low = mid + 1
    else:
      return true

  return false

echo search(@[2,3,4,5,6,7,8], 5) # true

Universal function call syntax (UFCS)

  • UFCS allows  procedures to be called using the syntax for method calls, by using the receiver as the first parameter, and the given arguments as the remaining parameters
    • e.g It allows you to write a.square(b) or square(a, b) interchangeably
  • This feature is especially useful when chaining complex function calls

 

Instead of writing

 

foo(bar(a))
a.bar().foo()

It is possible to write

echo @[1,2,3,4,5].map(a => a * 2).filter(a => a > 2).foldr(a + b) # 28
echo evens(divide(multiply(values, 10), 3))

vs

echo values.multiply(10).divide(3).evens

Intuitive user defined type system

import strutils

type
    Person = object
        name: string
        age: int

proc speak (p: Person) =
    echo "My name is $1 and I am $2 years old." % [p.name, $p.age]

let person = Person(name: "jim", age: 30)

person.speak() # My name is jim and I am 30 years old.

# Basic OOP 

type
  Parent = ref object of RootObj
      name: string
      
  Father = ref object of Parent
  Mother = ref object of Parent
      
  Student = object
      parent: Parent
      grade: string
      
method getName(self: Parent): string {.base.} = 
  quit("To override")

method getName(self: Father): string = 
  "Father's name: " & self.name

method getName(self: Mother): string = 
  "Mother's name: " & self.name    

let 
  student1 = Student(grade: "A", parent: Father(name: "Jim"))
  student2 = Student(grade: "C", parent: Mother(name: "Jane"))

echo student1.parent.getName() # Father's name: Jim
echo student2.parent.getName() # Mother's name: Jane

Operator overloading

type Point = tuple[x, y: int]

proc `+`(a, b: Point): Point =
  (a.x + b.x, a.y + b.y)

let
  p1 = (x: -1, y: 4)
  p2 = (x: 5, y: -2)
  p3 = p1 + p2

echo p3 # (x: 4, y: 2)
iterator `->`(from, to: int): int =
  var i = from
  while i < to:
    yield i
    inc(i)

for x in 0->5: echo x
  • Operators can have different implementations depending on their arguments
  • Operators are simply overloaded procedures
type Point = tuple[x, y: int]

proc `+`(a, b: Point): Point =
  (a.x + b.x, a.y + b.y)

let
  p1 = (x: -1, y: 4)
  p2 = (x: 5, y: -2)
  p3 = `+`(p1, p2)

echo p3 # (x: 4, y: 2)

First class functions + closures

proc range(start, till: int, step: int = 1): iterator(): int =
  return iterator(): int {.closure.} =
    var x = start
    while x < till:
      yield  x
      x += step

proc print(iter: iterator(): int) =
  for item in iter(): echo item

print(range(1, 10, step=2))  # 1 3 5 7 9
import sequtils

proc double(x: int): int = x*x

let square = double

echo map(@[1,2,3], square) #1, 4, 9
import seqUtils

echo map(
    @[1,2,4,5], 
    proc(x: int): int =
        let y = x * 2
        y + 2
)

# @[4, 6, 10, 12]

Rich standard library

  • json

  • httpclient (async + sync)

  • unittest

  • os  -  Basic  operating system facilities

  • strutils  -  Various string utility routines

  • sequtils  -  Operations for the built-in seq type which are inspired by functional programming languages

  • tables  -  Hashtable/Dictionary

  • algorithm & math

  • ...

Metaprogramming

Metaprogramming is a programming technique in which computer programs have the ability to treat programs as their data. It means that a program can be designed to read, generate, analyse or transform other programs, and even modify itself while running

- ​Wikipedia

Generics

proc max[T](a, b: T): T = 
  if a < b:
      return b 
  else:
      return a

echo max(1, 2) # 2, inferred type is max[int]
echo max[float](11.9, 8.0) # 11.9, explicit type
echo max("hello", "world") # world
echo max("one", 1) #Error: type mismatch
  • Write algorithms in terms of types that are not known until the algorithm is invoked
type 
    Node[T] = object
      next: Node[T]
      data: T

proc newNode[T](data: T): Node[T] =
  Node[T](data: data)

let
    n1 = newNode(12)
    n2 = newNode("New York")
proc max[T: int | float](a, b: T): T = 
    if a < b:
        return b 
    else:
        return a

echo max("hello", "world") # type mismatch error

Templates

  • Allows simple substitution mechanism operating on Nim's AST

  • To invoke a template, call it like a procedure

  • Templates are declarative

echo mul(2, 3)
echo 2 * 3

At compile time its re-written to

template mul(x, y: int): int = x * y

Macros

  • Enable advanced compile-time code transformations
  • Reduce code boilerplate
  • Macros are procedural
import macros

macro mul(x, y: int): typed =
  expectKind(x, nnkIntLit)
  expectKind(y, nnkIntLit)

  let stmtList = newNimNode(nnkStmtList)
  stmtList.add(
    newCall(
      newIdentNode("echo"), 
      newStrLitNode("Result is: "),
      infix(x, "*", y)
    )      
  )
  return stmtList

mul(2, 2) # Result is: 4
import marcos

dumpTree:
  echo("Result is: ", 2*2)

# StmtList
# Call
#   Ident !"echo"
#   StrLit Result is: 
#   Infix
#     Ident !"*"
#     IntLit 2
#     IntLit 2

Efficient

Parallelism​

  • threads and threadpool module

  • GC safety, isolated heaps & sharing memory is restricted

import threadpool, locks

var lock: Lock
initLock(lock)

var counter {.guard: lock.} = 0

proc increment(x: int) = 
  for i in 0.. <x:
    withLock lock:
      inc(counter)

spawn increment(10_000)
spawn increment(10_000)

sync()
echo counter # 20000
var data = "Hello World"
 
proc showData() {.thread.} =
  echo data
 
var thread: Thread
createThread(thread, showData)

joinThread(thread)

# 'showData' is not GC-safe as it accesses 'data' 
# which is a global using GC'ed memory [GcUnsafe2]
import os, locks

var
  threads: array[0..4, Thread[int]]
  channel: Channel[string]
  counter: int
  lock: Lock

proc worker(interval: int) {.thread.} =
  while true:
    let data = channel.tryRecv()
    if data.dataAvailable:
      echo "> Got msg: " & data.msg
      echo "> Doing work"
      acquire(lock)
      counter += 1 #shared data, actual work is to increment
      release(lock)
      sleep(interval) # long running work
      echo "> Done with work"
      break
    else:
      # slow the channel poll
      sleep(interval)
      echo "> Waiting for work"

when isMainModule:
  initLock(lock)
  channel.open()
  defer: channel.close()

  for i in 0..high(threads):
    createThread(threads[i], worker, 200)

  for i in 0..high(threads):
    sleep(50) # delay to start work
    channel.send("Start work") # signal

  joinThreads(threads) # wait for completion

  echo "Counter value: " & $counter #5

Cross platform

  • Supports C/C++, Objective-C and Javascript backends

    • https://nim-lang.org/docs/backends.html

  • Cross compilation

    • https://nim-lang.org/docs/nimc.html#cross-compilation

  • Nim Installer

    • https://nim-lang.org/docs/niminst.html

Other language features

  • No GIL

  • FFI

  • Self hosted

  • async/await

  • Default function parameters

  • Variable length arguments

  • Named function arguments 

  • Multiple return values [via tuples]

  • Case/If statements as expression 

  • Package manager

  • Docs generator

  • ....

Real-world​ use cases? 

  • Web services / API's

  • Command line applications

  • Compilers

  • Scientific computing

  • Games

  • Automation scripts

  • UI applications

  • Python C extensions 

  • And lots more...

Why not Nim?

  • Smaller ecosystem

  • Latent compiler bugs (very rare)

Project showcase

  • Arraymancer: A fast, ergonomic and portable tensor library with a deep learning focus

  • ao: Physically based ray tracer in Nim

  • Jester: A sinatra-like web framework

  • Karax: Single page JS applications

  • Reel valley: read more here

  • Nim pymod: Call Nim from Python

  • Razcal: Build cross platform desktop app with Lua, MoonScript, and Layout Language

  • NimLox: Interpreter for the 'Lox' language written in Nim

  • ...

Links: How to get started

Summary

  • Language goals

    • as fast as C

    • as expressive as python

    • as extensible as Lisp

  • Key takeaway points

    • Incredibly fast, extensible & fun

    • Python look & feel

    •  Zero dependency binary distribution

    • Comprehensive Std library

 

Thanks

Questions?

@cabhishek

Nim: An Introduction [WIP]

By Abhishek Kapatkar

Nim: An Introduction [WIP]

  • 1,381