-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Nim for Python Programmers
Feature | 🐍 Python | 👑 Nim |
---|---|---|
Execution model | Virtual Machine (Interpreter) | Machine code via C/C++ (Compiler) |
Written using | C (CPython) | Nim |
License | Python Software Foundation License | MIT |
Version (Major) | 3.x |
1.x |
Metaprogramming | ✔️ metaclass, exec, eval, ast (Run-time code expansion) | ✔️ template, macros (Compile-time code expansion) |
Memory management | Garbage collector | Multi-paradigm memory management (garbage collectors, ARC/ORC, manual) |
Typing | Dynamic, Duck Typing | Static |
Dependent types | ❎ | ✔️ Partial support |
Generics | Duck Typing | ✔️ |
int8/16/32/64 types | ❎ | ✔️ |
uint8/16/32/64 types | ❎ | ✔️ |
float32/float64 types | ❎ | ✔️ |
Char types | ❎ | ✔️ |
Subrange types | ✔️ | ✔️ |
Enum types | ✔️ | ✔️ |
Bigints (arbitrary size) | ✔️ | ✔️ jsbigints, #14696 |
Biggest built-in integer | Unknown, limited by free memory |
18_446_744_073_709_551_615 for uint64 type |
Arrays | ✔️ | ✔️ |
Type inference | Duck typing | ✔️ |
Closures | ✔️ | ✔️ |
Operator overloading | ✔️ | ✔️ on any type |
Custom operators | ❎ | ✔️ |
Object-Oriented | ✔️ | ✔️ |
Methods | ✔️ | ✔️ |
Exceptions | ✔️ | ✔️ |
Anonymous functions | ✔️ multi-line, single-expression | ✔️ multi-line, multi-expression |
List comprehensions | ✔️ | ✔️ |
Dict comprehensions | ✔️ | ✔️ |
Set comprehensions | ✔️ | ✔️ |
Custom object comprehensions | ✔️ generator expression | ✔️ |
Pattern Matching builtin | ❎ | ✔️ |
Immutability of types | Basic types (number, string, bool), tuple, frozenset | ✔️ |
Immutability of variables | ❎ | ✔️ |
Function arguments immutability | Depending on type | Immutable |
Formatted string literals | ✔️ f-strings | ✔️ strformat |
FFI | ✔️ ctypes, C extension API (Cython via pip) | ✔️ C, C++, Objective C, JS (depending on used backend) |
Async | ✔️ | ✔️ |
Threads | ✔️ Global Interpreter Lock | ✔️ |
Regex | ✔️ Perl-compatible | ✔️ Perl-compatible |
Documentation comments | ✔️ plain-text multi-line strings (reStructuredText via Sphinx) | ✔️ ReStructuredText/Markdown |
Package publishing | ✔️ not built-in, requires twine
|
✔️ built-in, nimble
|
Package manager | ✔️ pip
|
✔️ nimble
|
Code autoformatter | ✔️ black and others via pip |
✔️ nimpretty built-in, nimlint
|
File extensions | .py, .pyw, .pyc, .pyd, .so | .nim, .nims |
Temporary intermediate representation (IR) format | .pyc (CPython VM bytecode) | C, C++, Objective C (LLVM IR via nlvm) |
Uses #!shebang on files | ✔️ | ✔️ nimr , nimcr
|
REPL | ✔️ | inim, Nim4Colab |
Indentation | Tabs and spaces, uniform per code block, 4 spaces by convention | Spaces only, uniform per code block, 2 spaces by convention |
Notes:
- Python anonymous function lambdas are known to be slow compared to normal functions.
- Python Regex claims to be PCRE compatible, but in practice PCRE Regexes may not work.
- Python "multi-line" anonymous functions may require using
;
and Linters/IDE may complain about it.
Creating a new variable uses var
or let
or const
.
Nim has immutability and compile-time function execution.
You can assign functions to variables.
Feature | const |
let |
var |
---|---|---|---|
Run-Time | NO | ✔️ YES | ✔️ YES |
Compile-Time | ✔️ YES | NO | NO |
Immutable | ✔️ YES | ✔️ YES | NO |
AutoInitialized | ✔️ YES | ✔️ YES | ✔️ YES |
Reassignable | NO | NO | ✔️ YES |
Requires Assignment | ✔️ YES | ✔️ YES | NO |
Can be Global | ✔️ YES | ✔️ YES | ✔️ YES |
For advanced users, is possible to skip variable Auto-Initialization.
Variables can be multi-line without "escaping" them, useful for long lines and long ternary operators, minimal example:
variable = 666 + \
420 * \
42 - \
9
assert variable == 18297
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var variable = 666 +
420 *
42 -
9
assert variable == 18297
This works with function calls too:
import std/strutils
var variable = " 12345 "
.strip
.parseInt
assert variable == 12345
You can use underscores, new lines and white spaces on variable names:
let `this must be
positive`: Positive = 42
assert this_must_be_positive == 42
const `this is my nice named variable` = 42
You can use reserved keywords as variable names.
When learning Nim or quick prototyping it's okay to use var
, although it's much better to learn the difference between different variable declarations.
Spaces must be consistent on your code, mainly around operators:
echo 2 - 1 # OK
echo 2-1 # OK
Bad inconsistent spaces:
echo 2 -1 # Error
# ^ parses as "-1"
Omitting spaces on your code does not improve anything. All operators are functions in Nim.
- Scope "leaks", "bugs", "glitches", etc.
for x in range(0, 9):
if x == 6:
print(x)
print(x)
Output:
6
8 # Leak!
⬆️ Python ⬆️ ⬇️ Nim ⬇️
for x in 0..9:
if x == 6:
echo x
echo x
Output:
Error: undeclared identifier: 'x'
Note that in the example we use a simple int
,
but imagine if x
was a few Gigabytes of RAM in size,
it may "leak" out of the for
loop, to the rest of the outer or main scope.
Another example:
x = 0
y = 0
def example():
x = 1
y = 1
class C:
assert x == 0 and y == 1 # ???
x = 2
example()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var x = 0
var y = 0
proc example() =
var x = 1
var y = 1
type C = object
assert x == 1 and y == 1
x = 2
example()
More on the same example:
x = 0
y = 0
def example():
x = 1
y = 1
class C:
assert x == 0 and y == 0 # ???
x = 2
try:
raise
except Exception as y:
pass
example()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var x = 0
var y = 0
proc example() =
var x = 1
var y = 1
type C = object
assert x == 1 and y == 1
x = 2
try:
raise
except Exception as y:
discard
example()
def example(argument = [0]):
argument.append(42)
return argument
print(example())
print(example())
print(example())
Output:
[0, 42]
[0, 42, 42]
[0, 42, 42, 42]
⬆️ Python ⬆️ ⬇️ Nim ⬇️
func example(argument = @[0]): auto =
argument.add 42
return argument
echo example()
echo example()
echo example()
Output:
Error: type mismatch: got <seq[int], int literal(42)>
but expected one of:
proc add[T](x: var seq[T]; y: sink T)
first type mismatch at position: 1
required type for x: var seq[T]
but expression 'argument' is immutable, not 'var'
Import | 🐍 Python | 👑 Nim |
---|---|---|
Only one symbol, use unqualified | from math import sin |
from std/math import sin |
All symbols, use unqualified | from math import * |
import std/math (recommended) |
All symbols, use fully qualified | import math (recommended) |
from std/math import nil |
"import as" another name | import math as potato |
import std/math as potato |
Both of the above at the same time | ❎ | from std/math as m import nil |
All symbols except one, use unqualified | ❎ | import std/math except sin |
All symbols except several, use unqualified | ❎ | import std/math except sin, tan, PI |
Include another module in this module | ❎ | include somemodule |
Your modules and types are not going to collide!, even if you have types named like modules, just chill and keep coding...
In Nim, import std/math
imports all the symbols from the math
module (sin
, cos
, etc) so that they can be used unqualified. The Python equivalent is from math import *
.
If you prefer to not import all the symbols, and always use qualified names instead, the Nim code is from std/math import nil
. Then you can call math.sin()
, math.cos()
, etc. The Python equivalent is import math
.
It is generally safe to import all names in Nim because the compiler will not actually compile any unused functions (so there's no overhead), and since Nim is statically typed, it can usually distinguish between the two imported functions with the same names based on the types of the arguments they are called with. In the rare cases where the types are the same, you can still fully qualify the name to disambiguate.
Python and Nim share these import statements:
# Python and Nim
import foo, bar, baz
import foo
import bar
import baz
Alternative syntaxes:
# Python
import foo, \
bar, \
baz
# Nim
import foo,
bar,
baz
# Useful for small diffs when adding/removing imports
import
foo,
bar,
baz
import
foo, bar, baz,
more, imports
The variant with one import
statement per line is common in Python and Nim, but in Nim the form import foo, bar, baz
is also seen often.
More examples:
## This is documentation for the module.
# This is a comment.
include prelude
import std/sugar as stevia
from std/math import nil
from std/with as what import nil
__import__("math")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
template imports(s) = import s
imports math
Sometimes in the wild you may see code samples or files without the imports but they work anyway (?).
Nim can use import
from the compile command, or from a .nims
file:
nim c --import:sugar file.nim
nim c --import:folder/mymodule file.nim
nim js --import:strutils --include:mymodule file.nim
Sometimes projects or quick code examples use this to save some typing, thanks to Dead Code Elimination if the imported symbols are not used they will not exist on the compiled output.
See also:
Sometimes you may feel that Python has more symbols available by default without any import
compared to Nim,
to get a similar experience of having the basic most common imports ready to start coding you can use
prelude:
include prelude
echo now()
echo getCurrentDir()
echo "Hello $1".format("World")
prelude is an
include
file
that simply imports common modules for your convenience, to save some typing.
prelude works for JavaScript target too.
- If Symbols are unqualified, how do you know where symbols come from?
Given foo()
is a symbol:
- Python: you typically have
object.foo()
rather thanmodule.foo()
, no UFCS. - Nim: you typically have
foo()
, with UFCS support.
Typically the Editor/IDE should hint where the Symbols come from, like any other programming language:
Nim comes built-in with NimSuggest for Editor/IDE integrations.
Contrary to Python, Nim type system has all the info about all the symbols:
import std/macros
macro findSym(thing: typed) = echo thing.getType.lineInfo
findSym:
echo # Where echo comes from?.
echo
comes from:
lib/system.nim(1929, 12)
When learning Nim or quick prototyping it is okay to still use the symbols fully qualified if you want, produces no errors for doing so, but it is much better to code idiomatic Nim.
In Python all symbols in the module are visible and mutable from modules that imports it, including symbols that should not be used or mutated outside the module.
In Nim everything is private by default and therefore is not visible from other modules.
To make symbols public and visible in other modules you have to use the asterisk *
:
let variable* = 42
const constant* = 0.0
proc someFunction*() = discard
template someTemplate*() = discard
type Platypus* = object
fluffyness*: int
The asterisk not only makes the symbol visible to the outside world,
the symbol will also appear in the generated documentation (nim doc
).
When you import the module, the symbol will be automatically added to the namespace,
but private (not exported) symbols without *
will not be visible.
The asterisk is like a visual cue for humans to immediately understand what symbols are a part of the "the public API" just by looking at the module's source code.
The asterisk *
sometimes is mentioned as "star".
For better understanding, it is recommended to read https://narimiran.github.io/2019/07/01/nim-import.html
Sometimes in Python you see these kinds of constructs which will be executed at runtime when the module containing these imports is itself imported:
try:
import module
except ImportError:
import othermodule as module
try:
from module import some_func
except ImportError:
# Fallback implementation
def somefunc():
return some_value
Nim resolves all imports at compile-time, so something like an ImportError
does not exist.
There's no need to handle import errors at runtime.
Arrays in Nim are fixed size, start at index 0
and can contain elements of the same type.
When passing an array to a function in Nim, the argument is an immutable reference. Nim will include run-time checks on the bounds of the arrays.
You can use an openarray
to accept an array of any size on the function arguments,
and you can use low(your_array)
and high(your_array)
to query the bounds of the array.
Nim string
is compatible with openArray[char]
to avoid unneeded copies for optimization,
char
is compatible with int
, therefore string
manipulation can be done with math in-place transparently,
so a function that takes openArray[char]
accepts "abcd"
and ['a', 'b', 'c', 'd']
.
See also:
- Whats the size of the different data types?.
import std/json
type Foo = object
type Bar = enum true, false
# (Weird spacing intended)
assert sizeOf( Foo ) == 1
assert sizeOf( Bar ) == 1
assert sizeOf( bool ) == 1
assert sizeOf( {true} ) == 1
assert sizeOf( [true] ) == 1
assert sizeOf( (true) ) == 1
assert sizeOf( int8 ) == 1
assert sizeOf( {'k': 'v'} ) == 2
assert sizeOf( int16 ) == 2
assert sizeOf( int32 ) == 4
assert sizeOf( float32 ) == 4
assert sizeOf( int ) == 8
assert sizeOf( float ) == 8
assert sizeOf( @[true] ) == 8
assert sizeOf( %*{} ) == 8
assert sizeOf( pointer ) == 8
This is only approximation for the empty primitives on 64Bit.
Objects in Nim behave quite differently from classes in Python.
Objects support inheritance and OOP. Classes are named types in Nim.
Functions are free floating functions, not bound to objects
(however, you can use them in a very similar way to Python),
you can call a function on objects with the Object.function()
.
Nim does not have an implicit self
nor this
.
Is best practice to put all your types near the top of the file, but not mandatory.
Imagine it like functions get "glued" to the objects at compile-time, then you can use it at run-time as if it was Python class and methods.
From Python to Nim as minimum as possible example:
class Kitten:
""" Documentation Here """
def purr(self):
print("Purr Purr")
Kitten().purr()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
type Kitten = object ## Documentation Here
proc purr(self: Kitten) = echo "Purr Purr"
Kitten().purr()
Minimal inheritance example:
type Animal = object of RootObj
type Kitten = object of Animal
assert Kitten is Animal
Python-like object orientation examples:
type Animal = ref object of RootObj ## Animal base object.
age: int
name: string ## Attributes of base object.
type Cat = ref object of Animal ## Cat inherited object.
playfulness: float ## Attributes of inherited object.
func increase_age(self: Cat) =
self.age.inc() # Cat object function, access and *modify* object.
var kitten = Cat(name: "Tom") # Cat object instance.
kitten.increase_age() # Cat object function used.
assert kitten.name == "Tom" # Assert on Cat object.
assert kitten.age == 1
Inheritance example:
type
LUCA = ref object of RootObj
Archea = ref object of LUCA
Prokaryota = ref object of Archea
Eukaryota = ref object of Prokaryota
Animalia = ref object of Eukaryota
Chordata = ref object of Animalia
Mammalia = ref object of Chordata
Primates = ref object of Mammalia
Haplorhini = ref object of Primates
Simiiformes = ref object of Haplorhini
Hominidae = ref object of Simiiformes
Homininae = ref object of Hominidae
Hominini = ref object of Homininae
Homo = ref object of Hominini
Homosapiens = ref object of Homo
assert Homosapiens() is LUCA
assert LUCA() isnot Homosapiens
assert sizeOf(Homosapiens) == sizeOf(LUCA)
let human = Homosapiens()
assert human is Homosapiens
See also:
After the Cat example probably you are wondering how to do def __init__(self, arg):
.
Python __init__()
is Nim newObject()
or initObject()
, lets make an __init__()
for the Cat:
type Cat = object # Cat object.
age: int
name: string # Attributes of Cat object.
func initCat(age = 2): Cat = # Cat.__init__(self, age=2)
result.age = age # self.age = age
result.name = "adopt_me" # self.name = "adopt_me"
var kitten = initCat() # Cat object instance.
assert kitten.name == "adopt_me" # Assert on Cat object.
assert kitten.age == 2
Naming is a convention and best practice, when you want init for Foo
just make newFoo()
or initFoo()
.
As you may notice initCat
is just a function that returns a Cat
.
-
initFoo()
forobject
. -
newFoo()
forref object
.
Read the documentation for Naming things following conventions and best practices.
Object constructor is also the way to set custom default values to the attributes of your objects:
type Cat = object
age: int # AutoInitialized to 0
name: string # AutoInitialized to ""
playfulness: float # AutoInitialized to 0.0
sleeping: bool # AutoInitialized to false
func initCat(): Cat =
result.age = 1 # Set default value to 1
result.name = "Bastet" # Set default value to "Bastet"
result.playfulness = 9.0 # Set default value to 9.0
result.sleeping = true # Set default value to true
A more complete structure for a basic program can be something like:
## Simple application to do Foo with the Bar.
type
Animal = ref object of RootObj
age: int
name: string
Cat = ref object of Animal
playfulness: float
func initCat(age = 2): Cat =
result.age = age
result.name = "adopt_me"
func increase_age(self: Cat) =
self.age.inc()
proc main() =
var kitten = Cat(name: "Tom")
kitten.increase_age()
assert kitten.name == "Tom"
assert kitten.age == 1
when isMainModule:
main()
runnableExamples:
echo "Optionally some documentation code examples here"
assert 42 == 42
Python objects that internally use code generation are very very slow,
scaling with size, the more you use it the slower it goes,
dataclass
, metaclass
, Decorators, etc can be more than 25 ~ 50x slower than a normal class
,
pathlib.Path
and its methods can be more than 25 ~ 50x slower than a normal str
,
and defeats any optimization, including a .pyc
file.
Cython does not have CTFE, so it does not help with this specifically.
- Nim code expansion is done at compile-time, making its code generation zero cost at run-time.
Example of ARC code expansion during compilation using --expandArc
,
this is how Nim does compile-time memory management (approximation):
See also:
- Nim passes data around "By Value" or "By Reference" ?.
Declaration | Value or Reference? | Implicit or Explicit? | Managed or Unmanaged? | Observations |
---|---|---|---|---|
symbol: int |
By value | Implicit | Managed | Frequent use |
symbol: var int |
By reference | Implicit | Managed | Frequent use |
symbol: ref int |
By reference | Explicit | Managed | Frequent use |
symbol: ptr int |
By reference | Explicit | Unmanaged | C/C++ FFI |
symbol: var ref int |
By reference | Implicit | Managed | Rare |
symbol: var ptr int |
By reference | Implicit | Unmanaged | Rare |
symbol: pointer |
By reference | Explicit | Unmanaged Pointer | C/C++ FFI |
- Iff a "by value" symbol is big, then it is passed "by reference" automatically.
- You can disable this optimization using
{.bycopy.}
pragma on the symbol. - The
{.byref.}
forces passing "by reference", the reverse of{.bycopy.}
. - Nim
seq
is passed around "by reference" by default. - Nim
string
is Copy-On-Write COW (ARC/ORC). - Pointer Arithmetic can be performed with pointer.
In Python, simple integer for loops use the range
generator function. For the 1- and 2- argument forms of this function, nim's ..
iterator works almost the same way:
for i in 0 .. 10:
echo i # Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
for i in 5 .. 10:
echo i # Prints 5, 6, 7, 8, 9, 10
Note that the ..
operator includes the end of the range, whereas Python's range(a, b)
does not include b
. If you prefer this behavior, use the ..<
iterator instead:
for i in 0 ..< 10:
echo i # Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Python range()
also has an optional third parameter,
which is the value to increment by each step, which can be positive or negative.
If you need this behavior, use the countup
or
countdown
iterators:
for i in countup(1, 10, 2):
echo i # Prints 1, 3, 5, 7, 9
for i in countdown(9, 0, 2):
echo i # Prints 9, 7, 5, 3, 1
Convert from range
to seq
:
import sequtils
const subrange = 0..9
const seqrange = toSeq(subrange)
assert seqrange is seq[int]
See also:
The syntax for slice ranges is different. Python a[x:y]
is Nim a[x ..< y]
.
let variable = [1, 2, 3, 4]
assert variable[0 .. 0] == @[1]
assert variable[0 .. 1] == @[1, 2]
assert variable[0 ..< 2] == @[1, 2]
assert variable[0 .. 3] == @[1, 2, 3, 4]
In Nim a reverse index or backwards index use ^
with the number, like ^1
,
backwards index has a specific type BackwardsIndex
,
and also it can be "prepared" at compile-time as a const
:
const lastOne = ^1 # Compile-time
assert lastOne is BackwardsIndex
assert [1, 2, 3, 4, 5][2 .. lastOne] == @[3, 4, 5]
assert [1, 2, 3, 4, 5][2 .. ^1] == @[3, 4, 5]
var another = ^3 # Run-time
assert [1, 2, 3, 4, 5][0 .. another] == @[1, 2, 3]
assert [1, 2, 3, 4, 5][^3 .. ^1] == @[3, 4, 5] # 2 Reverse index
Lets compare very simplified examples:
[0, 1, 2][9] # No Index 9
Crashes at run-time because there is no index 9:
$ python3 example.py
Traceback (most recent call last):
File "example.py", line 1, in <module>
[0, 1, 2][9]
IndexError: list index out of range
$
Lets see Nim:
discard [0, 1, 2][9] # No Index 9
Compile and run:
$ nim compile --run example.nim
example.nim(1, 19) Warning: can prove: 9 > 2 [IndexCheck]
example.nim(1, 18) Error: index 9 not in 0..2 [0, 1, 2][9]
$
Nim checks at compile-time that [0, 1, 2]
has no index 9
, because 9 > 2
, wont compile nor run.
This also works with Subrange, lets say you have a integer variable that must be positive:
let must_be_positive: Positive = -9
Compile and run:
$ nim compile --run example.nim
example.nim(1, 34) Warning: can prove: 1 > -9 [IndexCheck]
example.nim(1, 34) Error: conversion from int literal -9 to Positive is invalid.
$
Nim checks at compile-time that must_be_positive
is not Positive
because 1 > -9
, wont compile nor run.
Another example:
var variable0: 5..8 = 5 # int range type, value must be between '5' and '8'.
variable0 = 8
variable0 = 7
assert not compiles(variable0 = 4)
assert not compiles(variable0 = 9)
assert not compiles(variable0 = 0)
assert not compiles(variable0 = -1)
assert not compiles(variable0 = -9)
var variable1: 3.3..7.5 = 3.3 # float range type, value must be between '3.3' and '7.5'.
variable1 = 7.5
variable1 = 5.5
assert not compiles(variable1 = 3.2)
assert not compiles(variable1 = 7.6)
assert not compiles(variable1 = 0.0)
assert not compiles(variable1 = -1.0)
assert not compiles(variable1 = -9.0)
var variable2: 'b'..'f' = 'b' # char range type, value must be between 'b' and 'f'.
variable2 = 'f'
variable2 = 'c'
assert not compiles(variable2 = 'g')
assert not compiles(variable2 = 'a')
assert not compiles(variable2 = 'z')
assert not compiles(variable2 = '0')
assert not compiles(variable2 = '9')
var variable3: Positive = 1 # Positive type, value must be > 0.
variable3 = 1
variable3 = 999
assert not compiles(variable3 = 0)
assert not compiles(variable2 = -1)
assert not compiles(variable2 = -9)
var variable4: Natural = 0 # Natural type, value must be >= 0.
variable4 = 1
variable4 = 999
assert not compiles(variable4 = -1)
assert not compiles(variable4 = -9)
You can control this with --staticBoundChecks:on
or --staticBoundChecks:off
.
With --staticBoundChecks:off
it may raise error at run-time like Python.
- For better documentation see: https://nim-lang.github.io/Nim/drnim.html
Python do not have a Null Coalescing Operator (at the time of writing).
Python uses this kind of constructs:
other = bar if bar is not None else "default value" # "bar" may be Null?, or not ?.
Nim has wrapnils module with ?.
Null Coalescing Operator,
that simplifies code by reducing need for if..elif...else
branches around intermediate values that maybe be Null.
assert ?.foo.bar.baz == "" # "bar" may be Null?, or not ?.
Null is None
in Python. Null is nil
in Nim.
See https://nim-lang.github.io/Nim/wrapnils.html
For "With Context Manager" in Nim you have the following options:
See the Templates section for examples.
Lang | String | Multi-line string | Raw String | Multi-line Raw string | Formatted Literals | Quote |
---|---|---|---|---|---|---|
🐍 Python | "foo" |
"""foo""" |
r"foo" |
r"""foo""" |
f"""{1 + 2}""" |
" '
|
👑 Nim | "foo" |
"""foo""" |
r"foo" |
r"""foo""" |
fmt"""{1 + 2}""" |
" |
Ops | 🐍 Python | 👑 Nim |
---|---|---|
Lower | "ABCD".lower() |
"ABCD".toLowerAscii() |
Strip | " ab ".strip() |
" ab ".strip() |
Split | "a,b,c".split(",") |
"a,b,c".split(",") |
Concatenation | "a" + "b" |
"a" & "b" |
Find | "abcd".find("c") |
"abcd".find("c") |
Starts With | "abc".startswith("ab") |
"abc".startswith("ab") |
Ends With | "abc".endswith("ab") |
"abc".endswith("ab") |
Split Lines | "1\n2\n3".splitlines() |
"1\n2\n3".splitlines() |
Slicing | "abcd"[0:2] |
"abcd"[0 ..< 2] |
Slicing 1 char | "abcd"[2] |
"abcd"[2] |
Reverse Slicing | "abcd"[-1] |
"abcd"[^1] |
Normalize | unicodedata.normalize("NFC", "Foo") |
"Foo".normalize() |
Count Lines | len("1\n2\n3".splitlines()) |
"1\n2\n3".countLines() |
Repeat | "foo" * 9 |
"foo".repeat(9) |
Indent | textwrap.indent("foo", " " * 9) |
"a".indent(9) |
Unindent | textwrap.dedent("foo") |
"foo".unindent(9) |
Parse Bool |
bool(distutils.util.strtobool("fALse")) ❓ |
parseBool("fALse") |
Parse Int | int("42") |
parseInt("42") |
Parse Float | float("3.14") |
parseFloat("3.14") |
Formatted String Literals | f"foo {1 + 2} bar {variable}" |
fmt"foo {1 + 2} bar {variable}" |
Levenshtein distance | ❎ | editDistance("Kitten", "Bitten") |
- Nim string operations require
import std/strutils
. - A very detailed comparison.
Single memory allocation strings can be done with newStringOfCap(capacity = 42)
,
that returns 1 new empty string ""
but with allocated capacity
of 42
,
but if you pass beyond the capacity
it will not crash nor buffer overflow:
variable = ""
assert variable == "" # length is 0, capacity is 0, 1 allocations, 0 copies
variable += "a" # length is 1, capacity is 1, 2 allocations, 1 copies
variable += "b" # length is 2, capacity is 2, 3 allocations, 2 copies
variable += "c" # length is 3, capacity is 3, 4 allocations, 3 copies
variable += "d" # length is 4, capacity is 4, 5 allocations, 4 copies
assert variable == "abcd"
# TOTAL: 5 allocations, 4 copies
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var variable = newStringOfCap(2)
assert variable == "" # length is 0, capacity is 2, 1 allocations, 0 copies
variable.add "a" # length is 1, capacity is 2, 1 allocations, 0 copies
variable.add "b" # length is 2, capacity is 2, 1 allocations, 0 copies
variable.add "c" # length is 3, capacity is 3, 2 allocations, 0 copies
variable.add "d" # length is 4, capacity is 4, 3 allocations, 0 copies
assert variable == "abcd"
# TOTAL: 3 allocations, 0 copies
This difference may get bigger for strings inside for loops or while loops.
Nim string
is compatible with openArray[char]
to avoid unneeded copies for optimization,
char
is compatible with int
, therefore string
manipulation can be done with math in-place transparently,
so a function that takes openArray[char]
accepts "abcd"
and ['a', 'b', 'c', 'd']
.
Nim strformat
implements formatted string literals inspired by Python F-strings.
strformat
is implemented using metaprogramming and the code expansion is done at compile-time.
It also works for JavaScript target.
Similar to Python F-string you can
debug the key-value inside the string using an equal sign,
fmt"{key=}"
expands to fmt"key={value}"
:
let x = "hello"
assert fmt"{x=}" == "x=hello"
assert fmt"{x = }" == "x = hello"
Nim strformat
supports Backslash, while Python F-string does not:
>>> print( f"""{ "yep\nope" }""" ) # Run-time error.
Error: f-string expression part cannot include a backslash.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
echo fmt"""{ "yep\nope" }""" # Nim works.
yep
ope
You can choose a custom character pair to open and close the formatting inside the string just passing the char
as argument:
import std/strformat
let variable = 42
assert fmt("( variable ) { variable }", '(', ')') == "42 { variable }"
assert fmt("< variable > { variable }", '<', '>') == "42 { variable }"
Using characters like Backtick and Space ' '
works:
import std/strformat
let variable = 42
assert fmt(" variable`{variable}", ' ', '`') == "42{variable}"
Use | 🐍 Python | 👑 Nim |
---|---|---|
Operating System | os | os |
String operations | string | strutils |
Date & time | datetime | times |
Random | random | random |
Regular expressions (Backend) | re | re |
Regular expressions (Frontend) | ❎ | jsre |
HTTP | urllib | httpclient |
Logging | logging | logging |
Run external commands | subprocess | osproc |
Path manipulation | pathlib, os.path | os |
Mathematic | math, cmath | math |
MIME Types | mimetypes | mimetypes |
SQLite SQL | sqlite3 | db_sqlite |
Postgres SQL | ❎ | db_postgres |
Levenshtein Distance | ❎ | editdistance |
Serialization | pickle | json, jsonutils, marshal |
Base64 | base64 | base64 |
Open web browser URL | webbrowser | browsers |
Async | asyncio | asyncdispatch, asyncfile, asyncnet, asyncstreams |
Unittests | unittest | unittest |
Diff | difflib | diff |
Colors | colorsys | colors |
MD5 | hashlib.md5 | md5 |
SHA1 | hashlib.sha1 | sha1 |
HTTP Server | http.server | asynchttpserver |
Lexer | shlex | lexbase |
Multi-Threading | threading | threadpool |
URL & URI | urllib.parse | uri |
CSV | csv | parsecsv |
Parse command line arguments | argparse | parseopt |
SMTP | smtplib | smtp |
HTTP Cookies | http.cookies | cookies |
Statistics | statistics | stats |
Text wrapping | textwrap | wordwrap |
Windows Registry | winreg | registry |
POSIX | posix | posix, posix_utils |
SSL | ssl | openssl |
CGI | cgi | cgi |
Profiler | cprofile, profile | nimprof |
Monotonic time | time.monotonic | monotimes |
Run functions at exit | atexit | exitprocs |
Set file permissions | os, stat | os, filepermissions |
Recursive walk of filesystem | os.walk | os.walkDirRec, globs.walkDirRecFilter |
Templating engine | string.Template | Source Code Filters |
Deques | collections.deque | deques |
B-Tree based ordered Dictionary | ❎ | btreetables |
Critical Bit Tree Dict/Set | ❎ | critbits |
Pooled Memory Allocation | ❎ | pools |
Parse JSON | json | parsejson, json |
Parse INI | configparser | parsecfg |
Parse XML | xml | parsexml, xmltree |
Parse HTML | html.parser | htmlparser |
Parse SQL | ❎ | parsesql |
Colors on the Terminal | ❎ | terminal |
Linux Distro Detection | ❎ | distros |
HTML Generator | ❎ | htmlgen |
Arrow Functions | ❎ | sugar |
In-Place to Work-on-Copy | ❎ | sugar.dup |
Syntax Sugar | ❎ | sugar |
JavaScript & Frontend | ❎ | dom, asyncjs, jscore, jsffi, dom_extensions, jsre |
- Not complete just a quick overview. For more info see https://nim-lang.org/docs/lib.html
Tuples are fixed size, start at index 0
, can contain mixed types,
can be anonymous or named, named tuple has no extra overhead over anonymous tuple.
(1, 2, 3)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
(1, 2, 3)
- Keys named, no Tuple name. Python NamedTuple requires
import std/collections
.
collections.namedtuple("_", "key0 key1")("foo", 42)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
(key0: "foo", key1: 42)
- Keys named, Tuple named. Python NamedTuple requires
import std/collections
.
collections.namedtuple("NameHere", "key0 key1")("foo", 42)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
type NameHere = tuple[key0: string, key1: int]
var variable: NameHere = (key0: "foo", key1: 42)
Nim Tuples are a lot like Python NamedTuple in that the tuple members have names.
See manual for a more in depth look at tuples.
Nim sequences are not fixed size, can grow and shrink, start at index 0
and can contain elements of the same type.
["foo", "bar", "baz"]
⬆️ Python ⬆️ ⬇️ Nim ⬇️
@["foo", "bar", "baz"]
@
is a function that converts from array
to seq
.
variable = [item for item in (-9, 1, 42, 0, -1, 9)]
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let variable = collect(newSeq):
for item in @[-9, 1, 42, 0, -1, 9]: item
Comprehensions can be assigned to const
too, and it will run at compile-time.
The comprehension is implemented as a macro
that is expanded at compile-time,
you can see the expanded code using the --expandMacro
compiler option:
let variable =
var collectResult = newSeq(Natural(0))
for item in items(@[-9, 1, 42, 0, -1, 9]):
add(collectResult, item)
collectResult
The comprehensions can be nested, multi-line, multi-expression, zero overhead:
import std/sugar
let values = collect(newSeq):
for val in [1, 2]:
collect(newSeq):
for val2 in [3, 4]:
if (val, val2) != (1, 2):
(val, val2)
assert values == @[@[(1, 3), (1, 4)], @[(2, 3), (2, 4)]]
Single-line example:
print([i for i in range(0, 9)])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
echo(block: collect newSeq: (for i in 0..9: i))
Python Comprehensions convert the code to a Generator, but Nim Comprehensions do not convert the code to an Iterator:
import std/sugar
func example() =
discard collect(newSeq):
for item in @[-9, 1, 42, 0, -1, 9]:
if item == 0: return
item
example()
⬆️ Nim ⬆️ ⬇️ Python ⬇️
def example():
[item for item in [-9, 1, 42, 0, -1, 9] if item == 0: return]
example()
Python complains:
SyntaxError: invalid syntax.
Some code can not work in Python but it should work in Nim, like return
, etc
because code is silently converted to Generator by Python, but is expanded to normal code by Nim.
- Whats
collect()
?.
collect takes as argument whatever your returning type uses as the constructor.
variable = {key: value for key, value in enumerate((-9, 1, 42, 0, -1, 9))}
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let variable = collect(initTable(4)):
for key, value in @[-9, 1, 42, 0, -1, 9]: {key: value}
-
collect()
requiresimport std/sugar
.
variable = {item for item in (-9, 1, 42, 0, -1, 9)}
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let variable = collect(initHashSet):
for item in @[-9, 1, 42, 0, -1, 9]: {item}
-
collect()
requiresimport std/sugar
.
Lang | Set | Ordered Set | Bitset | Bit Fields | Imports |
---|---|---|---|---|---|
🐍 Python | set() |
❎ | ❎ | ❎ | |
👑 Nim | HashSet() |
OrderedSet() |
set |
Bit Fields | import std/sets |
- Python Set can be replaced with HashSet.
Python Sets are not like Nim set type,
the "default" Set is a Bitset,
that for every possible value of the contained type,
it stores 1 Bit indicating whether it is present in the set,
so you should use it if the type has a finite limited range of possible values,
if the possible values that will go in the set are finite and known at compile-time, you can create an Enum
for them.
The biggest integer you can fit on a set normally is 65535
equals to high(uint16)
.
You can fit bigger integers using an integer Subrange, if you dont need small integers,
an example really stressing set to fit 2_147_483_647
equals to high(int32)
on a set at compile-time:
const x = {range[2147483640..2147483647](2147483647)}
assert x is set # Equals to {2147483647}
The Nim set type is faster and memory-efficient. In fact, the set is implemented with a bit vector, whereas HashSet is implemented as a dictionary. For simple flag types and small mathematical sets, use set.
- If you are just learning, use HashSet.
Use Tables for Python dicts.
Lang | Dictionary | Ordered Dictionary | Counter | Imports |
---|---|---|---|---|
🐍 Python | dict() |
OrderedDict() |
Counter() |
import std/collections |
👑 Nim | Table() |
OrderedTable() |
CountTable() |
import std/tables |
dict(key="value", other="things")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
to_table({"key": "value", "other": "things"})
collections.OrderedDict([(8, "hp"), (4, "laser"), (9, "engine")])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
to_ordered_table({8: "hp", 4: "laser", 9: "engine"})
collections.Counter(["a", "b", "c", "a", "b", "b"])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
to_count_table("abcabb")
Examples:
import std/tables
var dictionary = to_table({"hi": 1, "there": 2})
assert dictionary["hi"] == 1
dictionary["hi"] = 42
assert dictionary["hi"] == 42
assert len(dictionary) == 2
assert dictionary.has_key("hi")
for key, value in dictionary:
echo key, value
Tables are just syntax sugar to an array of tuples:
assert {"key": "value", "k": "v"} == [("key", "value"), ("k", "v")]
assert {"key": true, "k": false} == @[("key", true), ("k", false)]
B-Tree based generic sorted Tables using the same API.
See also:
"result0" if conditional else "result1"
⬆️ Python ⬆️ ⬇️ Nim ⬇️
if conditional: "result0" else: "result1"
You probably notice that the Ternary Operator is just an if..else
inline.
Reading files line by line
with open("yourfile.txt", "r") as f:
for line in f:
print(line)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
for line in lines("yourfile.txt"):
echo line
-
lines()
Documentation https://nim-lang.org/docs/io.html#lines.i%2Cstring
Reading and writing files:
write_file("yourfile.txt", "this string simulates data")
assert read_file("yourfile.txt") == "this string simulates data"
Reading files at compile-time:
const constant = static_read("yourfile.txt") # Returns a string at compile-time
import std/os
os.chmod("file.txt", 0o777)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import fusion/filepermissions
chmod "file.txt", 0o777
Examples assume a file "file.txt"
exists.
Both use the Octal Unix file permissions.
Also a lower level API is available on os
module.
See https://nim-lang.github.io/fusion/src/fusion/filepermissions.html
import std/os
class withDir:
# Unsafe without a __del__()
def __init__(self, newPath):
self.newPath = os.path.expanduser(newPath)
def __enter__(self):
self.savedPath = os.getcwd()
os.chdir(self.newPath)
def __exit__(self, etype, value, traceback):
os.chdir(self.savedPath)
with withDir("subfolder"):
print("Inside subfolder")
print("Go back outside subfolder")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import fusion/scripting
withDir "subfolder":
echo "Inside subfolder"
echo "Go back outside subfolder"
Examples assume a folder "subfolder"
exists.
Python optionally has third party dependencies to do the same too, examples use standard library.
Some Python third party dependencies may convert the code inside withDir
to a Generator,
forcing you to change the code (like return
to yield
etc), examples use standard library.
See https://nim-lang.github.io/fusion/src/fusion/scripting.html
def isPositive(arg: int) -> bool:
return arg > 0
map(isPositive, [1, 2,-3, 5, -9])
filter(isPositive, [1, 2,-3, 5, -9])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
proc isPositive(arg: int): bool =
return arg > 0
echo map([1, 2,-3, 5, -9], isPositive)
echo filter([1, 2,-3, 5, -9], isPositive)
- Map and Filter operations requires
import std/sequtils
.
variable: typing.Callable[[int, int], int] = lambda var1, var2: var1 + var2
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var variable = proc (var1, var2: int): int = var1 + var2
Multi-line example:
var anon = func (x: int): bool =
if x > 0:
result = true
else:
result = false
assert anon(9)
Python anonymous functions can not use return
, but just works in Nim:
example = lambda: return 42
assert example() == 42
Complains SyntaxError: invalid syntax
.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let example = func: int = return 42
assert example() == 42
Python anonymous functions can not use yield
, but just works in Nim:
example = lambda: for i in range(0, 9): yield i
for _ in example(): pass
Complains SyntaxError: invalid syntax
.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let example = iterator: int =
for i in 0..9: yield i
for _ in example(): discard
Anonymous Functions in Nim is basically a function without a name.
- Templates and Macros can be used similar to Python Decorators.
def decorator(argument):
print("This is a Decorator")
return argument
@decorator
def function_with_decorator() -> int:
return 42
print(function_with_decorator())
⬆️ Python ⬆️ ⬇️ Nim ⬇️
template decorator(argument: untyped) =
echo "This mimics a Decorator"
argument
func function_with_decorator(): int {.decorator.} =
return 42
echo function_with_decorator()
- Why Nim won't use
@decorator
syntax?.
Nim uses {.
and .}
because it can have several decorators together.
Also Nim one works on variables and types:
func function_with_decorator(): int {.discardable, inline, compiletime.} =
return 42
let variable {.compiletime.} = 1000 / 2
type Colors {.pure.} = enum Red, Green, Blue
@
is a function that converts from array
to seq
.
Python uses multi-line strings with JSON inside, Nim uses literal JSON directly on the code.
import std/json
variable = """{
"key": "value",
"other": true
}"""
variable = json.loads(variable)
print(variable)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import json
var variable = %*{
"key": "value",
"other": true
}
echo variable
-
%*
converts everything inside the braces to JSON, JSON has a typeJsonNode
. -
%*
can have variables and literals inside the braces. - JSON can have comments inside the braces of
%*
, Nim kind of comments. - If the JSON is not valid JSON the code will not compile.
-
JsonNode
can be useful in Nim because is a type that can have mixed types and grow/shrink. - You can read JSON at compile-time, and store it on a constant as a string.
- To parse JSON from string you can use
parseJson("{}")
. - To parse JSON from a file
parseFile("file.json")
. - JSON documentation
if __name__ == "__main__":
main()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
when is_main_module:
main()
import std/unittest
def setUpModule():
"""Setup: Run once before all tests in this module."""
pass
def tearDownModule():
"""Teardown: Run once after all tests in this module."""
pass
class TestName(unittest.TestCase):
"""Test case description"""
def setUp(self):
"""Setup: Run once before each tests."""
pass
def tearDown(self):
"""Teardown: Run once after each test."""
pass
def test_example(self):
self.assertEqual(42, 42)
if __name__ == "__main__":
unittest.main()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import std/unittest
suite "Test Name":
echo "Setup: Run once before all tests in this suite."
setup:
echo "Setup: Run once before each test."
teardown:
echo "Teardown: Run once after each test."
test "example":
assert 42 == 42
echo "Teardown: Run once after all tests in this suite."
- Unittest documentation.
- Nimble the package manager can also run Unittests.
- NimScript can also run Unittests.
-
You can run documentation as Unittests with
runnableExamples
.
-
assert
can take ablock
, you can customize the message for better user experience:
let a = 42
let b = 666
doAssert a == b, block:
("\nCustom Error Message!:" &
"\n a equals to " & $a &
"\n b equals to " & $b)
Alternative to unittest
, prepared for big projects and has more features.
- https://nim-lang.github.io/Nim/testament.html (Recommended)
Docstrings in Nim are ReSTructuredText and MarkDown comments starting with ##
,
notice ReSTructuredText and MarkDown can be mixed together if you want.
Generate HTML, Latex (PDF) and JSON documentation from source code with nim doc file.nim
.
Nim can generate a dependency graph DOT .dot
file with nim genDepend file.nim
.
You can run documentation as Unittests with runnableExamples
.
"""Documentation of module"""
class Kitten(object):
"""Documentation of class"""
def purr(self):
"""Documentation of method"""
print("Purr Purr")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
## Documentation of Module *ReSTructuredText* and **MarkDown**
type Kitten = object ## Documentation of type
age: int ## Documentation of field
proc purr(self: Kitten) =
## Documentation of function
echo "Purr Purr"
For short lines, you can write the code on a single line, inline constructs without errors or warnings.
let a = try: 1 + 2 except: 42 finally: echo "Inline try"
let b = if true: 2 / 4 elif false: 4 * 2 else: 0
for i in 0 .. 9: echo i
proc foo() = echo "Function"
(proc () = echo "Anonymous function")()
template bar() = echo "Template"
macro baz() = echo "Macro"
var i = 0
while i < 9: i += 1
when is_main_module: echo 42
- Why Nim is CamelCase instead of snake_case?.
It really isn't, Nim is Style Agnostic.
let camelCase = 42 # Declaring as camelCase
assert camel_case == 42 # Using as snake_case
let snake_case = 1 # Declaring as snake_case
assert snakeCase == 1 # Using as camelCase
let `free style` = 9000
assert free_style == 9000
This feature allows Nim to seamlessly interoperate with a lot of programming languages with different styles.
For more homogeneous code default is chosen, and can even be enforced if you want,
to enforce the default code style you can add to the compile command --styleCheck:hint
,
Nim will style check your code before compilation, similar to pycodestyle
in Python,
if you want even more strict style you can use --styleCheck:error
.
Nim comes with a builtin code auto-formatter named Nimpretty.
A lot of programming languages have some kind of Case Insensitivity, such as: PowerShell, SQL, PHP, Lisp, Assembly, Batch, ABAP, Ada, Visual Basic, VB.NET, Fortran, Pascal, Forth, Cobol, Scheme, Red, Rebol.
If you are just starting from scratch, you can use Python-like names while learning, it will not produce an error for doing so until you learn more.
- Why Nim won't use
def
instead ofproc
?.
Nim is using proc
for normal functions from "Procedure" naming.
Use func
for when your routine cannot and should not access global or thread local variables.
Nim has side-effects tracking.
You can not use echo
inside func
, because echo
mutates stdout
, is a Side-Effect, use debugEcho
instead.
See also:
If you are just starting from scratch, you can use proc
for all the functions while learning,
it will not produce an error for doing so, until you learn more.
Nim has Async built-in since a long time ago, works as you may expect with async
, await
, Future
, etc.
asyncdispatch is a module to write concurrent code using the async
/await
syntax.
Future
is a Type (like a Future in Python, like a Promise on JavaScript).
{.async.}
is a Pragma that converts functions to Async (like async def
in Python).
Let's convert the official Python Asyncio Hello World to Nim:
async def main():
print("Hello ...")
await asyncio.sleep(1)
print("... World!")
asyncio.run(main())
⬆️ Python ⬆️ ⬇️ Nim ⬇️
proc main() {.async.} =
echo("Hello ...")
await sleep_async(1)
echo("... World!")
wait_for main()
Internally Async is implemented using metaprogramming (Macros, Templates, Pragmas, etc).
Description | asyncCheck | waitFor | await |
---|---|---|---|
Waits for the Future to complete | ❎ | ✔️ | ✔️ |
Ignores the Future | ✔️ | ❎ | ❎ |
Returns result inside Future | ❎ | ✔️ | ✔️ |
Only available inside async | ❎ | ❎ | ✔️ |
- Why Nim won't use
async def
?.
Async is just a macro
in Nim, no need to change the syntax of the language, is like a Decorator in Python.
Also in Nim the same function can be Async and Sync at the same time, with the same code, with the same name.
In Python when you have a library lets say "foo", then maybe you have foo
(Sync) and aiofoo
(Async),
usually completely different projects, repos, developers and APIs,
this is not needed in Nim, or rarely seen, thanks to said feature.
Because Async is just a macro
in Nim you can create your own Async your way too.
See also asyncfile, asyncnet, asyncstreams, asyncftpclient, asyncfutures.
You never have to actually manually edit C, the same way in Python you never manually edit the PYC files.
In Nim you code by writing Nim, the same way in Python you code writing Python.
A Template replaces its invocation with the template body at compile-time.
Imagine it like the compiler will copy and paste a chunk of code for you.
A template allows to have a function-like constructs without any overhead or to split huge functions into smaller parts.
Too many function and variable names may pollute the local namespace. Variables inside templates do not exist outside of their template. Templates do not exist in the namespace at run-time (if you do not export them), Templates may optimize certain values if they are known at compile-time.
Templates cannot do import
nor export
of libraries automatically implicitly,
templates do not "auto-import" symbols used inside itself,
if you use any imported library on the body of a template,
you must import that library when invoking that template.
Inside templates you can not use return
because a template is not a function.
Templates allow you to implement a very high-level beautiful API for everyday usage, while keeping the low-level optimized stuff out of your head and DRY.
Python with open("file.txt", mode = "r") as file:
implemented using 1 template:
GIF is not perfect, but a lazy simplified approximation!.
This is not the way to read files in Nim, just an exercise.
Template is not perfect, but a lazy approximation!, exercise for the reader to try to improve it ;P
template withOpen(name: string, mode: char, body: untyped) =
let flag = if mode == 'w': fmWrite else: fmRead # "flag" doen't exist outside of this template
let file {.inject.} = open(name, flag) # Create and inject `file` variable, `file` exists outside of this template because of {.inject.}
try:
body # `body` is the code passed as argument
finally:
file.close() # Code after the code passed as argument
withOpen("testing.nim", 'r'): # Mimic Python with `open("file", mode='r') as file`
echo "Hello Templates" # Code inside the template, this 2 lines are "body" argument on the template
echo file.read_all() # This line uses "file" variable
If you are just starting from scratch, do not worry, you can use functions for everything while learning.
Sharing variables between functions is similar to Python.
Global variable:
global_variable = ""
def function0():
global global_variable
global_variable = "cat"
def function1():
global global_variable
global_variable = "dog"
function0()
assert global_variable == "cat"
function1()
assert global_variable == "dog"
function0()
assert global_variable == "cat"
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var global_variable = ""
proc function0() =
global_variable = "cat"
proc function1() =
global_variable = "dog"
function0()
assert global_variable == "cat"
function1()
assert global_variable == "dog"
function0()
assert global_variable == "cat"
Object Attribute:
class IceCream:
def __init__(self):
self.object_attribute = None
def function_a(food):
food.object_attribute = 9
def function_b(food):
food.object_attribute = 5
food = IceCream()
function_a(food)
assert food.object_attribute == 9
function_b(food)
assert food.object_attribute == 5
function_a(food)
assert food.object_attribute == 9
⬆️ Python ⬆️ ⬇️ Nim ⬇️
type IceCream = object
object_attribute: int
proc functiona(food: var IceCream) =
food.object_attribute = 9
proc functionb(food: var IceCream) =
food.object_attribute = 5
var food = IceCream()
functiona(food)
assert food.object_attribute == 9
functionb(food)
assert food.object_attribute == 5
functiona(food)
assert food.object_attribute == 9
You can pass functions as argument of functions like in Python.
If you are migrating from an interpreted language, like Python or JavaScript, you may find strange mentions of "In-Place" and "Out-Place" somewhere in Nim, if you dont know what it means then Nim looks like having duplicated functions.
Python allocates a new string or object when something on it changes somehow,
lets say you have a huge string on a variable and change 1 character of it,
it duplicates the string on memory but the new copy has 1 character changed,
thats working on a new copy, thats named "Out-Place", most Python works like this,
on the example of the big string, Nim only changes the character you want to change,
not duplicating the string in memory, has some functions that work in-place,
some functions work on a new copy, documentation usually explains it,
using macro
Nim can turn from an in-place function to out-place one.
Nim stdlib modules designed for the JavaScript target usually work on a new copy, because how the JavaScript target is, no in-place API or no benefits on using it.
Some Nim stdlib modules that work on a new copy may or may not be changed to work in-place in the future.
Examples:
import std/sugar # sugar.dup
func inplace_function(s: var string) = # Does not use "string" but "var string"
s = "CHANGED"
# In-Place algo.
var bar = "in-place"
inplace_function(bar) ## Variable mutated in-place.
assert bar == "CHANGED"
# Out-Place algo.
assert "out-place".dup(inplace_function) == "CHANGED" ## Variable mutated on a new copy.
- https://github.com/yglukhov/nimpy/wiki#publish-to-pypi
- https://github.com/sstadick/ponim/blob/master/README.md#nim--python--poetry--
- https://github.com/sstadick/nython#nython
If you want the compilation to be completely silent (you may miss important warnings and hints),
you can add to the compile command --hints:off --verbosity:0
.
The Compiler help is long, to make it more user friendly only the most frequent commands are shown with --help
,
if you want to see the full help you can use --fullhelp
.
When your code is ready for production you should use a Release build,
you can add to the compile command -d:release
.
Feature | Release Build | Debug Build |
---|---|---|
Speed | Fast | Slow |
File Size | Small | Big |
Optimized | ✔️ | ❎ |
Tracebacks | ❎ | ✔️ |
Run-time checks | ✔️ | ✔️ |
Compile-time checks | ✔️ | ✔️ |
assert |
❎ | ✔️ |
doAssert |
✔️ | ✔️ |
Nim compiles to C, so it can run on Arduino and similar hardware.
Has several memory management strategies to fit your needs, including full manual memory management. Nim binaries are small when built for Release and it can fit the hardware tiny storage.
- https://github.com/zevv/nim-arduino
- https://github.com/elcritch/nesper#example-code
- https://gitlab.com/endes123321/nimcdl/tree/master#nimcdl-nim-circuit-design-language
- https://github.com/cfvescovo/Arduino-Nim#arduino-nim
- https://gitlab.com/nimbed/nimbed#nimbed
- https://gitlab.com/endes123321/led-controller-frontend#led-controller-frontend
- https://gitlab.com/jalexander8717/msp430f5510-nim
- https://github.com/mwbrown/nim_stm32f3
- https://github.com/gokr/ardunimo
- https://gitlab.com/NetaLabTek/Arduimesp
- https://ftp.heanet.ie/mirrors/fosdem-video/2020/AW1.125/nimoneverything.webm
SuperCollider is C++ so it can be re-utilized using Nim.
Theoretically, Nim SuperCollider plugins should be just as fast as C code. Nim metaprogramming allows to build LiveCoding friendly DSLs.
Some projects for Nim LiveCoding:
- https://github.com/vitreo12/omni#omni
- https://github.com/capocasa/scnim#scnim---writing-supercollider-ugens-using-nim
See this
The key to understanding Nim is that Nim was designed to be as fast as C, but to be much safer. Many of the design decisions are based on making it harder to shoot yourself in the foot. In Python, there are no pointers (everything is treated as a reference). While Nim does give you pointers, Nim gives you other, safer tools for your everyday needs, while pointers are mostly reserved for interfacing with C and doing low-level system programming.
Contrarily to Python, most Nim code can be executed at compile time to perform meta-programming. You can do a lot of the DSLs possible with Python decorators/metaprogramming with Nim macros and pragmas. (And some stuff that you can't!). Of course, this requires some different patterns and more type safety.
Intro
Getting Started
- Install
- Docs
- Curated Packages
- Editor Support
- Unofficial FAQ
- Nim for C programmers
- Nim for Python programmers
- Nim for TypeScript programmers
- Nim for D programmers
- Nim for Java programmers
- Nim for Haskell programmers
Developing
- Build
- Contribute
- Creating a release
- Compiler module reference
- Consts defined by the compiler
- Debugging the compiler
- GitHub Actions/Travis CI/Circle CI/Appveyor
- GitLab CI setup
- Standard library and the JavaScript backend
Misc