For Python Programmers

Ever wondered if there existed a language

  • as fast as C

  • as expressive as Python

  • as extensible as Lisp

Look no further then!

Abhishek

Engineer @ Netflix

 

Twitter/Github: @cabhishek

San Francisco

Talk outline

  • Quick Intro
  • Python vs Nim
  • Why Nim?
  • Nim resources

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?

  • 400+ Committers
  • 3000+ PR's merged
  • 4000+ Issues fixed
  • 500+ Packages
  • 14,000+ Commits

Volunteers

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

Language goals

  1. Efficiency

  2. Expressiveness

  3. Elegance

Python vs Nim

A Map of the Territory

Features Python Nim
Execution Model Interpreted Compiled
Memory Management Automatic Automatic or Manual
Types Dynamic Static
Generics Duck Typing Yes
Object Oriented Yes Minimal
Closures Yes Yes
Function overload No Yes
Distribution Requires CPython Binary
Multi Threaded No Yes
Performance Depends? Comparable to C/C++

Key differences

  • Type system
  • Less emphasis on OOP
  • Object vs Value type
  • Procedural programming
  • Strict compile time checks
  • Execution model
  • Performance 

Code samples

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, strformat

type
    Person = object
        name: string
        age: int

proc speak (p: Person) =
    echo fmt"My name is {p.name} and I am {p.age} years old."

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

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

type 
  Status = enum
    open, closed, pending
  
  Account = object
    status: Status
    balance: float
    name: string

proc deposit(account: var Account, amount: float) =
  account.balance += amount

var account: Account = (
  status: Status.open, 
  balance: 150.2, 
  name: "my savings"
)
account.deposit(20)

echo account.balance # 170.2

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
  • Generic type definition and you can specialize it by specifying a particular type on a case-by-case basis
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
  • https://nim-lang.org/docs/tut3.html​
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​

  • GC safety, isolated heaps & sharing memory is restricted

  • threads and threadpool module

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++ 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

  • Self hosted

  • No GIL

  • FFI (Foreign function interface)

  • 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 more...

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: Game in JS. Details here

  • Spry: Programing language

  • 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

  • ...

Why not Nim?

  • Smaller ecosystem

  • Occasional compiler bugs

Links: How to get started

Summary

  • Language goals

    1. Efficiency

    2. Expressiveness

    3. Elegance 

  • Key takeaway points

    • Incredibly fast, extensible & fun

    • Python look & feel

    •  Zero dependency binary distribution

    • Comprehensive Std library

 

Thanks

Questions?

@cabhishek

Nim for Python programmers

By Abhishek Kapatkar

Nim for Python programmers

Intro to Nim language for python developers.

  • 3,085