Nim
— Kaushal ModiCollection of Nim snippets with brief notes as I try them out from the official Nim tutorial, Nim by Example and many other places. This also includes my own musings and hard-learned lessons.
- Nim Help
- Echo
- Syntax
- Templates and Macros
- Representing one type in another
- Data Types
- String Stuff
- Unicode
- Math
- Variable “Types”
- Types
- TODO Pointers and References
- Regular Expressions
- Command line parameters
- Terminal
- Shell
- File paths
- File handling
- Exceptions
- Rules of Thumb (learnt from Nimisms)
- Nimisms
- Importing and Exporting Modules
- Modules/Packages/Libraries
- Nim By Example
- DONE Hello World
- DONE Variables
- DONE If, Else, While
- DONE Case Statements
- DONE For Loops & Iterators
- DONE Procs
- DONE First Class Functions
- DONE Blocks
- DONE Primitive Types
- DONE Type Aliases
- DONE Object Types
- DONE Enum Types
- DONE Distinct Types
- DONE Strings
- DONE Arrays
- DONE Seqs
- DONE Bitsets (Set type)
- DONE Files
- TODO JSON
- DONE Varargs
- TODO Object Oriented Programming
- TODO Macros
- Nim in Action
- Unit testing
- Documentation
- One Liners
- DONE Deployment
- Miscellaneous
- Object Variant
--gc:arc
– move, sink and stuff- Parallel
- TODO Converting a string to symbol
- Converting a symbol to string
- Debugging using GDB
- Checking if something compiles (
compiles
) - Checking if an identifier is declared
- Checking if a compilation switch is present (
defined
) - Getting the values of compile-time
-d:
switches - Changing the
nimcache/
directory - Compiling in 32-bit mode
- Splitting Org header args
- Detecting if debug build is run (no
-d:release
or-d:danger
)
- NEED TO UNDERSTAND Pragmas
- NEED TO UNDERSTAND Methods
- Questions/Doubts
- References
- Updated for Nim 1.7.1 (devel) again
- Updated for Nim 1.7.1 (devel)
- Updated for Nim 1.5.1 (devel)
- Updated for Nim 1.1.0
- Updated for Nim 0.20.0
- No need to import
typetraits
when just printing the type name, re-eval all code snippets using Nim devel as of today - Overhaul all Nim doc links to point to the Devel docs version
- Verified all the code snippets for Nim 0.19.0
Nim Help #
If using Nim 0.19.0 or newer, use nim --fullhelp
1 — don’t use
nim --help
.
I ended up with above conclusion as I needed the --out:
functionality
but couldn’t find it in the default help (--help
). I don’t see why
anyone would ever use the less informative --help
over
--fullhelp
.
If people are complaining about “too much information in help”, they
should use it the right way.. pipe the help to grep
or rg
.
> nim --fullhelp | rg out
-o:FILE, --out:FILE set the output filename
--stdout output to stdout
in the generated output
Echo #
Nim version #
assert NimVersion is string
echo NimVersion
assert NimMajor is int
echo NimMajor
assert NimMinor is int
echo NimMinor
assert NimPatch is int
echo NimPatch
1.7.1
1
7
1
See Check Type (is
) for is
.
Echo with newlines #
The echo
proc prints to the stdout with a newline added at the
end.
echo("Hello World")
Hello World
The parentheses are optional.
echo "Hello World"
Hello World
Echo with multiple arguments #
From Nim Docs – echo
:
proc echo(x: varargs[typed, `$`]) {..}
It means that echo
accepts multiple arguments, and all of them are
stringified using the $
proc.
echo
outputs each of those stringified arguments on the stdout one
after another and appends a newline at the very end. So the echo
statements in the below snippet have the same outputs:
# First concatenating the strings and passing as single arg to echo
echo $100 & "abc" & $2.5
# Passing multiple args to echo, and letting echo "concatenate" them
echo 100, "abc", 2.5
100abc2.5
100abc2.5
writeLine
#
Another way to print to the stdout is to use the writeLine
proc,
but by passing the stdout
argument to the File parameter.
stdout.writeLine "Hello World!"
stdout.writeLine "Hello World again!!"
Hello World!
Hello World again!!
Echo without newlines #
stdout.write("Hello")
stdout.write("World")
HelloWorld
Colored or Styled echo #
Use styledEcho
template from the terminal
module (which is
syntactic sugar for stdout.styledWriteLine
from that same module).
The syntax looks like this:
styledEcho <style1>, <style2>, <some string>, resetStyle, <some string>, <style3>, <some other string>, resetStyle
As shown above (<style1>, <style2>,
), you can stack multiple
“styles” together if they make sense.. like styleBright, fgBlue,
.
Available Styles #
Style #
type
Style* = enum ## different styles for text output
styleBright = 1, ## bright text
styleDim, ## dim text
styleItalic, ## italic (or reverse on terminals not supporting)
styleUnderscore, ## underscored text
styleBlink, ## blinking/bold text
styleBlinkRapid, ## rapid blinking/bold text (not widely supported)
styleReverse, ## reverse
styleHidden, ## hidden text
styleStrikethrough ## strikethrough
Foreground Color #
type
ForegroundColor = enum
fgBlack = 30, ## black
fgRed, ## red
fgGreen, ## green
fgYellow, ## yellow
fgBlue, ## blue
fgMagenta, ## magenta
fgCyan, ## cyan
fgWhite ## white
Background Color #
type
BackgroundColor = enum
bgBlack = 40, ## black
bgRed, ## red
bgGreen, ## green
bgYellow, ## yellow
bgBlue, ## blue
bgMagenta, ## magenta
bgCyan, ## cyan
bgWhite ## white
Other #
type
TerminalCmd = enum
resetStyle, ## reset attributes
fgColor, ## set foreground's true color
bgColor ## set background's true color
styledEcho
Example #
import std/[terminal]
styledEcho "unstyled text", styleBright, fgGreen, "[PASS]", resetStyle, fgGreen, " Yay!"
styledEcho "unstyled text", styleBright, fgRed, "[FAIL]", resetStyle, fgRed, " Nay :("
unstyled text^[[1m^[[32m[PASS]^[[0m^[[32m Yay!^[[0m
unstyled text^[[1m^[[31m[FAIL]^[[0m^[[31m Nay :(^[[0m
The binary control char is manually replaced with ASCII “^[” in the above results block and more below. That’s so that they are visible in the Markdown/HTML, and the Org file also remains “ascii-diffable”.
As you see above, styledEcho
automatically adds the ANSI code for
resetting the style (^[[0m
) at the end of the line, before adding
the trailing newline.
Echoing styled text without trailing newline #
Below snippet shows the use of stdout.styledWrite
to echo strings of
different styles (includes changing the foreground and background
colors) on the same line.
import std/[terminal]
stdout.styledWrite(fgRed, "red text ") # no newline here
stdout.styledWrite(fgGreen, "green text ") # no newline here
stdout.styledWrite(fgBlue, "blue text ") # no newline here
stdout.styledWrite(fgYellow, "yellow text ") # no newline here
stdout.styledWrite("ordinary text ") # no newline here
stdout.styledWrite(fgCyan, "cyan text ") # no newline here
echo ""
^[[31mred text ^[[0m^[[32mgreen text ^[[0m^[[34mblue text ^[[0m^[[33myellow text ^[[0mordinary text ^[[36mcyan text ^[[0m
Lower level styling procs/templates #
setForeGroundColor
- Sets foreground color for the stdout output
that follows. It is the same as calling
stdout.setForeGroundColor
. setBackGroundColor
- Sets background color for the stdout output
that follows. It is the same as calling
stdout.setBackGroundColor
. resetAttributes
- Resets the styling/coloring. It is the same as
calling
stdout.resetAttributes
.
import std/[terminal]
setForeGroundColor(fgGreen)
echo "green text"
echo "more green text"
setForeGroundColor(fgRed)
echo "red text"
resetAttributes()
echo "ordinary text"
^[[32mgreen text
more green text
^[[31mred text
^[[0mordinary text
Below code snippet is similar to Code Snippet 1, except that the
low-level procs setForeGroundColor
, resetAttributes
and
stdout.write
are used instead of stdout.styledWrite
.
import std/[terminal]
setForeGroundColor(fgRed)
stdout.write("red text ") # no newline here
setForeGroundColor(fgGreen)
stdout.write("green text ") # no newline here
setForeGroundColor(fgBlue)
stdout.write("blue text ") # no newline here
setForeGroundColor(fgYellow)
stdout.write("yellow text ") # no newline here
resetAttributes()
stdout.write("ordinary text ") # no newline here
setForeGroundColor(fgCyan)
stdout.write("cyan text ") # no newline here
resetAttributes()
echo ""
^[[31mred text ^[[32mgreen text ^[[34mblue text ^[[33myellow text ^[[0mordinary text ^[[36mcyan text ^[[0m
Standard Error #
To send message on stderr, use writeLine
proc to explicitly write
to stderr
File.
stderr.writeLine "Error!"
quit 1
Above will generate this error:
Error!
To send messages to stderr
that don’t end in newlines, use
stderr.write
instead.
stderr.write "Error1"
stderr.write "Error2"
quit 1
Above will generate this error:
Error1Error2
DONE When to use stdout.flushFile
? #
tl;dr Do buffer or file flushing frequently especially if the messages written to stdout/file are short.
From the reader Hasan in the comments below:
In my experience, regardless of the language, the input is buffered until the first EOL or a specific length is hit. So if you write something with a trailing EOL, you won’t need to flush, but if you have only a few characters, it is needed. I personally use it when I want to make sure that the output is sent, not buffered.
Also leorize from Nim IRC mentioned SO answer related to flushing buffers in C. The person Mike answering this says that using the below is very useful in C:
printf("Buffered, will be flushed");
fflush(stdout); // Prints to screen or whatever your standard out is
or
fprintf(fd, "Buffered, will be flushed");
fflush(fd); // Prints to a file
Here’s an excerpt from the rest of that answer talking about flushing stdout:
Why would you want to flush an output buffer?
Usually when I do it, it’s because the code is crashing and I’m trying to debug something. The standard buffer will not print everytime you call
printf()
it waits until it’s full then dumps a bunch at once. So if you’re trying to check if you’re making it to a function call before a crash, it’s helpful toprintf
something like “got here!”, and sometimes the buffer hasn’t been flushed before the crash happens and you can’t tell how far you’ve really gotten.Another time that it’s helpful, is in multi-process or multi-thread code. Again, the buffer doesn’t always flush on a call to a
printf()
, so if you want to know the true order of execution of multiple processes you should fflush the buffer after every print.I make a habit to do it, it saves me a lot of headache in debugging. The only downside I can think of to doing so is that
printf()
is an expensive operation (which is why it doesn’t by default flush the buffer).
Also, in the logging module docs, you see:
Warning: When logging on disk or console, only error and fatal messages are flushed out immediately. Use
flushFile()
where needed.
dump
: An “echo” that auto-prints the expression and its value #
dump
can be very
instrumental for quick debugging. It saves you from writing a bunch of
echo &"a = {a}"
type of code – Now you just write dump a
instead! 😍
import std/[sugar]
let
a = 100
b = 200
c = (a, b)
dump a
dump b
dump (a + b)
dump (a / b)
dump c
a = 100
b = 200
(a + b) = 300
(a / b) = 0.5
c = (100, 200)
Syntax #
Line continuation #
Nim does not have any line-continuation character – a character you can use to break a long line of code.
Instead line continuation is inferred if a line ends in an
operator, ,
, or any of these opening brackets ((
, [
, {
).
See the below code. It works, but it has a really long line of code.
proc isTriangle(s: openArray[int]): bool =
return (s.len == 3) and (s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1]) and (s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])
true
Below will not work as the continued lines are not ending with an operator – You will get Error: invalid indentation.
I really wish this worked!
proc isTriangle(s: openArray[int]): bool =
return (s.len == 3)
and (s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1])
and (s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])
Below, I am ending the continued lines with the and
operator. But
that will fail with the same error too.. because I am not indenting
the continued lines.
proc isTriangle(s: openArray[int]): bool =
return (s.len == 3) and
(s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1]) and
(s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])
Finally, below works because:
- The continued lines are ending with an operator (
and
in this example). - When continued, they resume after an indentation.
proc isTriangle(s: openArray[int]): bool =
return (s.len == 3) and
(s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1]) and
(s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])
true
Templates and Macros #
TODO Templates #
You can pass a block of statements as a last argument to a template
call following the special :
as shown below:
template foo(body: untyped) =
body
foo(): # special colon
echo "hello"
echo "world"
hello
world
DONE Return type of templates #
Below template has a return type of “void” and it still is returning a literal integer 1. So you get an error.
template foo() =
1
echo foo()
nim_src_co9l9c.nim(6, 9) Error: expression '1' is of type 'int literal(1)' and has to be discarded
To fix that, you need to assign the correct return type for the foo
template:
template foo(): int =
1
echo foo()
1
Or set it to untyped
so that the template replacement is done before
any semantic checking or type resolution.
template foo(): untyped =
1
echo foo()
1
Thanks to @mratsim from GitHub for this reply to my question
regarding untyped
return type for templates:
untyped
is useful to make sure the replacement is done before any semantic checking/type resolution.
If you are getting confused about whether or not to set a template’s
return type or what to set it to, just set it to untyped
.
If in doubt, set a template’s return type as untyped
.
NEED TO UNDERSTAND Macros #
- Comment by @Vindaar from GitHub on fixing the macro here.
- His further response on why
result[^1].add(arg)
was used instead ofresult.add(arg)
. - https://flenniken.net/blog/nim-macros/
- https://nim-lang.org/blog/2018/06/07/create-a-simple-macro.html
- https://hookrace.net/blog/introduction-to-metaprogramming-in-nim/
NEED TO UNDERSTAND Quote Do #
Example of quote do
#
Ref by @Vindaar from GitHub from here.
Once I understand Nim macros and quote do
, I need to revisit this
example to understand the quote do
magic. -
import std/[macros]
type
ShapeKind {.pure.} = enum
Triangle, Rectangle
Shape = object
case kind: ShapeKind
of Triangle:
aT: int
bT: int
of Rectangle:
aR: int
bR: int
proc calcArea(s: Shape) =
discard
proc createNext(c: NimNode): NimNode =
let typ = c[0]
let name = c[1]
let a = c[2][0][0]
let aVal = c[2][0][1]
let b = c[2][1][0]
let bVal = c[2][1][1]
case $typ
of "Triangle":
a.ident = toNimIdent($a & "T")
b.ident = toNimIdent($b & "T")
of "Rectangle":
a.ident = toNimIdent($a & "R")
b.ident = toNimIdent($b & "R")
result = quote do:
let `name` = Shape(kind: `typ`, `a`: `aVal`, `b`: `bVal`)
`name`.calcArea()
macro shape(input: untyped): untyped =
## should produce
## let aaa = Shape(kind: Triangle, a: 17, b: 23)
## aaa.calcArea()
## let bbb = Shape(kind: Rectangle, a: 5, b: 8)
## bbb.calcArea()
result = newStmtList()
echo input.treeRepr
for i in 0 ..< input.len:
let c = input[i]
case c.kind
of nnkCommand:
result.add createNext(c)
else:
echo "Needs to be a Command!"
echo result.repr
shape:
Triangle aaa:
a = 17
b = 23
Rectangle bbb:
a = 5
b = 8
TODO Quote Helper #
TODO Term Rewriting Macros #
Nim Manual – Term Rewriting Macros
Term rewriting macros (TRM) are macros or templates that have not only a name but also a pattern that is searched for after the semantic checking phase of the compiler. This means they provide an easy way to enhance the compilation pipeline with user defined optimizations.
import std/[strformat]
template optMul{`*`(a, 2)}(a: int): int =
debugEcho("-> Term rewriting activated!")
a + a
echo &"First arg = 3 ({$type(3)})"
echo "Calculating 3 * 2:" # Why isn't term rewriting activated here?
echo 3 * 2
let x = 3
echo ""
echo &"First arg = x ({$type(x)})"
echo "Calculating x * 2:"
echo x * 2
echo ""
echo &"First arg = 2 ({$type(2)})"
echo "Calculating 2 * x:"
echo 2 * x
First arg = 3 (int)
Calculating 3 * 2:
6
First arg = x (int)
Calculating x * 2:
-> Term rewriting activated!
6
First arg = 2 (int)
Calculating 2 * x:
6
Above, the compiler now rewrites x * 2
as x + x
. The code inside
the curlies ({`*`(a, 2)}
) is the pattern to match against.
The operators *
, **
, |
, ~
(see TRM pattern operators) have a
special meaning in patterns if they are written in infix notation. So
to match verbatim against *
the ordinary function call syntax needs
to be used.
TO BE FIXED TRM Side Effects #
Unfortunately optimizations are hard to get right. See the below tiny
example that doesn’t work as expected. It’s causing the side effects
in f
proc to repeat too!
template optMul{`*`(a, 2)}(a: int): int =
debugEcho("-> Term rewriting activated!")
a + a
proc f(): int =
echo "side effect!"
result = 55
echo f() * 2
-> Term rewriting activated!
side effect!
side effect!
110
We should not duplicate a
(the a
parameter in that optMul
TRM)
if it denotes an expression that has a side effect!
Fortunately Nim knows whether or not side effects are there. So with
the below, the TRM should not be getting activated if creating a
has
side effects.
template optMul{`*`(a, 2)}(a: int{noSideEffect}): int =
debugEcho("-> Term rewriting activated!")
a + a
proc f(): int =
echo "side effect!"
result = 55
echo f() * 2 # not optimized ;-)
side effect!
110
TO BE FIXED TRM Commutative #
You can make one overload matching with a constraint and one without, and the one with a constraint will have precedence, and so you can handle both cases differently.
So what about 2 * a
? We should tell the compiler *
is
commutative.
We cannot really do that however as the following code only swaps arguments blindly. Below code will cause the TRM to be executed exactly (odd) 36 times, and then for later multiplications, the TRM doesn’t get activated at all! (see Nim Issue #9288):
template mulIsCommutative{`*`(a, b)}(a, b: int): int =
debugEcho("-> Term rewriting activated!")
b * a
let x = 3
echo "Calculating x * 2:"
echo x * 2
echo ""
echo "Calculating x * 2 again:"
echo x * 2
echo ""
echo "Calculating x * 3:"
echo x * 3
Calculating x * 2:
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
6
Calculating x * 2 again:
6
Calculating x * 3:
9
TRM Parameter Constraints #
The Nim Manual says:
What optimizers really need to do is a canonicalization.
I think that “canonicalization” means, make the TRM arg types less generic, so that that infloop can be prevented.
So by limiting the first arg to be a literal int, the canonMul
TRM
will be activated only if the first arg is a literal int (like 3,
and not when it’s a variable holding int 3).
template canonMul{`*`(a, b)}(a: int{lit}, b: int): int =
debugEcho("-> Term rewriting activated!")
b * a
let
x = 3
y = 4
echo "The TRM won't be activated for 'x * y' because 'x' is not a literal int:"
echo x * y
echo ""
echo "The TRM will activated for '3 * y' because '3' *is* a literal int:"
echo 3 * y
The TRM won't be activated for 'x * y' because 'x' is not a literal int:
12
The TRM will activated for '3 * y' because '3' *is* a literal int:
-> Term rewriting activated!
12
The int{lit}
parameter pattern matches against an expression of type
int
, but only if it’s a literal.
lit
is just one of the many available TRM parameter constraints!
Representing one type in another #
From Type / To Type | bool | char | int | float | string |
---|---|---|---|---|---|
bool | - | N/A | int | float | $ or strformat.fmt or use plain logic to return string |
char | N/A | - | ord (or int ) | float | $ or strformat.fmt or custom logic using newString |
int | float.bool (or bool , int needs to be 0 or 1) | float.char (or chr or char , int needs to be in [0,255]) | - | float | $ or strformat.fmt or strutils.intToStr |
float | bool | char (truncation + rollover) | int (truncation + rollover) | - | $ or strformat.fmt |
string | strutils.parseBool | as a seq of char | strutils.parseInt | strutils.parseFloat | - |
From bool
#
bool
to char
#
not applicable
DONE bool
to int
#
import std/[strformat]
let
b_seq = @[true, false]
for b in b_seq:
var b_int = b.int
echo &"Value of {$type(b)} b is {b}"
echo &"Value of {$type(b_int)} b_int is {b_int}"
Value of bool b is true
Value of int b_int is 1
Value of bool b is false
Value of int b_int is 0
DONE bool
to float
#
import std/[strformat]
let b_seq = @[true, false]
for b in b_seq:
var b_float = b.float
echo &"Value of {$type(b)} b is {b}"
echo &"Value of {$type(b_float)} b_float is {b_float}"
Value of bool b is true
Value of float b_float is 1.0
Value of bool b is false
Value of float b_float is 0.0
DONE bool
to string
#
import std/[strformat]
let b_seq = @[true, false]
for b in b_seq:
var b_string1 = $b
var b_string2 = &"{b}"
var b_string3 = if b: "true" else: "false"
echo &"Value of {$type(b)} b is {b}"
echo &" Value of {$type(b_string1)} b_string1 is {b_string1}"
echo &" Value of {$type(b_string2)} b_string2 is {b_string2}"
echo &" Value of {$type(b_string3)} b_string3 is {b_string3}"
Value of bool b is true
Value of string b_string1 is true
Value of string b_string2 is true
Value of string b_string3 is true
Value of bool b is false
Value of string b_string1 is false
Value of string b_string2 is false
Value of string b_string3 is false
From char
#
char
to bool
#
not applicable
DONE char
to int
#
import std/[strformat]
let
c = 'A'
c_int1 = c.ord
c_int2 = c.int
echo &"Value of {$type(c)} c is {repr(c)}"
echo &" Value of {$type(c_int1)} c_int1 is {c_int1}"
echo &" Value of {$type(c_int2)} c_int2 is {c_int2}"
Value of char c is 'A'
Value of int c_int1 is 65
Value of int c_int2 is 65
As char
is an Ordinal type,
ord
is very suitable for
converting a char to int.
DONE char
to float
#
import std/[strformat]
let
c = 'A'
c_float = c.float
echo &"Value of {$type(c)} c is {repr(c)}"
echo &"Value of {$type(c_float)} c_float is {c_float}"
Value of char c is 'A'
Value of float c_float is 65.0
DONE char
to string
#
import std/[strformat, strutils]
let c_seq = @['A', 'b', '@']
for c in c_seq:
let
c_string1 = $c
c_string2 = &"{c}"
c_string3 = @[c].join("")
var c_string4 = newString(1)
c_string4[0] = c
echo &"Value of {$type(c)} c is {repr(c)}"
echo &" Value of {$type(c_string1)} c_string1 is {c_string1}"
echo &" Value of {$type(c_string2)} c_string2 is {c_string2}"
echo &" Value of {$type(c_string3)} c_string3 is {c_string3}"
echo &" Value of {$type(c_string4)} c_string4 is {c_string4}"
Value of char c is 'A'
Value of string c_string1 is A
Value of string c_string2 is A
Value of string c_string3 is A
Value of string c_string4 is A
Value of char c is 'b'
Value of string c_string1 is b
Value of string c_string2 is b
Value of string c_string3 is b
Value of string c_string4 is b
Value of char c is '@'
Value of string c_string1 is @
Value of string c_string2 is @
Value of string c_string3 is @
Value of string c_string4 is @
Join a sequence of characters into a string #
import std/[strformat, strutils]
let
c_seq = @['a', 'b', 'c', 'd']
str = c_seq.join("")
echo &"{str} is the stringified form of {$type(c_seq)} {c_seq}"
abcd is the stringified form of seq[char] @['a', 'b', 'c', 'd']
From int
#
DONE int
to bool
#
int
value of 0 isfalse
.- All other
int
values aretrue
.
import std/[strformat]
let
i_seq = @[-1, 0, 1, 2]
for i in i_seq:
echo &"Value of {$type(i)} i is {i}"
let
i_bool = i.bool
echo &" Value of {$type(i_bool)} i_bool is {i_bool}"
Value of int i is -1
Value of bool i_bool is true
Value of int i is 0
Value of bool i_bool is false
Value of int i is 1
Value of bool i_bool is true
Value of int i is 2
Value of bool i_bool is true
One way to get around this limitation on int values is to first
convert it to a float
and then to a bool
. With this, any non-zero
value of the int will return true
, and only the value of 0 will
return false
.
import std/[strformat]
let i_seq = @[-1000, 0, 1, 1000]
for i in i_seq:
echo &"Value of {$type(i)} i is {i}"
var i_bool: bool
try:
i_bool = i.float.bool
echo &" Value of {$type(i_bool)} i_bool is {i_bool}"
except:
echo &" [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1000
Value of bool i_bool is true
Value of int i is 0
Value of bool i_bool is false
Value of int i is 1
Value of bool i_bool is true
Value of int i is 1000
Value of bool i_bool is true
DONE int
to char
#
- The int value needs to be in the
[0, 255]
range. - RangeDefect exception is thrown if that value is outside that range.
import std/[strformat]
let i_seq: seq[int] = @[-1, 0, 62, 256]
for i in i_seq:
var i_char1, i_char2: char
echo &"Value of {$type(i)} i is {i}"
try:
i_char1 = chr(i) # or i.chr
echo &" Value of {$type(i_char1)} i_char1 is {repr(i_char1)}"
except:
echo &" [Error] {getCurrentException().name}: {getCurrentException().msg}"
try:
i_char2 = i.char # or char(i)
echo &" Value of {$type(i_char2)} i_char2 is {repr(i_char2)}"
except:
echo &" [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1
[Error] RangeDefect: value out of range: -1 notin 0 .. 255
[Error] RangeDefect: value out of range
Value of int i is 0
Value of char i_char1 is '\0'
Value of char i_char2 is '\0'
Value of int i is 62
Value of char i_char1 is '>'
Value of char i_char2 is '>'
Value of int i is 256
[Error] RangeDefect: value out of range: 256 notin 0 .. 255
[Error] RangeDefect: value out of range
chr
converts only an 8-bit unsigned integer (range[0 .. 255]
).
One way to get around this limitation on int values is to first
convert it to a float
and then to a char
. With this, any value of
int < 0 or >= 256 will returned a rolled-over char value.
import std/[strformat]
let i_seq = @[-2, -1, 0, 1, 254, 255, 256, 257]
for i in i_seq:
var i_char = i.float.char
echo &"Value of {$type(i)} i is {i}"
echo &" Value of {$type(i_char)} i_char is {repr(i_char)}"
Value of int i is -2
Value of char i_char is '\254'
Value of int i is -1
Value of char i_char is '\255'
Value of int i is 0
Value of char i_char is '\0'
Value of int i is 1
Value of char i_char is '\1'
Value of int i is 254
Value of char i_char is '\254'
Value of int i is 255
Value of char i_char is '\255'
Value of int i is 256
Value of char i_char is '\0'
Value of int i is 257
Value of char i_char is '\1'
DONE int
to float
#
import std/[strformat]
let i_seq: seq[int] = @[-1, 0, 1000]
for i in i_seq:
var i_float = i.float
echo &"Value of {$type(i)} i is {i}"
echo &"Value of {$type(i_float)} i_float is {i_float}"
Value of int i is -1
Value of float i_float is -1.0
Value of int i is 0
Value of float i_float is 0.0
Value of int i is 1000
Value of float i_float is 1000.0
DONE int
to string
#
import std/[strformat, strutils]
let i_seq: seq[int] = @[-1000, 0, 1000]
for i in i_seq:
echo &"Value of {$type(i)} i is {i}"
var
i_str1 = $i
i_str2 = &"{i}"
i_str3 = intToStr(i) # strutils
echo &" Value of {$type(i_str1)} i_str1 is {i_str1}"
echo &" Value of {$type(i_str2)} i_str2 is {i_str2}"
echo &" Value of {$type(i_str3)} i_str3 is {i_str3}"
Value of int i is -1000
Value of string i_str1 is -1000
Value of string i_str2 is -1000
Value of string i_str3 is -1000
Value of int i is 0
Value of string i_str1 is 0
Value of string i_str2 is 0
Value of string i_str3 is 0
Value of int i is 1000
Value of string i_str1 is 1000
Value of string i_str2 is 1000
Value of string i_str3 is 1000
Why is even intToStr
needed? #
As we see above $
provides a very concise way to print integers as
strings. On the other hand, intToStr
is verbose and an oddly named
function.. the only one named as fooToBar (we don’t have strToInt
,
but parseInt
.. I wish it were named as the former). Also, if one
wants to use intToStr
, they need to first import strutils
.
From intToStr
docs:
proc intToStr(x: int; minchars: Positive = 1): string {..}
Converts x to its decimal representation.
The resulting string will be minimally minchars characters long. This is achieved by adding leading zeros.
So intToStr
would be used to add leading zeros:
import std/[strutils]
let i_arr = [-100, -50, 0, 0, 123, 1000]
for i in i_arr:
echo intToStr(i, 10)
-0000000100
-0000000050
0000000000
0000000000
0000000123
0000001000
The minchars parameter in intToStr
does not count the negative
sign character.
But then, a similar result also can be achieved by the general
formatting macro &
from strformat
. See below to see what I mean by
“similar”.
import std/[strformat]
let i_arr = [-100, -50, 0, 0, 123, 1000]
for i in i_arr:
echo &"{i: 011}"
-0000000100
-0000000050
0000000000
0000000000
0000000123
0000001000
Breakdown of the " 011" format specifier —
Here is the general &
/ fmt
format specifier syntax:
[[fill]align][sign][#][0][minimumwidth][.precision][type]
- The initial space is the ‘[sign]’ part which indicates that space should be used for positive numbers. I used that so that the positive numbers align well (right align) with the negative numbers.
- The “0” is the ‘[0]’ part, which 0-pads the numbers on the left.
- “11” is the ‘[minimumwidth]’ part which counts the “-” sign
character too. The minimum width is set to 11 to match the 10
argument given to the
intToStr
in the above example.
I prefer the fmt
behavior better. But also, as floatToStr
does not
exist, and I would anyways need to resort to fmt
for formatting
negative floats, I might as well do the same for negative ints
too — Consistency.
So, still.. there isn’t a strong case to use intToStr
.
From float
#
DONE float
to bool
#
float
value of 0.0 isfalse
.- All other
float
values aretrue
.
import std/[strformat]
let
f_seq = @[-1.1, -0.5, 0.0, 0.5, 1.1]
for f in f_seq:
var f_bool = f.bool
echo &"Value of {$type(f)} f is {f:>5.2f} => Value of {$type(f_bool)} f_bool is {f_bool}"
Value of float64 f is -1.10 => Value of bool f_bool is true
Value of float64 f is -0.50 => Value of bool f_bool is true
Value of float64 f is 0.00 => Value of bool f_bool is false
Value of float64 f is 0.50 => Value of bool f_bool is true
Value of float64 f is 1.10 => Value of bool f_bool is true
DONE float
to char
(truncation + rollover) #
Floats whose truncated integer values are >= 256 or < 0 will roll-over
to a char value in the range '\0' .. '\255'
as shown in the below
snippet.
import std/[strformat]
let f_seq: seq[float] = @[-2.0, -1.5, -1.1, 0.0, 0.4, 1.0, 65.0, 65.3, 255.0, 256.0, 257.0]
for f in f_seq:
var f_char = f.char
echo &"Value of {$type(f)} f is {f}"
echo &"Value of {$type(f_char)} f_char is {repr(f_char)}"
Value of float f is -2.0
Value of char f_char is '\254'
Value of float f is -1.5
Value of char f_char is '\255'
Value of float f is -1.1
Value of char f_char is '\255'
Value of float f is 0.0
Value of char f_char is '\0'
Value of float f is 0.4
Value of char f_char is '\0'
Value of float f is 1.0
Value of char f_char is '\1'
Value of float f is 65.0
Value of char f_char is 'A'
Value of float f is 65.3
Value of char f_char is 'A'
Value of float f is 255.0
Value of char f_char is '\255'
Value of float f is 256.0
Value of char f_char is '\0'
Value of float f is 257.0
Value of char f_char is '\1'
DONE float
to int
(truncation + rollover) #
Note from the below example that rollover happens at extremes of the int values.
import std/[strformat, math]
let f_seq: seq[float] = @[low(int).float, (low(int)/2).float, -1.9, -1.5, -1.1, 0.0, 0.4, 0.5, 0.9, 1.0, (high(int)/2).float, high(int).float]
for f in f_seq:
var f_int = f.int
echo &"Value of {$type(f)} f is {f}"
echo &"Value of {$type(f_int)} f_int is {f_int}"
Value of float f is -9.223372036854776e+18
Value of int f_int is -9223372036854775808
Value of float f is -4.611686018427388e+18
Value of int f_int is -4611686018427387904
Value of float f is -1.9
Value of int f_int is -1
Value of float f is -1.5
Value of int f_int is -1
Value of float f is -1.1
Value of int f_int is -1
Value of float f is 0.0
Value of int f_int is 0
Value of float f is 0.4
Value of int f_int is 0
Value of float f is 0.5
Value of int f_int is 0
Value of float f is 0.9
Value of int f_int is 0
Value of float f is 1.0
Value of int f_int is 1
Value of float f is 4.611686018427388e+18
Value of int f_int is 4611686018427387904
Value of float f is 9.223372036854776e+18
Value of int f_int is -9223372036854775808
DONE float
to string
#
import std/[strformat]
let f_seq: seq[float] = @[-1.9, -1.5, -1.1, 0.0, 0.4, 0.5, 0.9, 1.0]
for f in f_seq:
var
f_string1 = $f
f_string2 = &"{f}"
echo &"Value of {$type(f)} f is {f}"
echo &" Value of {$type(f_string1)} f_string1 is {f_string1}"
echo &" Value of {$type(f_string2)} f_string2 is {f_string2}"
Value of float f is -1.9
Value of string f_string1 is -1.9
Value of string f_string2 is -1.9
Value of float f is -1.5
Value of string f_string1 is -1.5
Value of string f_string2 is -1.5
Value of float f is -1.1
Value of string f_string1 is -1.1
Value of string f_string2 is -1.1
Value of float f is 0.0
Value of string f_string1 is 0.0
Value of string f_string2 is 0.0
Value of float f is 0.4
Value of string f_string1 is 0.4
Value of string f_string2 is 0.4
Value of float f is 0.5
Value of string f_string1 is 0.5
Value of string f_string2 is 0.5
Value of float f is 0.9
Value of string f_string1 is 0.9
Value of string f_string2 is 0.9
Value of float f is 1.0
Value of string f_string1 is 1.0
Value of string f_string2 is 1.0
From string
#
DONE string
to bool
#
import std/[strformat, strutils]
let s_seq = @["true", "True", "tRuE", "false", "False", "FaLsE"]
for s in s_seq:
var s_bool = parseBool(s)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(s_bool)} s_bool is {s_bool}"
Value of string s is true
Value of bool s_bool is true
Value of string s is True
Value of bool s_bool is true
Value of string s is tRuE
Value of bool s_bool is true
Value of string s is false
Value of bool s_bool is false
Value of string s is False
Value of bool s_bool is false
Value of string s is FaLsE
Value of bool s_bool is false
DONE string
to char
#
A string can be represented as a sequence of chars as shown below.
import std/[strformat]
let s = "abcd\n"
var c_seq: seq[char]
for c in s:
c_seq.add(c)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(c_seq)} c_seq is {c_seq}"
for i, c in c_seq:
echo &" c_seq[{i}] = {repr(c)} (type: {$type(c)})"
Value of string s is abcd
Value of seq[char] c_seq is @['a', 'b', 'c', 'd', '\n']
c_seq[0] = 'a' (type: char)
c_seq[1] = 'b' (type: char)
c_seq[2] = 'c' (type: char)
c_seq[3] = 'd' (type: char)
c_seq[4] = '\10' (type: char)
DONE string
to int
#
import std/[strformat, strutils]
let
s = "1212"
s_int = parseInt(s)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(s_int)} s_int is {s_int}"
Value of string s is 1212
Value of int s_int is 1212
DONE string
to float
#
import std/[strformat, strutils]
let
s = "12.12"
s_float = parseFloat(s)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(s_float)} s_float is {s_float}"
Value of string s is 12.12
Value of float s_float is 12.12
Data Types #
DONE int
types #
- int8
- 8-bit signed integer type
- int16
- 16-bit signed integer type
- int32
- 32-bit signed integer type
- int64
- 64-bit signed integer type
- int
- This is the same size as the size of the pointer. So if the code is compiled in 32-bit mode, int will be the same size as int32, and if it’s compiled in 64-bit mode (on a 64-bit CPU), it will be the same size as int64.
Below code is compiled in 32-bit mode:
import std/[strformat]
echo &"Size of int32 / int / int64 = {sizeof(int32)} / *{sizeof(int)}* / {sizeof(int64)}"
Size of int32 / int / int64 = 4 / *4* / 8
And below is compiled in the usual 64-bit mode – Notice the change in the size of int type:
import std/[strformat]
echo &"Size of int32 / int / int64 = {sizeof(int32)} / *{sizeof(int)}* / {sizeof(int64)}"
Size of int32 / int / int64 = 4 / *8* / 8
Below is also compiled using the default 64-bit mode.
import std/[strformat]
var
aInt: int = 1
aInt8: int8 = 2
aInt16: int16 = 3
aInt32: int32 = 4
aInt64: int64 = 5
echo &"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt64 = aInt8 # works
aInt64 = aInt16 # works
aInt64 = aInt32 # works
aInt64 = aInt # works
echo &"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt = aInt8 # works
aInt = aInt16 # works
aInt = aInt32 # works
# aInt = aInt64 # Error: type mismatch: got <int64> but expected 'int'
echo &"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt32 = aInt8 # works
aInt32 = aInt16 # works
# aInt32 = aInt # Error: type mismatch: got <int> but expected 'int32'
# aInt32 = aInt64 # Error: type mismatch: got <int64> but expected 'int32'
echo &"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt16 = aInt8 # works
# aInt16 = aInt32 # Error: type mismatch: got <int32> but expected 'int16'
# aInt16 = aInt # Error: type mismatch: got <int> but expected 'int16'
# aInt16 = aInt64 # Error: type mismatch: got <int64> but expected 'int16'
echo &"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
# aInt8 = aInt16 # Error: type mismatch: got <int16> but expected 'int8'
# aInt8 = aInt32 # Error: type mismatch: got <int32> but expected 'int8'
# aInt8 = aInt # Error: type mismatch: got <int> but expected 'int8'
# aInt8 = aInt64 # Error: type mismatch: got <int64> but expected 'int8'
aInt64 = 5, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 1
aInt64 = 1, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 1
aInt64 = 1, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 4
aInt64 = 1, aInt32 = 3, aInt16 = 3, aInt8 = 2, aInt = 4
aInt64 = 1, aInt32 = 3, aInt16 = 2, aInt8 = 2, aInt = 4
For 64-bit compilation, while both int and int64 types are 64-bit integer types, they are of different “levels”. “Levels” is my home-grown term as I don’t know how to explain this better based on what I am seeing.
Here’s my attempt at explaining that:
int64 > int > int32 > int16 > int8
My home-grown theory follows:
- int64 is at the “highest level”, and int8 is at the “lowest level”.
- You can assign a “lower level” typed variable to a “higher level” typed variable, but not the other way around.
So that’s why aInt64 = aInt
works, but aInt = aInt64
fails.
Similarly aInt = aInt32
works, but aInt32 = aInt
fails — That
exact same would apply to the 32-bit compilation too.
Related:
- Section Primitive Types
- Section
rand
does not acceptint64
input
Char and repr
#
When printing char values for debug, it’s better to print using
repr
so that unprintable chars like ACK (\6
) and BELL (\7
) can
be easily distinguished. See below for example:
for c in @['\6', '\7', '\32', '\33', '\65', 'B']:
echo "char with ascii value of ", c.ord, " = ", repr(c)
char with ascii value of 6 = '\6'
char with ascii value of 7 = '\7'
char with ascii value of 32 = ' '
char with ascii value of 33 = '!'
char with ascii value of 65 = 'A'
char with ascii value of 66 = 'B'
Getting min and max for various data types #
Using low
and high
#
import std/[math, strformat]
let
int_low = low(int)
int_low_2power = log2(-int_low.float).int
int32_low = low(int32)
int32_low_2power = log2(-int32_low.float).int
int64_low = low(int64)
int64_low_2power = log2(-int64_low.float).int
echo &"int: {low(int)} (-2^{int_low_2power}) -> {high(int)} (2^{int_low_2power} - 1)"
echo &"int32: {low(int32)} (-2^{int32_low_2power}) -> {high(int32)} (2^{int32_low_2power} - 1)"
echo &"int64: {low(int64)} (-2^{int64_low_2power}) -> {high(int64)} (2^{int64_low_2power} - 1)"
echo &"float: {low(float)} -> {high(float)}"
echo &"float32: {low(float32)} -> {high(float32)}"
echo &"float64: {low(float64)} -> {high(float64)}"
int: -9223372036854775808 (-2^63) -> 9223372036854775807 (2^63 - 1)
int32: -2147483648 (-2^31) -> 2147483647 (2^31 - 1)
int64: -9223372036854775808 (-2^63) -> 9223372036854775807 (2^63 - 1)
float: -inf -> inf
float32: -inf -> inf
float64: -inf -> inf
Using sizeof
#
From sizeof
docs:
returns the size of x in bytes. Since this is a low-level proc, its usage is discouraged - using
new
for the most cases suffices that one never needs to know x’s size.As a special semantic rule, x may also be a type identifier (
sizeof(int)
is valid).
import std/[strformat]
echo &"Size of bool is {sizeof(bool)} byte"
echo &"Size of char is {sizeof(char)} byte"
echo &"Size of int is {sizeof(int)} bytes"
echo &"Size of int32 is {sizeof(int32)} bytes"
echo &"Size of int64 is {sizeof(int64)} bytes"
echo &"Size of float is {sizeof(float)} bytes"
echo &"Size of float32 is {sizeof(float32)} bytes"
echo &"Size of float64 is {sizeof(float64)} bytes"
Size of bool is 1 byte
Size of char is 1 byte
Size of int is 8 bytes
Size of int32 is 4 bytes
Size of int64 is 8 bytes
Size of float is 8 bytes
Size of float32 is 4 bytes
Size of float64 is 8 bytes
Boolean checking “unset” variables #
isNil
non-string primitive types #
isNil
does not work for non-string primitive types like bool
,
char
, int
and float
.
import std/[strformat]
var
b: bool
c: char
i: int
f: float
echo &"initial value of {$type(b)} b = {b}, isNil? {b.isNil}"
echo &"initial value of {$type(c)} c = {c}, isNil? {c.isNil}"
echo &"initial value of {$type(i)} i = {i}, isNil? {i.isNil}"
echo &"initial value of {$type(f)} f = {f}, isNil? {f.isNil}"
Trying to evaluate the above gives this error (and then the similar for those other types in that code snippet too):
lib/pure/strformat.nim(315, 39) Error: type mismatch: got <bool>
but expected one of:
proc isNil[T](x: seq[T]): bool
proc isNil(x: cstring): bool
proc isNil(x: string): bool
proc isNil[T](x: ptr T): bool
proc isNil[T](x: ref T): bool
proc isNil(x: pointer): bool
proc isNil[T: proc](x: T): bool
expression: isNil(b)
/bin/sh: /tmp/babel-2oW0wY/nim_src_FoNExx: Permission denied
isNil
on string #
isNil
does not work on strings too (after the removal of nil as a
valid string value in Nim 0.19.0+).
var s: string
echo s.isNil()
Trying to do so will give this error:
nim_src_23lLuf.nim(6, 8) Error: usage of 'isNil' is a user-defined error
isNil
on File object #
Thanks to mashingan from Nim Forum for this tip.
var fo: File
echo "Before open: Is fo 'unset'? ", fo.isNil
fo = open("./nim.org")
echo "After open: Is fo 'unset'? ", fo.isNil
fo.close
echo "After close: Is fo 'unset'? ", fo.isNil
fo = nil
echo "After explicitly setting to nil: Is fo 'unset'? ", fo.isNil
Before open: Is fo 'unset'? true
After open: Is fo 'unset'? false
After close: Is fo 'unset'? false
After explicitly setting to nil: Is fo 'unset'? true
String Stuff #
See Strings for an introduction to the string datatype in Nim.
String Functions #
%
and format
#
The strutils
module defines %
and format
for string formatting
(along with many other things!).
%
needs the second argument to be a string, or an array or sequence
of strings.
import std/[strutils]
echo "$1 $2" % ["a", "b"]
echo "$1 $2" % @["a", "b"]
# echo "$1 $2" % [100, 200] # This gives error. % cannot have int list as arg, has to be an array/seq of strings
# echo "$1 $2" % ['a', 'b'] # This gives error. % cannot have char list as arg, has to be an array/seq of strings
echo "$1 $2" % [$100, $200]
a b
a b
100 200
format
does not have the requirement for the input to be a string
(or an array/seq of strings) – It auto-stringifies the elements in the
second argument.
import std/[strutils]
echo "$1 $2".format(["a", "b"])
echo "$1 $2".format("a", "b")
echo "$1 $2".format('a', 'b') # format, unlike % does auto-stringification of the input
echo "$1 $2".format(100, 200) # format, unlike % does auto-stringification of the input
a b
a b
a b
100 200
strformat
and &
/ fmt
#
This is a standard Nim library.
Refer to this separate set of notes that explains fmt
(or &
) from
the strformat
library in great detail, including examples for all
options in the fmt
format specifier.
fmt
has some limitations compared to its equivalent macro &
(from
the same strformat
library). Read here for more. So I prefer to use
&
consistently everywhere instead of fmt
. Unless mentioned
otherwise, whatever is written about strformat.fmt
applies to the
strformat.&
too.
import std/[strformat]
let
a = 100
b = "abc"
echo &"a = {a}, b = {b}"
a = 100, b = abc
TO BE FIXED Use string variable containing message formatting for fmt
#
Below does not work:
import std/[strformat]
let
a = 100
msg = "a = {a}"
echo fmt(msg)
Gives error:
stack trace: (most recent call last)
lib/pure/strformat.nim(281) &
nim_src_ylMbyJ.nim(9, 10) Error: string formatting (fmt(), &) only works with string literals
I understand that this is a limitation of the nature of implementation
of fmt
because it needs its formatting string available at compile
time. But I wish this limitation wasn’t there.
String functions: Nim vs Python #
Refer to this separate set of notes where I compare the Nim String functions with those in Python 3.
String and character literals #
- String literals are enclosed in double quotes
- Character literals in single quotes.
- The single quote character literal is represented by
'\39'
.
- The single quote character literal is represented by
Special characters are escaped with \
: \n
means newline, \t
means tabulator, etc.
echo "Hello"
# echo 'Hello' # This will give error; single quotes are only for single character
echo 'a', 'b', '\39'
echo "c\n", '\t', 'b'
Hello
ab'
c
b
There are also raw string literals:
echo r"No need to escape \n and \t in here"
No need to escape \n and \t in here
In raw literals the backslash is not an escape character.
The third and last way to write string literals are long string literals like in
Python. They are written with three quotes: """ ... """
; they can span over multiple lines
and the \
is not an escape character either.
echo """Hey look at this.
I can keep on typing over multiple lines,
with 'single' or "double" quotes and even
back slashes: \n \t \r"""
Hey look at this.
I can keep on typing over multiple lines,
with 'single' or "double" quotes and even
back slashes: \n \t \r
String concatenation #
Use &
.
let s = "abc" & "def"
echo s
echo "ghi" & "jkl"
abcdef
ghijkl
String Comparison #
==
and !=
can be used to compare if two strings are equal.
assert "abc" == "abc"
assert "abc" != "acb"
The above is obvious. But see below for the “less than (or equal to)” and “greater than (or equal to)” comparisons:
If strings X and Y are of equal lengths, walking through the characters of both strings from the left-most side, for the first set of non-equal characters char-in-X and char-in-Y, char-in-X < char-in-Y => X < Y.
assert "a" < "b" assert "bb" > "ba" assert "ab" < "ba" assert "abc" < "abd" assert "bbc" > "abc"
The above rule applies even if strings X and Y are not of equal lengths.
So even if string X has more characters than string Y, X would be less than Y if for the first set of non-equal characters char-in-X and char-in-Y, char-in-X < char-in-Y.
assert "a" < "ab" assert "ab" > "a" assert "ab" < "b"
So string comparison is not a good way for version comparison:
# Let's say NimVersion is "0.20.99"
assert "0.20.99" >= "0.19.0"
assert "0.20.99" < "00.19.0" # Surprise!
assert "0.20.99" >= "0.09.0"
assert "0.20.99" < "0.9.0" # Surprise!
See Tuple comparison instead for Nim version comparison.
Unicode #
Rune #
Just as plain ASCII strings are composed of chars, strings with Unicode characters (>8-bits) are composed of Runes.
- The functions from
strutils
likeisAlphaNumeric
,isAlphaAscii
work on chars and strings. - The functions from
unicode
likeisAlpha
,isLower
,isUpper
, etc. work on Runes and strings.
import std/[unicode]
from std/strutils import isAlphaNumeric, isAlphaAscii
echo 'a'
echo 'a'.isAlphaAscii()
echo 'a'.isAlphaNumeric()
# echo 'a'.isAlpha() # this gives error: nim_src_YQy3FE.nim(6, 9) Error: type mismatch: got <char>
echo 'a'.Rune
echo 'a'.Rune.isLower()
echo 'a'.Rune.isUpper()
echo 'a'.Rune.isAlpha()
a
true
true
a
true
false
true
Walking through chars vs Runes #
Unicode symbols are allowed in strings, but are not treated in any
special way, so if you want count glyphs or uppercase Unicode symbols,
you must use the unicode
module.
import std/[strformat]
let str = "કૌશલ"
echo "Here is how it looks when str is parsed char-by-char:"
echo &" str = {str}, number of chars = {str.len}"
for i, c in str:
echo &" char {i} = {repr(c)} ({ord(c):#x})"
Here is how it looks when str is parsed char-by-char:
str = કૌશલ, number of chars = 12
char 0 = '\224' (0xe0)
char 1 = '\170' (0xaa)
char 2 = '\149' (0x95)
char 3 = '\224' (0xe0)
char 4 = '\171' (0xab)
char 5 = '\140' (0x8c)
char 6 = '\224' (0xe0)
char 7 = '\170' (0xaa)
char 8 = '\182' (0xb6)
char 9 = '\224' (0xe0)
char 10 = '\170' (0xaa)
char 11 = '\178' (0xb2)
import std/[unicode, strformat]
let str = "કૌશલ"
echo "Here is how it looks when str is parsed rune-by-rune:"
echo &" str = {str}, number of Runes = {str.runeLen}"
for i, r in str.toRunes:
echo &" Rune {i} = {r}"
Here is how it looks when str is parsed rune-by-rune:
str = કૌશલ, number of Runes = 4
Rune 0 = ક
Rune 1 = ૌ
Rune 2 = શ
Rune 3 = લ
Strings are generally considered to be encoded as UTF-8. So because of Unicode’s backwards compatibility, they can be treated exactly as ASCII, with all values above 127 ignored.
Unidecode #
unidecode is a Nim stdlib. It is used to transliterate unicode chars (non-English languages, etc.) to English ASCII characters – Ref.
When importing unidecode
, most likely you would want to compile your
Nim code with the -d:embedUnidecodeTable
switch.
import std/[unidecode/unidecode] # or just import unidecode
echo unidecode("મારુ નામ કૌશલ મોદી છે. હૂં અમદાવાદ થી છું.")
echo unidecode("Æneid")
echo unidecode("北京")
# Below is the same as above
echo unidecode("\xe5\x8c\x97\xe4\xba\xac")
maaru naam kaushl modii che. huuN amdaavaad thii chuN.
AEneid
Bei Jing
Bei Jing
In Emacs, with point over 北, if I do C-u C-x =
, I get:
buffer code: #xE5 #x8C #x97
So I translated that to \xe5\x8c\x97
, and then the same for 京.
If the embedUnidecodeTable
symbol is not defined during compilation,
an explicit call of the loadUnidecodeTable
proc is needed, with the
path to the unidecode.dat
file as its argument.
Below code is compiled without the -d:embedUnidecodeTable
switch.
- So the
findNimStdLib
proc is first defined to find the path to the installed Nim standard libraries, and based on that, the path to the inbuiltunidecode.dat
is derived. - Then
loadUnidecodeTable
proc is used to load that inbuilt table.
import std/[unidecode/unidecode] # or just import unidecode
import std/[os]
proc findNimStdLib*(): string =
## Tries to find a path to a valid "system.nim" file.
## Returns "" on failure.
try:
let nimexe = os.findExe("nim")
if nimexe.len == 0: return ""
result = nimexe.splitPath()[0] /../ "lib"
if not fileExists(result / "system.nim"):
when defined(unix):
result = nimexe.expandSymlink.splitPath()[0] /../ "lib"
if not fileExists(result / "system.nim"): return ""
except OSError, ValueError:
return ""
# Load the Unicode data file.
loadUnidecodeTable(findNimStdLib() / "pure/unidecode/unidecode.dat")
echo unidecode("મારુ નામ કૌશલ મોદી છે. હૂં અમદાવાદ થી છું.")
echo unidecode("Æneid")
echo unidecode("北京")
# Below is the same as above
echo unidecode("\xe5\x8c\x97\xe4\xba\xac")
maaru naam kaushl modii che. huuN amdaavaad thii chuN.
AEneid
Bei Jing
Bei Jing
See Nim StdLib path for the source of findNimStdLib
.
Math #
Random #
Call randomize
before rand
#
If you call just the rand
function without first calling
randomize
, you will get the same random value each time.
import std/[random]
echo rand(high(int))
4292486321577947087
In order to get truly random output, first call randomize
and then
rand
. If you evaluate the below snippet, you should get a different
output each time, unlike the above snippet.
import std/[random]
randomize()
echo rand(high(int))
8191563575897872325
Specifying randomization seed #
There are many use cases where you want to specify a particular randomization seed so that the randomized values are reproducible.
Specifying randomization seed using randomize
#
One way is to pass an integer seed to the randomize
function.
Re-evaluating below will result in the same output each time, like in Code Snippet 7, but with the difference that you can control the randomization seed.
import std/[random]
randomize(123) # arbitrary integer seed
echo rand(high(int))
3695677889614723175
FIXED randomize(0)
disables randomization #
From tests, it looks like randomize(0)
disables randomization
altogether and hard-codes the “randomized” var to 0!
Whether this is intended or not, this behavior is quite odd.
This was indeed odd, and is now fixed inimport std/[random]
echo "Setting randomization seed to a non-zero value; value of 1 is picked arbitrarily:"
randomize(1)
for _ in 0 .. 4:
echo rand(high(int))
echo "\nNow setting randomization seed to 0:"
randomize(0)
for _ in 0 .. 4:
echo rand(high(int))
After the fix, above code gives this error:
Error: unhandled exception: /home/kmodi/usr_local/apps/6/nim/devel/lib/pure/random.nim(516, 12) `seed != 0` [AssertionError]
But earlier, it gave this strange output:
Setting randomization seed to a non-zero value; value of 1 is picked arbitrarily:
68719493121
38280734540038433
1153018330890649890
1842080154353508865
97957842178638929
Now setting randomization seed to 0:
0
0
0
0
0
Getting the same Rand
state using initRand
#
The initRand
proc returns the same Rand
state for the same
provided integer seed. See
initRand
.
import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
echo rs.rand(high(int))
4611335679820174795
Random bool
#
import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
for _ in 0 .. 5:
echo rs.rand(1).bool
true
false
true
false
false
true
Random range #
Use rand(LOWER .. UPPER)
.
import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
for _ in 0 .. 5:
echo rs.rand(4 .. 5)
5
4
5
4
4
5
Works with negatives too:
import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
for _ in 0 .. 5:
echo rs.rand(-5 .. 5)
-2
-2
5
2
-5
-4
rand
range #
import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
let k = 3
for _ in 0 .. (2*k):
echo rs.rand(k)
3
0
1
0
0
1
3
As seen above, rand(k)
returns a random value in the range [0,k]
i.e. including the “k” value.
Limit of rand
#
rand
accepts int
(I think that is same is int64
– because this)
and float
(probably same as float32
? or float64
?.. couldn’t tell
from that).
From random.rand
docs:
Returns a random number in the range 0 .. max.
As rand
accepts int
, and it’s size is same as int64
, and is
signed (use uint
for unsigned), the max positive number that
rand
can generate is 2^63 - 1
.
DONE rand
does not accept int64
input #
Below gives an error.
import std/[random, strformat]
echo &"rand(9223372036854775807) = {rand(9223372036854775807)}"
nim_src_yz0syY.nim(5, 10) Error: type mismatch: got <int64>
but expected one of:
proc rand[T](r: var Rand; x: HSlice[T, T]): T
proc rand(max: float): float
proc rand(max: int): int
proc rand(r: var Rand; max: float): float
proc rand[T](r: var Rand; a: openArray[T]): T
proc rand[T](a: openArray[T]): T
proc rand[T](x: HSlice[T, T]): T
proc rand(r: var Rand; max: int): int
expression: rand(9223372036854775807'i64)
That’s because the rand
proc doesn’t accept/return int64
type
value.
But it does accept int
type. Even though both int
and int64
might be 64-bit integer types, they are not technically the same
type!
import std/[random, strformat]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
echo &"rand(9223372036854775807.int) = {rs.rand(9223372036854775807.int)}"
rand(9223372036854775807.int) = 4611335679820174795
Log #
log
#
Credit for the below log
function goes to @Paalon from GitHub [
ref ]:
import std/[math]
proc log [X, B: SomeFloat](x: X, base: B = E): auto =
## Computes the logarithm ``base`` of ``x``.
## If ``base`` is not specified, it defaults to the natural base ``E``.
when B is float64 or X is float64:
var r: float64
else:
var r: float32
if base == E:
r = ln(x)
else:
r = ln(x) / ln(base)
return r
import std/[random, strformat]
echo &"log10(111) = {log(111.float, 10.float)}"
echo &"log2(8) = {log(8.float, 2.float)}"
echo &"ln(8) = {log(8.float, E)}"
echo &"ln(8) = {log(8.float)}"
log10(111) = 2.045322978786657
log2(8) = 3.0
ln(8) = 2.079441541679836
ln(8) = 2.079441541679836
log2
#
import std/[math, strformat]
var
val = 8
power2 = log2(val.float) # need to cast val to a float
echo &"2^{power2} = {val}"
2^3.0 = 8
log10
#
import std/[math, strformat]
var
val = 100
power10 = log10(val.float) # need to cast val to a float
echo &"10^{power10} = {val}"
10^2.0 = 100
Exponentiation #
Using ^
from math
module (Non-negative integer exponentiation) #
Need to import math
module for the exponentiation operator ^
to
work.
You can do X ^ Y
where X
can be an integer or float, but Y
has
to be an integer >= 0.
import std/[math]
echo (2^0)
echo (2^3)
echo (2.2^3)
1
8
10.648
Using pow
from math
module (Float exponentiation) #
As mentioned above, X ^ Y
works only if Y
is an integer and
>= 0.
But what if Y
is negative, or not an integer? .. In that case, you
need to use the pow
function from the same math
module.
import std/[math]
echo "Evaluating equivalent of 2^-1:"
echo pow(2.0, -1.0)
Evaluating equivalent of 2^-1:
0.5
About pow
and floats #
Note that using pow
has a requirement – Both of its arguments need
to be floats. So the below snippet will fail:
import std/[math]
echo pow(2, 1)
nim_src_We7gQw.nim(5, 9) Error: ambiguous call; both math.pow(x: float64, y: float64)[declared in lib/pure/math.nim(265, 7)] and math.pow(x: float32, y: float32)[declared in lib/pure/math.nim(264, 7)] match for: (int literal(2), int literal(1))
But below will work:
import std/[math]
echo pow(2.0, 1.0)
echo pow(2.float, 1.float) # same as above
2.0
2.0
Quotient and Remainder #
If you do “7 / 2”, the quotient is 3, and the remainder is 1.
Quotient #
The quotient is calculated using the binary operator
div
which basically does
integer division.
- The result is negative if either of the dividend or the divisor is negative; else it is positive.
import std/[strformat]
echo "divisor = 3"
for i in -4 .. 4:
echo &" {i:2} div 3 = {i div 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
echo &" {i:2} div -3 = {i div -3:2}"
divisor = 3
-4 div 3 = -1
-3 div 3 = -1
-2 div 3 = 0
-1 div 3 = 0
0 div 3 = 0
1 div 3 = 0
2 div 3 = 0
3 div 3 = 1
4 div 3 = 1
divisor = -3
-4 div -3 = 1
-3 div -3 = 1
-2 div -3 = 0
-1 div -3 = 0
0 div -3 = 0
1 div -3 = 0
2 div -3 = 0
3 div -3 = -1
4 div -3 = -1
Remainder / Modulo operator #
The remainder is calculated using the binary operator, Modulo
operator, mod
.
- The result has the sign of the dividend i.e. the sign of the divisor is ignored.
import std/[strformat]
echo "divisor = 3"
for i in -4 .. 4:
echo &" {i:2} mod 3 = {i mod 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
echo &" {i:2} mod -3 = {i mod -3:2}"
divisor = 3
-4 mod 3 = -1
-3 mod 3 = 0
-2 mod 3 = -2
-1 mod 3 = -1
0 mod 3 = 0
1 mod 3 = 1
2 mod 3 = 2
3 mod 3 = 0
4 mod 3 = 1
divisor = -3
-4 mod -3 = -1
-3 mod -3 = 0
-2 mod -3 = -2
-1 mod -3 = -1
0 mod -3 = 0
1 mod -3 = 1
2 mod -3 = 2
3 mod -3 = 0
4 mod -3 = 1
Incrementing/Decrementing #
Incrementing #
inc
proc increments variables of
Ordinal types:
int
, char
, enum
.
inc a
is similar to doing a = a + 1
or a += 1
for int
or char
type variable a
.
var a = 100
echo a
inc a
echo a
inc a
echo a
100
101
102
var a = 'b'
echo a
inc a
echo a
inc a
echo a
b
c
d
type
Direction = enum
north, east, south, west
var a = north
echo a
inc a
echo a
inc a
echo a
north
east
south
Decrementing #
dec
proc decrements variables of Ordinal types: int
, char
,
enum
.
dec a
is similar to doing a = a - 1
or a -= 1
for int
or char
type variable a
.
var a = 100
echo a
dec a
echo a
dec a
echo a
100
99
98
var a = 'b'
echo a
dec a
echo a
dec a
echo a
b
a
`
type
Direction = enum
north, east, south, west
var a = west
echo a
dec a
echo a
dec a
echo a
west
south
east
Bitwise operations #
Bitwise And #
import std/[strformat]
let
a = 15
b = 1
var
c = (a + b) and 0x0f
echo &"4-bit addition (overflow): {a:#03x} + {b:#03x} = {c:#03x}"
echo &"4-bit addition (overflow): {a:#06b} + {b:#06b} = {c:#06b}"
4-bit addition (overflow): 0xf + 0x1 = 0x0
4-bit addition (overflow): 0b1111 + 0b0001 = 0b0000
Variable “Types” #
Keyword | Variable type | Must be initialized? | Can be set during runtime? | Should be evaluable at compile? |
---|---|---|---|---|
var | Mutable | No (type must be specified though if not initialized) | Yes, multiple times | No |
let | Immutable | Yes, though these can be initialized at run time. | Yes, just once | No |
const | Constant | Yes | No | Yes |
Mutable variables (var
) #
var
variables are mutable.
var
a = "foo"
b = 0
c: int # Works fine, initialized to 0
# Works fine, `a` is mutable
a.add("bar")
echo a
b += 1
echo b
c = 3
echo c
c = 7
echo c
foobar
1
3
7
“Python 3.8”-like Walrus operator or when-let
in Emacs-Lisp #
proc test0(): int =
return 0
proc test1(): int =
return 1
proc main() =
if (var x = test0(); x) == 1:
echo "Is 1"
else:
echo "Not 1"
if (var x = test1(); x) == 1:
echo "Is 1"
else:
echo "Not 1"
main()
Not 1
Is 1
Immutable variables (let
) #
let
variables are not mutable, but they can be set at run time.
let
d = "foo"
e = 5
# Compile-time error, must be initialized at creation
# f: float # Below line fixes the error
f: float = 2.2
# Compile-time error, `d` and `e` are immutable
# Below 2 lines are commented out to fix the compilation error
# d.add("bar")
# e += 1
echo d
echo e
echo f
foo
5
2.2
Constants (const
) #
const
“variables” are not mutable, and they have to be set at
compile time.
# Computed at compilation time
const
s = "abcdef"
sLen = s.len
echo s
echo sLen
abcdef
6
let
vs const
#
The difference between let
and const
is:
let
allows a variable value to be assigned at run time (though it cannot be re-assigned).let input = readLine(stdin) # works
const
means “enforce compile time evaluation and put it into a data section” i.e. you should be able to evaluate the value of aconst
variable during compile time. So setting aconst
variable usingreadLine
(that takes user input at run time) will result in an error.const input = readLine(stdin) # Error: constant expression expected
return
keyword and result
Variable #
Below example shows the use of return
keyword:
proc getAlphabet(): string =
var accm = ""
for letter in 'a' .. 'z': # see iterators
accm.add(letter)
return accm
echo getAlphabet()
abcdefghijklmnopqrstuvwxyz
The result
variable is a special variable that serves as an implicit
return variable, which exists because the control flow semantics of
the return
statement are rarely needed. The result
variable is
initialized in the standard way, as if it was declared with:
var result: ReturnType
For example, the getAlphabet()
function above could be rewritten
more concisely as:
proc getAlphabet(): string =
result = ""
for letter in 'a' .. 'z':
result.add(letter)
echo getAlphabet()
abcdefghijklmnopqrstuvwxyz
A possible gotcha is declaring a new variable called result
and expecting it
to have the same semantics.
proc unexpected(): int =
var result = 5 # Uh-oh, here 'result' got declared as a local variable because of 'var' keyword
result += 5
echo unexpected() # Prints 0, not 10
0
Variable Auto-initialization #
Global variables (like the ones in below example) cannot remain uninitialized in Nim.. they are always auto-initialized to some value.
var
fooBool: bool
fooInt: int
fooFloat: float
fooString: string
fooSeqString: seq[string]
echo "Value of 'uninitialized' variable 'fooBool': ", fooBool
echo "Value of 'uninitialized' variable 'fooInt': ", fooInt
echo "Value of 'uninitialized' variable 'fooFloat': ", fooFloat
echo "Value of 'uninitialized' variable 'fooString': ", fooString
echo "Value of 'uninitialized' variable 'fooSeqString': ", fooSeqString
Value of 'uninitialized' variable 'fooBool': false
Value of 'uninitialized' variable 'fooInt': 0
Value of 'uninitialized' variable 'fooFloat': 0.0
Value of 'uninitialized' variable 'fooString':
Value of 'uninitialized' variable 'fooSeqString': @[]
Uninitialized variables #
From section Variable Auto-initialization, we see that global variables always get auto-initialized.
Thanks to @data-man from GitHub, I learn that local variables can remain
uninitialized, with the use of the noinit
2 pragma.
Local variables would be the ones like the var
variables inside a
proc
.
As global variables always auto-initialize, the {.noinit.}
pragma
will not work in the below snippet. The ui_i
var still
auto-initializes with all elements set to 0.
Nim should have thrown an error if someone tried to use {.noinit.}
for global vars, but instead it silently does nothing!
var ui_i {.noinit.}: array[3, int]
echo "Value of uninitialized variable 'ui_i': ", ui_i
Value of uninitialized variable 'ui_i': [0, 0, 0]
Pollute Stack #
A little function PolluteStack
to help test if {.noinit.}
works.
import std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
proc polluteStack() =
var
c: char = (rs.rand(high(char).int)).char
i32: int32 = (rs.rand(high(int32).int)).int32
i64: int64 = (rs.rand(high(int64).int)).int64
f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rs.rand(high(int64).int)).float64
b: bool = rs.rand(1).bool
I came up with the above function after reading this priceless tip by Stefan Salevski:
You may try to call another proc which writes some values to local variables, that should pollute the stack. And then call your ff proc.
I am open to learn better ways to pollute the stack.
Break-down of the polluteStack
function #
We will use just this one line for analysis, as the rest are similar.
f32: float32 = (rs.rand(high(int64).int)).float32
f32
is of typefloat32
. So whatever we are assigning it to must be of that type. Here, that whatever is(rs.rand(high(int64).int))
, and we are casting it tofloat32
type using(WHATEVER).float32
.- So here whatever is
rand
which takes inhigh(int64).int
as input. - The input to
rand
is casted toint
using.int
because as of writing this, it did not accept inputs of typeint64
.high(int64)
returns the maximum value ofint64
type.
- Both
float32
andfloat64
have min value of-inf
and max value of+inf
. The 32-bit/64-bit only changes the number of bytes using internally for storing those float values. So it is OK to use the samerand(high(int64).int
to generate a random number for both of these float types as long as we are casting them using the right type for the right variable.
For simplicity, this function generates only positive random values for each type.
Test polluteStack
#
import std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
proc polluteStack() =
var
c: char = (rs.rand(high(char).int)).char
i32: int32 = (rs.rand(high(int32).int)).int32
i64: int64 = (rs.rand(high(int64).int)).int64
f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rs.rand(high(int64).int)).float64
b: bool = rs.rand(1).bool
echo repr(c)
echo i32
echo i64
echo f32
echo f64
echo b
polluteStack()
'\203'
713337728
7622359537808702249
6.732090894058521e+18
5.509501416271037e+18
true
{.noinit.}
does not work in block
too #
I thought that variables in a block
would be local for the purpose
of {.noinit.}
. But they are not i.e. the no-initialization does not
work here too!
import std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
proc polluteStack() =
var
c: char = (rs.rand(high(char).int)).char
i32: int32 = (rs.rand(high(int32).int)).int32
i64: int64 = (rs.rand(high(int64).int)).int64
f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rs.rand(high(int64).int)).float64
b: bool = rs.rand(1).bool
# local var in block
block foo:
polluteStack()
var ui_i {.noinit.}: array[3, int]
echo "In block: Value of uninitialized variable 'ui_i' in block: ", ui_i
In block: Value of uninitialized variable 'ui_i' in block: [0, 0, 0]
Works for var
variables in proc
#
Finally, the below code returns random values as that local var
,
local to a
proc
will actually remain uninitialized:
import std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
proc polluteStack() =
var
c: char = (rs.rand(high(char).int)).char
i32: int32 = (rs.rand(high(int32).int)).int32
i64: int64 = (rs.rand(high(int64).int)).int64
f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rs.rand(high(int64).int)).float64
b: bool = rs.rand(1).bool
proc a() =
var
ui_i1 {.noinit.}: int
ui_i2 {.noinit.}: array[3, int]
echo "Value of uninitialized variable 'ui_i1': ", ui_i1
echo "Value of uninitialized variable 'ui_i2': ", ui_i2
polluteStack()
a()
Value of uninitialized variable 'ui_i1': 203
Value of uninitialized variable 'ui_i2': [3063762216384782021, 713337728, -3783521239797778229]
- Earlier confusion
- Even for local vars, if the vars were not
arrays, I was unable to have them echo with random values with
the
noinit
pragma. Nim Issue #7852 has some interesting code snippets that behaved differently wrtnoinit
between me and data-man. - Fix
polluteStack
proc. Above issue got fixed once I started using
the
Examples of uninitialized arrays #
Here are some more examples of uninitialized local variables of array types:
import std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
proc polluteStack() =
var
c: char = (rs.rand(high(char).int)).char
i32: int32 = (rs.rand(high(int32).int)).int32
i64: int64 = (rs.rand(high(int64).int)).int64
f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rs.rand(high(int64).int)).float64
b: bool = rs.rand(1).bool
proc a() =
var
ai {.noinit.}: array[3, int]
ac {.noinit.}: array[3, char]
ab {.noinit.}: array[3, bool]
af {.noinit.}: array[3, float]
echo ai
echo ac
echo ab
echo af
polluteStack()
a()
[3063762216384782021, 713337728, -3783521239797778229]
['\x0F', '\xC8', 'i']
[false, false, false]
[2.146009457924067e+148, 1.11242189140595e+142, 3.683500169218559e+201]
- Earlier confusion
- If I commented out the int array declaration in
the above code (of course, its
echo
too), thechar
andbool
arrays would start auto-initializing, but not thefloat
. - Fix
polluteStack
proc. The confusion was created because the stack was clean.. needed something to pollute the stack first. Above issue got fixed once I started using
the
Scalar local vars and noinit
#
- Earlier confusion
- Looks like
noinit
works only for thefloat64
var. - Fix
polluteStack
proc. Above issue got fixed once I started using
the
import std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
proc polluteStack() =
var
c: char = (rs.rand(high(char).int)).char
i32: int32 = (rs.rand(high(int32).int)).int32
i64: int64 = (rs.rand(high(int64).int)).int64
f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rs.rand(high(int64).int)).float64
b: bool = rs.rand(1).bool
proc bar() =
var
i32: int32
i64: int64
f32: float32
f64: float64
i32ni {.noInit.}: int32
i64ni {.noInit.}: int64
f32ni {.noInit.}: float32
f64ni {.noInit.}: float64
echo "i32 (auto-init) = ", i32
echo "i64 (auto-init) = ", i64
echo "f32 (auto-init) = ", f32
echo "f64 (auto-init) = ", f64
echo "i32ni (no init) = ", i32ni
echo "i64ni (no init) = ", i64ni
echo "f32ni (no init) = ", f32ni
echo "f64ni (no init) = ", f64ni
polluteStack()
bar()
i32 (auto-init) = 0
i64 (auto-init) = 0
f32 (auto-init) = 0.0
f64 (auto-init) = 0.0
i32ni (no init) = 1774718877
i64ni (no init) = 7622359537808702249
f32ni (no init) = 1.068217052948333e+18
f64ni (no init) = 2.146009457924067e+148
Types #
Check Type (is
) #
Use the is
proc like an
operator. FOO is TYPE
returns true
if FOO
is of type TYPE
.
echo "'c' is char? ", 'c' is char
echo "'c' is string? ", 'c' is string
echo """"c" is string? """, "c" is string
'c' is char? true
'c' is string? false
"c" is string? true
Heterogeneous Slice (HSlice) #
The heterogeneous slice type is an object of 2 elements – which can be of different types (and thus.. heterogeneous).
HSlice[T; U] = object
a*: T ## the lower bound (inclusive)
b*: U ## the upper bound (inclusive)
An HSlice can be created using below:
import std/[strformat]
var s1: HSlice[int, char]
s1.a = 4
s1.b = 'f'
echo &"s1=`{s1}' is of type {$type(s1)}"
s1=`4 .. f' is of type HSlice[system.int, system.char]
But that verbose style of assignment doesn’t look like a lot of
fun. So the double-dot operator
..
is defined to quickly
create HSlices. Below snippet creates the exact same HSlice as in
the above snippet:
import std/[strformat]
let s1 = 4 .. 'f'
echo &"s1=`{s1}' is of type {$type(s1)}"
s1=`4 .. f' is of type HSlice[system.int, system.char]
The HSlices are very commonly used with both elements having the
same type (that too, usually int
or char
) — in slice iterators, or
specify a range of numbers to randomize inbetween, or to get a slice
of a string, or a sequence or array. Also see Slice.
import std/[strformat]
let hs1 = 1 .. 3
echo &"hs1=`{hs1}' is of type {$type(hs1)}"
for n in hs1:
echo n
let
hs2 = 'k' .. 'm'
echo &"hs2=`{hs2}' is of type {$type(hs2)}"
for c in hs2:
echo c
import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
echo "random num: ", rs.rand(8 .. 9)
let str = "abcdefghijk"
echo str[2 .. 4]
hs1=`1 .. 3' is of type HSlice[system.int, system.int]
1
2
3
hs2=`k .. m' is of type HSlice[system.char, system.char]
k
l
m
random num: 9
cde
But by its definition, an HSlice can have elements of different types too:
import std/[strformat]
let
hs3 = 1 .. 'z'
hs4 = 0.99 .. "blah"
echo &"hs3=`{hs3}' is of type {$type(hs3)}"
echo &"hs4=`{hs4}' is of type {$type(hs4)}"
hs3=`1 .. z' is of type HSlice[system.int, system.char]
hs4=`0.99 .. blah' is of type HSlice[system.float64, system.string]
As the string slice notation uses HSlice, and the HSlice elements can be of different types, below works too:
let str = "abcdefghijk"
type
MyEnum = enum
Sixth = 5
Seventh
echo str[3 .. Sixth]
def
Here are few examples of nested HSlices:
import std/[strformat]
let
hs5 = 777 .. 'd' .. "foo"
hs6 = (777 .. 'd') .. "foo" # same as above
hs7 = 5 .. 6 .. 'x' .. 'y'
hs8 = ((5 .. 6) .. 'x') .. 'y' # same as above
echo &"hs5=`{hs5}' is of type {$type(hs5)}"
echo &"hs6=`{hs6}' is of type {$type(hs6)}"
echo &"hs7=`{hs7}' is of type {$type(hs7)}"
echo &"hs8=`{hs8}' is of type {$type(hs8)}"
hs5=`777 .. d .. foo' is of type HSlice[HSlice[system.int, system.char], system.string]
hs6=`777 .. d .. foo' is of type HSlice[HSlice[system.int, system.char], system.string]
hs7=`5 .. 6 .. x .. y' is of type HSlice[HSlice[HSlice[system.int, system.int], system.char], system.char]
hs8=`5 .. 6 .. x .. y' is of type HSlice[HSlice[HSlice[system.int, system.int], system.char], system.char]
As it can be seen above, things can get crazy too quickly, so use parentheses to break up the HSlices as needed – Below is an HSlice of two different types of HSlices:
import std/[strformat]
let
hs9 = (5 .. 6) .. ('x' .. 'y')
echo &"hs9=`{hs9}' is of type {$type(hs9)}"
hs9=`5 .. 6 .. x .. y' is of type HSlice[HSlice[system.int, system.int], HSlice[system.char, system.char]]
HSlice
/ Slice
bounds #
- Use
.a
and.b
respectively to get the left and right bounds of an HSlice. - Note that
a
is not necessarily less thanb
, in fact, they can be equal too.
import std/[strformat]
let
s1 = -100 .. 100 # a < b
s2 = 10 .. 1 # a > b
s3 = 7 .. 7 # a == b
echo &"s1=`{s1}' is of type {$type(s1)}."
echo &" Left bound = {s1.a}, right bound = {s1.b}"
echo &" Low bound = {min(s1.a, s1.b)}, high bound = {max(s1.a, s1.b)}"
echo &"s2=`{s2}' is of type {$type(s2)}."
echo &" Left bound = {s2.a}, right bound = {s2.b}"
echo &" Low bound = {min(s2.a, s2.b)}, high bound = {max(s2.a, s2.b)}"
echo &"s3=`{s3}' is of type {$type(s3)}."
echo &" Left bound = {s3.a}, right bound = {s3.b}"
echo &" Low bound = {min(s3.a, s3.b)}, high bound = {max(s3.a, s3.b)}"
s1=`-100 .. 100' is of type HSlice[system.int, system.int].
Left bound = -100, right bound = 100
Low bound = -100, high bound = 100
s2=`10 .. 1' is of type HSlice[system.int, system.int].
Left bound = 10, right bound = 1
Low bound = 1, high bound = 10
s3=`7 .. 7' is of type HSlice[system.int, system.int].
Left bound = 7, right bound = 7
Low bound = 7, high bound = 7
(Homogeneous) Slice #
A Slice
is an alias for
HSlice[T, T]
i.e. it is a “heterogeneous” slice where both of the
elements are of the same type.
Below snippet shows that the HSlice[int, int]
and Slice[int]
are
the same (as we can assign value from one of those types to another):
import std/[strformat]
let
s1: Slice[int] = 4 .. 6
echo &"s1=`{s1}' is of type {$type(s1)}"
let
s2: HSlice[int, int] = s1
echo &"s2=`{s2}' is of type {$type(s2)}"
s1=`4 .. 6' is of type Slice[system.int]
s2=`4 .. 6' is of type HSlice[system.int, system.int]
Attempting to assign an HSlice with different-type elements to a Slice will give compilation error:
let
s1: Slice[int] = 4 .. 'f'
nim_src_LLOTy4.nim(5, 22) Error: type mismatch: got <HSlice[system.int, system.char]> but expected 'Slice[system.int]'
Arrays and Sequences #
Arrays #
- Arrays are a homogeneous type, meaning that each element in the array has the same type.
- Arrays always have a fixed length which has to be specified at compile time (except for open arrays).
The array type specification needs 2 things: size of the array, and
the type of elements contained in that array using array[SIZE, TYPE]
.
The SIZE can be specified as a range like N .. M
, or (M-N)+1
for
short. The below example shows how an array of 6 integers can be
declared and assigned using two different methods.
let
i_arr1: array[0 .. 5, int] = [1, 2, 3, 4, 5, 6]
i_arr2: array[6, int] = [7, 8, 9, 10, 11, 12]
echo i_arr1
echo i_arr2
[1, 2, 3, 4, 5, 6]
[7, 8, 9, 10, 11, 12]
The [ .. ]
portion to the right of the assign operator (=
) is
called the array constructor.
- Built-in procs
low()
andhigh()
return the lower and upper bounds of an array.- Arrays can also have a non-zero starting index; see the below example.
len()
returns the array length.
let
i_arr: array[6 .. 11, int] = [1, 2, 3, 4, 5, 6]
echo "min_index = ", low(i_arr), ", max_index = ", high(i_arr), ", i_arr = ", i_arr, ", length = ", len(i_arr)
min_index = 6, max_index = 11, i_arr = [1, 2, 3, 4, 5, 6], length = 6
“Typed” Arrays #
Now, if you need to create a lot of arrays of the type array[6 .. 11, int]
, updating their size and/or element type can become painful and
error-prone. So the convention is to first create a type
for arrays,
and then declare variables using that custom type.
Below example is a re-written version of the above example using “typed” arrays.
type
IntArray = array[6 .. 11, int]
let
i_arr: IntArray = [1, 2, 3, 4, 5, 6]
echo "min_index = ", low(i_arr), ", max_index = ", high(i_arr), ", i_arr = ", i_arr, ", length = ", len(i_arr)
min_index = 6, max_index = 11, i_arr = [1, 2, 3, 4, 5, 6], length = 6
Inferred Array Types #
Just as you can do let foo = "abc"
instead of let foo: string = "abc"
, you can let Nim infer the array types too.
import std/[strformat]
let
arr1 = [1.0, 2, 3, 4]
arr2 = ['a', 'b', 'c', 'd']
arr3 = ["a", "bc", "def", "ghij", "klmno"]
echo &"arr1 is of type {$type(arr1)} with value {arr1}"
echo &"arr2 is of type {$type(arr2)} with value {arr2}"
echo &"arr3 is of type {$type(arr3)} with value {arr3}"
arr1 is of type array[0..3, float64] with value [1.0, 2.0, 3.0, 4.0]
arr2 is of type array[0..3, char] with value ['a', 'b', 'c', 'd']
arr3 is of type array[0..4, string] with value ["a", "bc", "def", "ghij", "klmno"]
Array elements need to be of the same type #
In the above example, let arr1 = [1.0, 2, 3, 4]
worked because Nim
inferred 2, 3 and 4 to be floats 2.0, 3.0 and 4.0.
But if you try to mix-and-match types in array elements where they cannot get coerced to the same type, you get an error.
let
arr1 = [1.0, 2, 3, 4, 'a']
nim_src_PZd0Ji.nim(5, 25) Error: type mismatch: got <char> but expected 'float64 = float'
Two dimensional Arrays #
See Operators for an example of 2-D arrays.
Arrays with enum as length #
Array types can be declared as array[<some_enum_type>, <element_type>]
too. In this case, the array length will be set equal
to the number of values in that enum type, and so it has to be
assigned exactly that many elements.
type
Foo = enum
oneHundred
twoHundred
threeHundred
proc dict(f: Foo) =
const
fooInt: array[Foo, int] = [100, 200, 300]
fooString: array[Foo, string] = ["one hundred", "two hundred", "three hundred"]
echo fooInt[f], " ", fooString[f]
dict(twoHundred)
200 two hundred
As seen from the above example, such “enum-length arrays” can serve as “dictionaries” to provide a one-to-one translation from each enum value to something else.
If such “enum-length arrays” are not assigned the required number of elements (equal to the number of enum values), you get a compile error.
type
Foo = enum
oneHundred,
twoHundred,
threeHundred
proc dict(f: Foo) =
const
fooInt: array[Foo, int] = [100, 200]
echo fooInt[f]
nim_src_KdnP9k.nim(11, 31) Error: type mismatch: got <array[0..1, int]> but expected 'array[Foo, int]'
Here’s another example from Nim By Example – Arrays:
type
PartsOfSpeech = enum
speechPronoun, speechVerb, speechArticle,
speechAdjective, speechNoun, speechAdverb
let partOfSpeechExamples: array[PartsOfSpeech, string] = [
"he", "reads", "the", "green", "book", "slowly"
]
echo partOfSpeechExamples
["he", "reads", "the", "green", "book", "slowly"]
Unchecked Array #
Thanks to shashlick from Nim IRC for the below example [ ref ]:
var
test = alloc0(sizeof(int) * 4)
test2 = cast[ptr UncheckedArray[int]](test)
test2[1] = 5
test2[3] = 11
#echo test2[] # doesn't work .. probably a bug?
for i in 0 .. 3:
echo test2[i]
0
5
0
11
Sequences #
Sequences are similar to arrays but of dynamic length which may change during runtime (like strings).
- In the sequence constructor
@[1, 2, 3]
, the[]
portion is actually the array constructor, and@
is the array to sequence operator. - Just like other Nim assignments, the sequence type does not need to be specified if it is directly assigned a value — the sequence type is inferred by Nim in that case. See Specifying Types for more information.
import std/[strformat]
let
i_seq1:seq[int] = @[1, 2, 3, 4, 5, 6]
i_seq2 = @[7.0, 8, 9]
echo &"i_seq1 is of type {$type(i_seq1)} with value {i_seq1}"
echo &"i_seq2 is of type {$type(i_seq2)} with value {i_seq2}"
i_seq1 is of type seq[int] with value @[1, 2, 3, 4, 5, 6]
i_seq2 is of type seq[float64] with value @[7.0, 8.0, 9.0]
- One can append elements to a sequence with the
add()
proc or the&
operator, pop()
can be used to remove (and get) the last element of a sequence.- Built-in proc
high()
returns the upper bound of a sequence.low()
also works for a sequence, but it will always return 0.
len()
returns the sequence length.
import std/[strformat]
var i_seq = @[1, 2, 3]
echo &"i_seq is of type {$type(i_seq)} with value {i_seq}"
echo &"i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
echo "Adding 100 using `add' .."
i_seq.add(100)
echo &" i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
echo "Adding 200 using `&' .."
i_seq = i_seq & 200
echo &" i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
echo "Popping the last seq element using `pop' .."
let popped_elem = i_seq.pop
echo &" popped_elem = {popped_elem}"
echo &" i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
i_seq is of type seq[int] with value @[1, 2, 3]
i_seq = @[1, 2, 3], length = 3, last elem = 3
Adding 100 using `add' ..
i_seq = @[1, 2, 3, 100], length = 4, last elem = 100
Adding 200 using `&' ..
i_seq = @[1, 2, 3, 100, 200], length = 5, last elem = 200
Popping the last seq element using `pop' ..
popped_elem = 200
i_seq = @[1, 2, 3, 100], length = 4, last elem = 100
Reversing Sequences #
Use reversed
proc from the stdlib
algorithm. It takes in arrays, sequences
and strings, but always returns sequences.
import std/[algorithm]
echo reversed([1, 2, 3]) # array
echo reversed(@[1, 2, 3]) # sequence
echo reversed("abc") # string
@[3, 2, 1]
@[3, 2, 1]
@['c', 'b', 'a']
Note that the string “abc” converts to a reversed sequence of chars
composing that string. If you want a reversed string, just join
those chars again:
import std/[algorithm, strutils]
echo reversed("abc").join("")
cba
Open Arrays #
Open array types can be assigned to only parameters of procedures that
receive values of seq
or array
types.
- Open array types are like dynamic arrays, and need to be specified with only the type of the elements that the array is going to contain.
- This array type can be used only for
parameters
in procedures.- So if you do
var foo: openArray[int]
orlet foo: openArray[int] = [1,2,3]
(inside a proc or outside), you will get Error: invalid type: ‘openarray[int]’ for var or Error: invalid type: ‘openarray[int]’ for let.
- So if you do
- The
openArray
type cannot be nested: multidimensional openarrays are not supported. - Built-in proc
high()
returns the upper bound of an open array too.low()
also works for an open array, but it will always return 0.
len()
returns the open array length.
import std/[strformat]
proc testOpenArray(x: openArray[int]) =
echo &"x = {x} (type: {$type(x)}, length = {x.len}, max index = {x.high}"
let
some_seq = @[1, 2, 3]
some_arr = [1, 2, 3]
echo "Passing a seq .."
testOpenArray(some_seq)
echo "Passing an array .."
testOpenArray(some_arr)
Passing a seq ..
x = [1, 2, 3] (type: openArray[int], length = 3, max index = 2
Passing an array ..
x = [1, 2, 3] (type: openArray[int], length = 3, max index = 2
If using a Nim devel version older than b4626a220b, use
echo repr(x)
to print an open array x
— plain old echo x
or
fmt
like in the above example won’t work. See
$
is not implemented for open arrays for details.
Open arrays serve as a convenience type for procedure parameters so that:
- We don’t need to explicitly mention the types of the passed in
array (or sequence) values.
- For example, you can have a proc parameter of type
openArray[int]
, and have it accept all of these types –array[5, int]
,array[1000, int]
,seq[int]
.. just that the array|seq|openArray elements need to be of the exact same type..int
in this case.
- For example, you can have a proc parameter of type
- The proc can receive an array of any length as long as they contain
the same type elements as defined in the
openArray[TYPE]
.
Thanks to r3d9u11 from Nim Forum for the below example (ref).
type MyArr = array[3, int]
let
a3: MyArr = [10, 20, 30]
a4 = [10, 20, 30, 40]
# Compiler doesn't allow passing arrays with any other length
proc countMyArr(a: MyArr): int =
for i in 0 .. a.high:
result += a[i]
echo "countMyArray(a3) = ", countMyArr(a3)
# countMyArray(a4) # will fail
# Compiler doesn't allow passing arrays with any other length
proc countExplicitArrayType(a: array[3, int]): int =
for i in 0 .. a.high:
result += a[i]
echo "countExplicitArrayType(a3) = ", countExplicitArrayType(a3)
# countExplicitArrayType(a4) # will fail
# Compiler allows passing arrays with different lengths
proc countOpenArray(a: openArray[int]): int =
for i in 0 .. a.high:
result += a[i]
echo "countOpenArray(a3) = ", countOpenArray(a3)
echo "countOpenArray(a4) = ", countOpenArray(a4)
countMyArray(a3) = 60
countExplicitArrayType(a3) = 60
countOpenArray(a3) = 60
countOpenArray(a4) = 100
Above, you can see that countMyArr
and countExplicitArrayType
procs can accept only int arrays of length 3.
Attempting to pass the 4-element int array a4
to those will give
an error like this:
nim_src_gHCsrO.nim(20, 61) Error: type mismatch: got <array[0..3, int]>
but expected one of:
proc countExplicitArrayType(a: array[3, int]): int
expression: countExplicitArrayType(a4)
But countOpenArray
proc is not limited by the input array length —
It can accept an int array of any length. So countOpenArray(a4)
works just fine.
Open Array Limitations #
Modifying Open array variables inside procedures #
From my brief experimentation, you cannot modify the open arrays easily. For example, below does not work. I thought that as a passed in sequence got converted to an openarray type, the reverse should be possible too. But that’s not the case ..
proc foo(i_oa: openArray[int]) =
var i_seq: seq[int] = i_oa
i_seq.add(100)
echo "input open array: ", i_oa
echo "that open array modified to a sequence: ", i_seq
foo(@[10, 20, 30, 40])
Above gives the error:
nim_src_dqL4Jb.nim(5, 28) Error: type mismatch: got <openarray[int]> but expected 'seq[int]'
Here is a workaround:
proc foo(i_oa: openArray[int]) =
var i_seq: seq[int]
for i in i_oa: # copying the input openarray to seq element by element
i_seq.add(i)
i_seq.add(100)
echo "input open array: ", i_oa
echo "that open array modified to a sequence: ", i_seq
foo(@[10, 20, 30, 40])
input open array: [10, 20, 30, 40]
that open array modified to a sequence: @[10, 20, 30, 40, 100]
FIXED $
is not implemented for open arrays #
Thanks to @data-man from GitHub, this issue is now fixed in b4626a220b!
import std/[strformat]
proc foo(x: openArray[int]) =
echo &"{x}"
foo([1, 2, 3])
[1, 2, 3]
Older failure
As
$
isn’t implement for open arrays,fmt
wouldn’t work for these either – Nim Issue #7940.proc testOpenArray(x: openArray[int]) = echo x
Above gives this error:
nim_src_ghd2T5.nim(5, 8) Error: type mismatch: got <openarray[int]> but expected one of: proc `$`(x: string): string proc `$`(s: WideCString): string proc `$`[T: tuple | object](x: T): string proc `$`(x: uint64): string proc `$`(x: int64): string proc `$`[T, IDX](x: array[IDX, T]): string proc `$`[Enum: enum](x: Enum): string proc `$`(w: WideCString; estimate: int; replacement: int = 0x0000FFFD): string proc `$`[T](x: set[T]): string proc `$`[T](x: seq[T]): string proc `$`(x: int): string proc `$`(x: cstring): string proc `$`(x: bool): string proc `$`(x: float): string proc `$`(x: char): string expression: $(x)
Until that gets fixed, the workaround is to use
repr
to print open arrays.
Seq vs Array vs Open Array #
Below are few useful comments from this Nim forum thread. While crediting the comment authors, I have taken the liberty to copy-edit those and add my own emphasis.
- Stefan_Salewski from Nim Forum #
- Seqs are generally fine when
performance and code size is not critical. As
seq
data structure contains a pointer to the data elements, we have some indirection, which decreases performance.Arrays are used when number of elements is known at compile time. They offer the maximum performance. Nim Arrays are value (i.e. not reference) objects, living on the stack (see Heap and Stack) if used inside of procs. So a very direct element access is possible. Basically element access is only calculation of an offset, which is the product of element size and index value. If index is constant, then this offset is known at compile time, and so the access is as fast as that of a plain proc variable. An example is a chess program where we have the 64 fields. So we generally would use a array of 64 elements, not a seq. (Well, beginners may use a two dimensional array of 8 x 8, but then we have two indices, which again increases access time. A plain array is OK for this game, as we can increase index by one when we move a piece to the left, and increase index by 8 when we move forward.)
OpenArrays is just a proc parameter type that can accept arrays and seqs. Whenever a proc can be used with
array
orseq
parameters, theopenArray
parameter type should be used. The word “OpenArray” is indeed a bit strange; it was used in the Wirthian languages Modula and Oberon, we have no better term currently. - r3d9u11 from Nim Forum #
- (Provided a very useful example which I expanded upon in Open Arrays).
Python-like Dictionaries #
Tuples #
A tuple type defines various named fields and an order of the
fields. The constructor ()
can be used to construct tuples. The
order of the fields in the constructor must match the order in the
tuple’s definition.
The assignment operator for tuples copies each component.
Defining tuples #
Assigning the tuple type directly, anonymously. For anonymous tuples don’t use the
tuple
keyword and square brackets.let person: (string, int) = ("Peter", 30) echo person
("Peter", 30)
Assigning the tuple type directly, with named fields.
let person: tuple[name: string, age: int] = ("Peter", 30) echo person
(name: "Peter", age: 30)
First defining a custom tuple type, and then using that. This is useful if you don’t wan’t to copy/paste a verbose tuple type like
tuple[name: string, age: int]
at multiple places.. that approach is also very error-prone and painful if you ever need to refactor that tuple type throughout your code.type Person = tuple[name: string, age: int] let person1: Person = (name: "Peter", age: 30) # skipping the field names during assignment works too, but this could be less # readable. person2: Person = ("Mark", 40) echo person1 echo person2
(name: "Peter", age: 30) (name: "Mark", age: 40)
Alternative way of defining that same custom tuple type.
type Person = tuple name: string age: int let person: Person = (name: "Peter", age: 30) echo person
(name: "Peter", age: 30)
Accessing tuple fields #
- The notation
t.field
is used to access a named tuple’s field. - Another notation is
t[i]
to access thei
‘th field. Herei
must be a constant integer. This works for both anonymous and named tuples.
Anonymous tuples #
import std/[strformat]
let person: (string, int) = ("Peter", 30)
echo &"Tuple person of type {$type(person)} = {person}"
echo person[0]
echo person[1]
Tuple person of type (string, int) = ("Peter", 30)
Peter
30
Named tuples #
import std/[strformat]
type
Person = tuple
name: string
age: int
let person: Person = ("Peter", 30)
echo &"Tuple person of type {$type(person)} = {person}"
echo person[0]
echo person.name
echo person[1]
echo person.age
Tuple person of type Person = (name: "Peter", age: 30)
Peter
Peter
30
30
Check if a field exists #
Use compiles
. If the argument is a tuple variable with valid field
reference, it will return true
– Ref.
Named tuples #
type
Person = tuple
name: string
age: int
let p: Person = ("Peter", 30)
echo compiles(p.age)
echo compiles(p.foo) # invalid field
echo compiles(p[0])
echo compiles(p[1])
echo compiles(p[2]) # invalid field.. tuple has only 2 fields
true
false
true
true
false
Above, the tuple variable p
is passed to compiles
.
You can instead pass the tuple type Person
too. This is useful if
you want to check if a tuple field exists before creating a variable
of its type. But then, only named field check works – Ref.
type
Person = tuple
name: string
age: int
echo compiles(Person.age)
echo compiles(Person.foo) # invalid field
echo compiles(Person[0]) # incorrectly returns 'false', compared to compiles(p[0]) in previous snippet
true
false
false
Do not use a tuple type identifier with field index reference when checking if that field exists.
Here’s why compiles(Person.age)
works, but compiles(Person[0])
doesn’t – Person.age
returns its type, int
, but Person[0]
causes compilation failure:
nim_src_AVBMhm.nim(10, 12) Error: no generic parameters allowed for Person
type
Person = tuple
name: string
age: int
echo Person.age
# echo Person[0] # causes compilation error
int
The compilation error makes sense, because a tuple type can be generic too!
Anonymous tuples #
let q: (string, int, float) = ("abc", 42, 3.117)
echo compiles(q[0])
echo compiles(q[1])
echo compiles(q[2])
echo compiles(q[3]) # invalid field.. tuple has only 3 fields
true
true
true
false
For the reason explained in above section, it is possible to check if a tuple field exists, by using its index, but only when using the tuple variable identifier, and not its type.
Finding number of tuple fields #
As suggested by @Vindaar from GitHub,
arity
from
typetraits can be used to get the number of fields in a tuple – Ref.
import std/[strformat, typetraits]
type
Person = tuple
name: string
age: int
let
p: Person = ("Peter", 30)
q: (string, int, float) = ("abc", 42, 3.117)
echo &"Number of fields in p = {p.type.arity}, value = {p}"
echo &"Number of fields in q = {q.type.arity}, value = {q}"
Number of fields in p = 2, value = (name: "Peter", age: 30)
Number of fields in q = 3, value = ("abc", 42, 3.117)
Looping through fields #
import std/[strutils]
type
MyTupNamed = tuple
x: int
y: int
let
tupNamed = (100, 200).MyTupNamed
tupAnon = (9, "abc")
echo "Looping through tupNamed fields .."
for val in tupNamed.fields:
echo " ", val
for f, val in tupNamed.fieldPairs:
echo " tupNamed.$1 = $2" % [f, $val]
echo "Looping through tupAnon fields .."
for val in tupAnon.fields:
echo " ", val
for f, val in tupAnon.fieldPairs:
echo " tupAnon.$1 = $2" % [f, $val]
Looping through tupNamed fields ..
100
200
tupNamed.x = 100
tupNamed.y = 200
Looping through tupAnon fields ..
9
abc
tupAnon.Field0 = 9
tupAnon.Field1 = abc
Tuple unpacking #
Tuples can be unpacked during variable assignment (and only then!). This can be handy to assign directly the fields of the tuples to individually named variables.
An example of this is the splitFile
proc from the os
module which
returns the directory, name and extension of a path at the same
time.
For tuple unpacking to work you must use parentheses around the values you want to assign the unpacking to, otherwise you will be assigning the same value to all the individual variables!
For example:
import std/[os]
let
path = "usr/local/nimc.html"
(dir, name, ext) = splitFile(path)
baddir, badname, badext = splitFile(path)
echo "dir = ", dir
echo "name = ", name
echo "ext = ", ext
echo "baddir = ", baddir
echo "badname = ", badname
echo "badext = ", badext
dir = usr/local
name = nimc
ext = .html
baddir = (dir: "usr/local", name: "nimc", ext: ".html")
badname = (dir: "usr/local", name: "nimc", ext: ".html")
badext = (dir: "usr/local", name: "nimc", ext: ".html")
Tuple type equality #
Comparing one named tuple to another #
Different named tuple-types are equivalent if they:
- specify fields of the same type, and
- of the same name in the same order.
type Person = tuple[name: string, age: int]
var person: Person
person = (name: "Peter", age: 30)
echo "person = ", person
var teacher: tuple[name: string, age: int] = ("Mark", 42)
echo "teacher = ", teacher
# The following works because the field names and types are the same.
person = teacher
echo "person = ", person
person = (name: "Peter", age: 30)
teacher = (name: "Mark", age: 42)
person = (name: "Mark", age: 42)
You don’t need to declare a type for a tuple to use it. – like the
example of teacher
variable in the above example.
Named tuples created with different field names will be considered different objects despite having same number of fields of the same types.
var person: tuple[name: string, kids: int] = ("Peter", 1)
let teacher: tuple[name: string, age: int] = ("Mark", 42)
person = teacher
Above will give this error:
nim_src_KDTO15.nim(6, 8) Error: type mismatch: got <tuple[name: string, age: int]> but expected 'tuple[name: string, kids: int]'
Comparing named tuple and anonymous tuple types #
From the following example, it looks like anonymous tuples are considered to be of the same type as named tuples if:
- they have the same number of fields, and
- with the same type and order.
import std/[strformat]
type Person = tuple[name: string, age: int]
let personAnon1 = ("Peter", 30)
var
personNamed: Person
personAnon2: (string, int)
echo &"Tuple personAnon1 of type {$type(personAnon1)} = {personAnon1}"
echo "Assigning an anonymous tuple to a named tuple .."
personNamed = personAnon1
echo &"Tuple personNamed of type {$type(personNamed)} = {personNamed}"
echo "Assigning a named tuple to an anonymous tuple .."
personAnon2 = personNamed
echo &"Tuple personAnon2 of type {$type(personAnon2)} = {personAnon2}"
Tuple personAnon1 of type (string, int) = ("Peter", 30)
Assigning an anonymous tuple to a named tuple ..
Tuple personNamed of type Person = (name: "Peter", age: 30)
Assigning a named tuple to an anonymous tuple ..
Tuple personAnon2 of type (string, int) = ("Peter", 30)
When declaring new tuple variables by direct value assignment, don’t forget to specify the tuple type! If you don’t do so, that tuple will default to an anonymous tuple.
From the above example, note that personAnon1
didn’t get the field
names as it got assigned using a direct value without specifying the
tuple type.
Tuple comparison #
const
nimVersion = (major: NimMajor, minor: NimMinor, patch: NimPatch)
echo nimVersion
assert nimVersion > (0, 18, 0)
assert nimVersion >= (1, 1, 0)
assert nimVersion < (2, 0, 0)
(major: 1, minor: 7, patch: 1)
Generic Tuples #
A tuple can be a generic too!
Thanks to @Vindaar from GitHub and TheLemonMan on IRC to help me understand this – Ref.
type
Person[N: static[int]] = tuple
name: string
childrenAges: array[N, int]
let
p0: Person[0] = ("Peter", [])
p1: Person[1] = ("Peter", [1])
p2: Person[2] = ("Peter", [1, 2])
p3: Person[3] = ("Peter", [1, 2, 3])
echo p0
echo p1
echo p2
echo p3
(name: "Peter", childrenAges: [])
(name: "Peter", childrenAges: [1])
(name: "Peter", childrenAges: [1, 2])
(name: "Peter", childrenAges: [1, 2, 3])
TODO Objects #
TODO Looping through object keys #
fields
and fieldPairs
#
- Note the quirks with using
fields
/fieldPairs
withfmt
/&
TODO Pointers and References #
TODO Pointer (ptr
) #
Dereferencing #
import std/[strformat]
type Foo = ref object
x, y: float
var f: Foo
new f
# Accessing the reference
echo &"Type of f = {$type(f)}"
echo &"{$type(f[])} f[] = {f[]}"
f[].y = 12
echo &"{$type(f[])} f[] = {f[]}"
# When accessing values the dereference operator [] can be left out.
f.x = 13.5
echo &"{$type(f[])} f[] = {f[]}"
Type of f = Foo
Foo:ObjectType f[] = (x: 0.0, y: 0.0)
Foo:ObjectType f[] = (x: 0.0, y: 12.0)
Foo:ObjectType f[] = (x: 13.5, y: 12.0)
Heap and Stack #
- Credit
- Below explanation is taken entirely from this comment by /u/PMunch from Reddit.
First order of business heap vs. stack: Whenever you call a function it creates a stack frame. Stacks are simply said first in last out, so when a function call is done it pops it’s frame from the stack, and you return to the previous frame. When you declare a variable inside a scope in is typically allocated on the stack. This means that when the function returns that part of memory is not in use anymore and the value is gone (technically it’s still there, but we shouldn’t try to access it).
The heap on the other hand is quite a bit different. Items on the heap
are allocated and live there until you deallocate them. In C this is
done manually with calls like malloc
and free
. In Nim however, and
C# for that matter, we have the garbage collector (or GC), this nifty
thing reads through our memory and checks if anything still points to
things allocated on the heap and if nothing is pointing to that which
was previously allocated it gets free’d. This means that we won’t leak
memory as easily as you would in C as things that we’ve lost every
reference to get’s automatically cleaned.
Okay, now that we know where our stuff lives in memory, let’s look
at how Nim represents those things. Basic types are things like
integers, floats, bools, and characters. Their size is known and
static. Nim also calls strings a basic type, but those are not quite
like the rest since they can change in size. Since our stack is first
in last out it means that it makes sense to store everything in
order. But storing things in order isn’t possible if you want to
change the size of something (for example appending to a string). So
when we create numbers in our Nim code they will be stored on the
stack, this is why you never need to free your numbers and don’t have
to set them to nil
when you’re done with them.
So what are types? In Nim you can create aliases for types like type age = int
this is just a way to say that age
is an integer, and it
will be treated like one for all intents and purposes. If we want to
create collections of types to represent something particular we can
create objects
, don’t think of these quite like objects in an object
oriented language, think of them more like structs in C. Such objects
are simply a collection of values. So in Nim when we create an object
it will live with us on the stack. If we return an object it will be
copied to our caller, and if we insert it into a data structure it
will also be copied. While this might be practical in many cases (even
offering a speed benefit if done right) we often don’t want to copy
our large objects around. This is when allocating on the heap comes
into play.
If we define our type as a ref object
it means that the type is
actually a reference to an object. I’ll come back to the difference
between a reference and a pointer later, but for now just remember
that a reference is the memory location of an object on the heap. This
means that if we return a ref object
it means that we’re only
returning the memory address of that object, not copying the object
itself. This also means that if we insert it into a data structure
only the reference to the object is inserted. Whenever you see new SomeObject
it means that it allocates memory for that object on the
heap, and gives us a reference to this object. If we had simply done
var myObject: SomeObject
and SomeObject
was defined as a ref object
we would only have a reference on our stack, so trying to
access it would crash saying we had an “Illegal storage access”. This
is because Nim defaults our value to nil
, and no-one is allowed to
access memory area 0.
So imagine we had an object that contained the height, the weight, and the age of a person. That could be represented by three integers. If we wanted to return this object it would mean copying all those three values to our caller. If we defined it as a reference, we would only pass one integer, the position in memory (on the heap) where we stored the three others. Conveniently this also means that if one functions modifies a value in a referenced object, that change would be visible for all other functions using the same reference (since they all point to the same place in memory). This is practical for example if you want to make one list sorted by age, one by height, and the third by weight. Instead of copying our person three times, we could just use three references, one in each list.
So now that we know where and how are values are stored we can
look at the difference between a pointer and a reference. In pure Nim
code you would typically only use references, these are what every
call to new
creates and what goes on behind the scenes most of the
time when working with strings. A reference is also called a managed
pointer, it simply means that Nim manages this area of memory, and
that it will be automatically free’d for us by the garbage collector
when Nim sees that we’re not using it any longer. Pointers on the
other hand are unmanaged meaning that Nim doesn’t try to do anything
with the memory behind that pointer, most of the time you won’t even
know what is there. The reason there are pointers in Nim is mostly to
interface with C. In C every time you want objects on the heap you
need to manually malloc
and free
them. Many C libraries work by
passing around a pointer to a structure containing some state and all
good libraries have some way of dealing with this memory. Typically
you do it by calling some initialisation function when you begin and
then some cleanup function when you are done. In Nim we might want to
use a C library such as that, but since we might loose the reference
in our own code while the library still keeps a reference somewhere
which Nim doesn’t know about we can’t have a reference to it as Nim
would garbage collect it. So instead we have pointers.
Regular Expressions #
These apply as of today (
):- If you have PCRE library on your system, use the
re stdlib.
- PCRE syntax is used when using this library. See
man pcresyntax
from your terminal (online).
- PCRE syntax is used when using this library. See
- Else, use
nix-regex
(needs to be installed usingnimble install regex
).
Using re
#
import std/[re]
let
text = """Mary had a little lamb."""
rgx = re"(?i)mary"
echo text.replace(rgx, "Mama lamb")
Mama lamb had a little lamb.
Using regex
#
Below is the same example as in Code Snippet 32, including
the regular expression syntax, except that import regex
is done
instead of import re
.
import regex
let
text = """Mary had a little lamb."""
rgx = re"(?i)mary"
echo text.replace(rgx, "Mama lamb")
Mama lamb had a little lamb.
Command line parameters #
paramCount
returns the number of command line parameters given to the application.commandLineParams
returns the command line parameters.- Do
import os
before using any of the above functions.
# Arguments passed: foo bar "zoo car"
import std/[os, strformat]
let
numParams = paramCount()
params = commandLineParams()
echo "Number of command line params: ", numParams
echo "Command line params: ", params
for n in 0 ..< numParams:
echo &"Param {n+1} = {params[n]}"
Number of command line params: 3
Command line params: @["foo", "bar", "zoo car"]
Param 1 = foo
Param 2 = bar
Param 3 = zoo car
Use the cligen library for serious longopt and shortopt command line parameter parsing.
Terminal #
Hello World animation #
- Credit
- Below code is taken from this Reddit comment.
Click here to see the animation on asciinema.org.
import std/[os, random, strutils, terminal]
randomize() # Initialize the RNG with a new seed each run
hideCursor() # Hide the cursor during runtime
var target, output: string
if paramCount() >= 1:
target = paramStr(1)
else:
target = "Hello, world"
output = spaces(target.len)
for i, x in target:
while x != output[i]:
output[i] = chr(rand(32 .. 126))
# Instead of writing a bunch of new lines, we can just write to the same
# line repeatedly.
eraseLine()
stdout.write(output)
stdout.flushFile() # Force writing to console every iteration
sleep(3) # milliseconds
# Add a new line at the end of program execution to keep the terminal tidy.
echo ""
showCursor() # Bring the cursor back
Send stdout to a file #
# stdout_to_file.nim
import std/[terminal, strformat, times]
if isatty(stdout): # ./stdout_to_file
echo "This is output to the terminal."
else: # ./stdout_to_file | cat
const
logFileName = "log.txt"
let
# https://github.com/jasonrbriggs/nimwhistle/blob/183c19556d6f11013959d17dfafd43486e1109e5/tests/cgitests.nim#L15
logFile = open(logFileName, fmWrite)
stdout = logFile
echo &"This is output to the {logFileName} file."
echo &"- Run using nim {NimVersion} on {now()}."
Shell #
Environment Variables #
- Use
putEnv
to set environment variable - Use
getEnv
to get environment variable
import std/[os]
let
envVar = "NIM_TEMP"
envVarVal = "foo"
putEnv(envVar, envVarVal)
echo getEnv(envVar)
foo
File paths #
Nim StdLib path #
Below code is taken from compiler/nimeval.nim
in Nim source code.
import std/[os]
proc findNimStdLib*(): string =
## Tries to find a path to a valid "system.nim" file.
## Returns "" on failure.
try:
let nimexe = os.findExe("nim")
if nimexe.len == 0: return ""
result = nimexe.splitPath()[0] /../ "lib"
if not fileExists(result / "system.nim"):
when defined(unix):
result = nimexe.expandSymlink.splitPath()[0] /../ "lib"
if not fileExists(result / "system.nim"): return ""
except OSError, ValueError:
return ""
echo findNimStdLib()
/home/kmodi/usr_local/apps/7/nim/devel/lib
File base name #
import std/[os]
let filePath = "tests/test1.org"
var (dir, basename, ext) = splitFile(filePath)
echo "dir = ", dir
echo "basename = ", basename
echo "ext = ", ext
dir = tests
basename = test1
ext = .org
Fetching various paths #
See the below example that demonstrates the difference among:
currentSourcePath.parentDir()
- Path of the Nim file containing this code
getProjectPath()
- Path of the Nim file being compiled
getCurrentDir()
- Path where the Nim-compiled binary is run
The locations of example foo.nim
and bar.nim
files are as follows:
<HERE>/
└── a/
├── b/
│ └── bar.nim
└── foo.nim
Content of a/foo.nim
:
# foo.nim
import b/bar
Content of a/b/bar.nim
:
# bar.nim
from std/macros import getProjectPath
from std/os import getCurrentDir, parentDir
const
sDir = currentSourcePath.parentDir()
# sDir will always be the path of the current bar.nim file.
pDir = getProjectPath()
# pDir will always be the path of the Nim file being compiled.
let
cDir = getCurrentDir()
# cDir will be the path where the compiled binary is being run.
echo "Source path = ", sDir
echo "Project path = ", pDir
echo "Current path = ", cDir
Current working directory | Command | sDir | pDir | cDir |
---|---|---|---|---|
(set at compile time) | (set at compile time) | (set at run time) | ||
<HERE>/ | nim c -r a/foo.nim | <HERE>/a/b/ | <HERE>/a/ | <HERE> |
<HERE>/a/ | ./foo | <HERE>/a/b/ | <HERE>/a/ | <HERE>/a/ |
<HERE>/a/b/ | ../foo | <HERE>/a/b/ | <HERE>/a/ | <HERE>/a/b/ |
References #
File handling #
Writing files #
let
fileName = "/tmp/foo/bar/zoo.txt"
dataStr = "abc"
writeFile(fileName, dataStr)
The directory containing the fileName
in the above example has to
exist, else you get the IOError
exception:
Error: unhandled exception: cannot open: /tmp/foo/bar/zoo.txt [IOError]
If you want to force-create a non-existing directory, you can do:
import std/[os, strformat]
let
fileName = "/tmp/foo/bar/zoo.txt"
dataStr = "abc"
(dir, _, _) = splitFile(fileName)
removeDir("/tmp/foo/bar/")
echo &"{dir} exists? {dirExists(dir)}"
if (not dirExists(dir)):
echo &" creating {dir} .."
createDir(dir)
echo &"{dir} exists now? {dirExists(dir)}"
writeFile(fileName, dataStr)
/tmp/foo/bar exists? false
creating /tmp/foo/bar ..
/tmp/foo/bar exists now? true
File Permissions #
Get/read file permissions #
import std/[os]
let
fileName = "/tmp/foo/bar/zoo.txt"
dataStr = "abc"
(dir, _, _) = splitFile(fileName)
if (not dirExists(dir)):
createDir(dir)
writeFile(fileName, dataStr)
let filePerm = getFilePermissions(fileName)
echo filePerm
{fpUserWrite, fpUserRead, fpGroupRead, fpOthersRead}
Octal string to set[FilePermission]
#
import std/[os]
proc parseFilePermissions(octals: string): set[FilePermission] =
## Converts the input permissions octal string to a Nim set for FilePermission type.
# https://nim-lang.github.io/Nim/os.html#FilePermission
var perm: set[FilePermission]
let
readPerms = @[fpUserRead, fpGroupRead, fpOthersRead]
writePerms = @[fpUserWrite, fpGroupWrite, fpOthersWrite]
execPerms = @[fpUserExec, fpGroupExec, fpOthersExec]
for idx, o in octals:
if o != '0':
if o in {'4', '5', '6', '7'}:
perm = perm + {readPerms[idx]}
if o in {'2', '3', '6', '7'}:
perm = perm + {writePerms[idx]}
if o in {'1', '3', '5', '7'}:
perm = perm + {execPerms[idx]}
result = perm
import std/[random, strformat]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests
for _ in 0 .. 10:
let perm = &"{rs.rand(7)}{rs.rand(7)}{rs.rand(7)}"
echo perm, " = ", parseFilePermissions(perm)
301 = {fpUserExec, fpUserWrite, fpOthersExec}
405 = {fpUserRead, fpOthersExec, fpOthersRead}
727 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupWrite, fpOthersExec, fpOthersWrite, fpOthersRead}
534 = {fpUserExec, fpUserRead, fpGroupExec, fpGroupWrite, fpOthersRead}
623 = {fpUserWrite, fpUserRead, fpGroupWrite, fpOthersExec, fpOthersWrite}
715 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpOthersExec, fpOthersRead}
563 = {fpUserExec, fpUserRead, fpGroupWrite, fpGroupRead, fpOthersExec, fpOthersWrite}
774 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpGroupWrite, fpGroupRead, fpOthersRead}
615 = {fpUserWrite, fpUserRead, fpGroupExec, fpOthersExec, fpOthersRead}
361 = {fpUserExec, fpUserWrite, fpGroupWrite, fpGroupRead, fpOthersExec}
675 = {fpUserWrite, fpUserRead, fpGroupExec, fpGroupWrite, fpGroupRead, fpOthersExec, fpOthersRead}
Exceptions #
Exception Hierarchy #
All exceptions are objects of type Exception
.
Exceptions were broken down into 2 main sub-categories: catchable
errors and defects in Nim 1.4.0. For backward compatibility,
defects behave like catchable errors (--panics:off
). But
defects are exceptions that should not be caught in your program
— It’s recommended to keep them uncatchable by compiling using the
--panics:on
switch.
Starting Nim 1.4.0, exception types in user code should extend from
either CatchableError
or Defect
, not from Exception
directly.
RootObj
Exception
CatchableError
IOError
EOFError
OSError
ResourceExhaustedError
ValueError
KeyError
Defect
(these should ideally not be caught in your program)ArithmeticDefect
DivByZeroDefect
OverflowDefect
AccessViolationDefect
AssertionDefect
OutOfMemoryDefect
IndexDefect
FieldDefect
RangeDefect
StackOverflowDefect
ReraiseDefect
ObjectAssignmentDefect
ObjectConversionDefect
FloatingPointDefect
FloatInvalidOpDefect
FloatDivByZeroDefect
FloatOverflowDefect
FloatUnderflowDefect
FloatInexactDefect
DeadThreadDefect
NilAccessDefect
Custom Exceptions #
type
MyError = object of CatchableError
Raising Exceptions #
type
MyError = object of CatchableError
raise newException(MyError, "details about what went wrong")
Evaluating above will give:
Error: unhandled exception: details about what went wrong [MyError]
Handling/catching Exceptions #
Above example had unhandled exceptions. You must always handle (my
rule) exceptions and do something about it; even if it means simply
printing out the exception error using getCurrentExceptionMsg()
.
type
MyError = object of CatchableError
try:
raise newException(MyError, "details about what went wrong")
except MyError:
stderr.writeLine "Error: ", getCurrentExceptionMsg()
quit 1 # Quit with error exit code
Evaluating above will print this on stderr:
Error: details about what went wrong
getCurrentExceptionMsg()
returns only the msg
parameter from the
Exception
object. If you
need to get the Exception name
too, use getCurrentException()
instead, which return a value of type Exception
, and then print its
name
as shown in the below example.
import std/[strformat]
type
MyError = object of CatchableError
try:
raise newException(MyError, "details about what went wrong")
except MyError:
echo &"[Error] {getCurrentException().name}: {getCurrentException().msg}"
[Error] MyError: details about what went wrong
Rules of Thumb (learnt from Nimisms) #
As I am collecting these rules for myself, I am seeing a trend.. a single space can break Nim!
Don’t add space before array constructors! #
Thanks to @data-man from GitHub for this tip.
Nim Issue #7840
var foo: array [0 .. 4, bool]
echo foo
Above gives:
nim_src_6ylaig.nim(4, 5) Error: invalid type: 'T' in this context: 'array' for var
But remove that space before the [
(aka the array constructor),
and it works!
var foo: array[0 .. 4, bool]
echo foo
[false, false, false, false, false]
Always surround = sign with spaces #
While below works:
let foo=true
echo foo
true
Below doesn’t!
let foo=@["abc", "def"]
echo foo
You will get this error:
int: system [Processing]
Hint: nim_src_kowItE [Processing]
nim_src_kowItE.nim(4, 10) Error: ':' or '=' expected, but found '['
/bin/sh: /tmp/babel-wQPYTr/nim_src_kowItE: Permission denied
But once you surround that =
with spaces, it will work. So always
do that.
let foo: seq[string] = @["abc", "def"]
echo foo
@["abc", "def"]
Always use spaces before and after ..
and ..<
#
Nim Issue #6216
var str = "abc"
echo str[0 .. str.high]
abc
var str = "abc"
echo str[0 ..< str.high]
ab
No space between proc identifier and list of args when num args >= 2 #
Below is a simple proc definition and invocation.. it works.
proc foo(a: int, b: int) =
echo a, " ", b
foo(123, 456)
123 456
But see what happens when I add a space before the opening parenthesis
in foo(123, 456)
:
proc foo(a: int, b: int) =
echo a, " ", b
foo (123, 456)
nim_src_Uewd95.nim(6, 1) Error: type mismatch: got <tuple of (int, int)>
but expected one of:
proc foo(a: int; b: int)
expression: foo (123, 456)
With that space, Nim thinks that we are passing a tuple (123, 456)
to foo
proc!
So no space between proc identifier and list of arguments!
TODO Avoid using auto
as proc return types #
From this comment by @GULPF from GitHub:
You could simplify the proc signature by having separate procs for float32/float64. IMO auto is a code smell and should be avoided, it just makes things harder to understand.
Context:
proc log*[X, B: SomeFloat](x: X, base: B): auto =
## Computes the logarithm ``base`` of ``x``
when B is float64 or X is float64:
var r: float64
else:
var r: float32
r = ln(x) / ln(base)
return r
TODO Update this section with the canonical way to write the above function #
Nimisms #
TO BE FIXED Picky about spaces before array constructor #
Below array declaration is from the manual:
var a {.noInit.}: array [0 .. 1023, char]
gives this error (Nim Issue #7840):
Hint: system [Processing]
Hint: nim_src_sSD4Jx [Processing]
nim_src_sSD4Jx.nim(4, 5) Error: invalid type: 'T' in this context: 'array' for var
Workaround.. don’t put any space before that [
#
var a {.noInit.}: array[0 .. 1023, char]
TO BE FIXED Subrange definition with ..<
#
While the rule in section Always use spaces before and after ..
and ..<
applies in general,
the same still does not work for the example in Nim Issue #6788.
const size = 6
type A = range[0 ..< size]
Trying to compile above gives this error:
nim_src_bshJJ4.nim(5, 10) Error: range types need to be constructed with '..', '..<' is not supported
Workaround #
const size = 6
type A = range[0 .. (size-1)]
FIXED Echoing sequences #
Nim Issue #6225
This issue has been fixed. –
Now fixed #
let seq1 = @[1, 2, 3]
echo "Integer elements: ", seq1
let seq2 = @['1', '2', '3']
echo "Char elements: ", seq2
let seq3 = @["1", "2", "3"]
echo "String elements: ", seq3
Integer elements: @[1, 2, 3]
Char elements: @['1', '2', '3']
String elements: @["1", "2", "3"]
Equivalent code in Python (sort of, because Python does not have
char
vs string
):
list1 = [1, 2, 3]
print('Integer elements: {}'.format(list1))
list2 = ['1', '2', '3']
print('String elements: {}'.format(list2))
Integer elements: [1, 2, 3]
String elements: ['1', '2', '3']
Old issue #
Nim Issue #6225
Unable to distinguish the seq element types from echo
outputs.
let seq1 = @[1, 2, 3]
echo "Integer elements: ", seq1
let seq2 = @['1', '2', '3']
echo "Char elements: ", seq2
let seq3 = @["1", "2", "3"]
echo "String elements: ", seq3
Integer elements: @[1, 2, 3]
Char elements: @[1, 2, 3]
String elements: @[1, 2, 3]
Same as above, but using .repr
:
let seq1 = @[1, 2, 3]
echo "Integer elements (Repr): ", seq1.repr
let seq2 = @['1', '2', '3']
echo "Char elements (Repr): ", seq2.repr
let seq3 = @["1", "2", "3"]
echo "String elements (Repr): ", seq3.repr
Integer elements (Repr): 0x7f4c87d8e048[1, 2, 3]
Char elements (Repr): 0x7f4c87d90048['1', '2', '3']
String elements (Repr): 0x7f4c87d8e0b8[0x7f4c87d900f8"1", 0x7f4c87d90120"2", 0x7f4c87d90148"3"]
Equivalent code in Python (sort of, because Python does not have
char
vs string
):
list1 = [1, 2, 3]
print('Integer elements: {}'.format(list1))
list2 = ['1', '2', '3']
print('String elements: {}'.format(list2))
Integer elements: [1, 2, 3]
String elements: ['1', '2', '3']
Improving the default echo
proc, a new proc debug
#
debug
proc is not needed, once Nim PR #6825
gets merged.
- Source by @bluenote10 from GitHub
import std/[macros, sequtils, strutils, sugar]
proc toDebugRepr[T](x: T): string =
when T is seq:
result = "@[" & x.map(el => toDebugRepr(el)).join(", ") & "]"
elif T is array:
result = "[" & x.map(el => toDebugRepr(el)).join(", ") & "]"
elif T is string:
result = "\"" & x & "\""
elif T is char:
result = "'" & x & "'"
else:
result = $x
macro debug*(args: varargs[typed]): untyped =
result = newCall(bindSym("echo"))
for arg in args:
result.add(newCall(bindSym("toDebugRepr"), arg))
debug 1, 2, 3
debug 123
debug "hello world"
debug "a", "b"
debug(@[1, 2, 3])
debug(@["1", "2", "3"])
debug(@['1', '2', '3'])
debug([1, 2, 3])
debug(["1", "2", "3"])
debug(['1', '2', '3'])
let temp_seq: seq[string] = "1,2,3".split(',', maxsplit=1)
echo temp_seq
debug temp_seq
123
123
"hello world"
"a""b"
@[1, 2, 3]
@["1", "2", "3"]
@['1', '2', '3']
[1, 2, 3]
["1", "2", "3"]
['1', '2', '3']
@[1, 2,3]
@["1", "2,3"]
Python print
for comparison #
print(1, 2, 3)
print("hello world")
print("a", "b")
print([1, 2, 3])
print(["1", "2", "3"])
1 2 3
hello world
a b
[1, 2, 3]
['1', '2', '3']
FIXED Inconsistency in parentheses requirement after echo #
Nim Issue #6210
This issue has been fixed.
import std/[strutils]
echo "Sequences:"
echo @["a", "b", "c"].join(" ")
echo join(@["a", "b", "c"], " ")
echo "Lists:"
echo (["a", "b", "c"].join(" ")) # Works!
echo join(["a", "b", "c"], " ") # Works!
echo ["a", "b", "c"].join(" ") # This did not use to work, now works! -- Mon Oct 16 11:03:52 EDT 2017 - kmodi
var list = ["a", "b", "c"]
echo list.join(" ") # Works too!
Sequences:
a b c
a b c
Lists:
a b c
a b c
a b c
a b c
Importing and Exporting Modules #
import
#
The import
statement can contain just the module name with the path,
if needed too.
Importing a module present in the current directory.
foo/ - foo.nim - bar.nim
# Inside foo.nim import bar
Importing a module present in a sub-directory.
foo/ - foo.nim extra/ - bar.nim
# Inside foo.nim import extra/bar
export
#
The export
statement can contain only the module name – No paths
(ref).
Importing and then exporting a module present in the current directory.
foo/ - foo.nim - bar.nim
# Inside foo.nim import bar export bar
Importing a module present in a sub-directory, and then exporting the same.
foo/ - foo.nim extra/ - bar.nim
# Inside foo.nim import extra/bar export bar
Exporting identifiers (Asterisks after proc names, etc.) #
From Nim Tutorial – Modules:
A module may gain access to the symbols of another module by using the
import
statement. Only top-level symbols that are marked with an asterisk (*
) are exported.
I got curious about the *
after I read code like below here:
proc getLinkableFiles*(appPath: string, dest: string=expandTilde("~")): seq[LinkInfo] =
See Object Types for exporting objects and their individual fields.
Modules/Packages/Libraries #
sugar
(Nim 0.19.0+) #
This is a standard Nim library.
This library was called future
in Nim 0.18.0 and older.
This module implements nice syntactic sugar based on Nim’s macro system.
DONE Lambdas (=>
) #
Here is an example of using lambda
in Emacs Lisp, that returns the
1-incremented value of the input.
(let ((plus1 (lambda (x)
(+ x 1))))
(funcall plus1 2))
Here’s the same using the =>
macro in Nim:
import std/[sugar]
proc int2Int(fn: proc(inp: int): int, x: int): int = # Define the proc parameter and return types
## Wrapper proc to define the ``fn`` type, and to pass its inputs.
fn(x) # Call the passed fn with input x
echo int2Int(x => x + 1, 2) # Call the int2Int wrapper with the fn definition and input arg
3
- The
int2Int
wrapper as in the above example is needed because Nim needs to know the types of thefn
proc parameters and its return value. - Later we call that wrapper with the
fn
definitionx => x + 1
and its input argument2
.
Thanks to @narimiran from GitHub for this snippet as an another
example of using =>
, with map
.
import std/[sequtils, sugar]
let
a = [1, 2, 3, 5, 7]
b = a.map(x => 2*x)
echo b
@[2, 4, 6, 10, 14]
Also see Anonymous procedures.
DONE Syntactic sugar for proc types (->
) #
The ->
macro helps abbreviate proc types.
proc(inp: int): int
can be replaced withint -> int
.proc(x: int; y: float): string
can be replaced with(int, float) -> string
.
So Code Snippet 36 can be rewritten using ->
as shown below:
import std/[sugar]
proc int2Int(fn: int -> int, x: int): int =
## Wrapper proc to define the ``fn`` type, and to pass its inputs.
fn(x)
echo int2Int(x => x + 1, 2)
3
Here are few more examples of using =>
and ->
:
import std/[sugar]
proc twoInts2Int(fn: (int, int) -> int; x, y: int): int =
## Wrapper proc to define the ``fn`` type, and to pass its inputs.
fn(x, y)
proc twoFlts2Flt(fn: (float, float) -> float; x, y: float): float =
## Wrapper proc to define the ``fn`` type, and to pass its inputs.
fn(x, y)
echo twoInts2Int((x, y) => x + y, 2, 3)
echo twoFlts2Flt((x, y) => x + y, 1.11, 2.22)
5
3.33
The above example can be made more concise by using Generics. Thanks
to @narimiran from GitHub for pointing out that the known types for
the inputs need to come first in the twoInpOneOut
proc signature
below.
As the type of fn
is unknown, it cannot be placed as the first
parameter of twoInpOneOut
as that would fail the auto-inference of
types of x
and y
.
import std/[sugar]
proc twoInpOneOut[T](x, y: T; fn: (T, T) -> T): T =
## Wrapper proc to define the ``fn`` type, and to pass its inputs.
fn(x, y)
echo twoInpOneOut(2, 3, (x, y) => x + y)
echo twoInpOneOut(1.11, 2.22, (x, y) => x + y)
echo twoInpOneOut("abc", "def", (x, y) => x & y)
5
3.33
abcdef
List Comprehension using []
and lc
(Deprecated) #
lc
has been deprecated since nim 1.2.0. New alternative is to use
the sugar.collect
macro, or to use the comprehension
package.
List comprehension is implemented in the sugar
module using the []
macro.
The syntax is:
lc[<RETURN_EXPR using ELEMs> | (<ELEM1> <- <LIST1> [, <ELEM2> <- <LIST2>, ..] [, <COND>]), <SEQ_TYPE>]
- The whole
lc[ .. ]
expression returns a sequence of typeseq[SEQ_TYPE]
. - ELEM1 is an element of LIST1, ELEM2 is an element of LIST2, and so
on.
<ELEM2> <- <LIST2>
and onwards are optional.
- LIST2 can use earlier declared element vars like ELEM1.
- LIST3 can use earlier declared element vars like ELEM1 and ELEM2, and so on.
- Optional COND can use one or more of the declared element vars.
- RETURN_EXPR can use one or more of the declared element vars.
- RETURN_EXPR is of the type SEQ_TYPE.
Here’s an example (Ref) that splits a string into a sequence of characters:
# This code snippet will not work nim 1.2.0 onwards. See sugar.collect instead.
import std/[strformat, sugar]
let
str = "nim lang"
cSeq = lc[c | (c <- str), # lc[EXPR | (ELEM1 <- LIST1),
char] # SEQ_TYPE]
echo &"str of type {$type(str)} = {str}"
echo &"cSeq of type {$type(cSeq)} (seq[SEQ_TYPE]) = {cSeq}"
str of type string = nim lang
cSeq of type seq[char] (seq[SEQ_TYPE]) = @['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']
The above example can be written more concisely using mapIt
or
map
and =>
from sugar
module as shown below or even using comp
from comprehension
package.
import std/[sequtils, sugar]
let
str = "nim lang"
cSeq = str.map(c => c)
echo cSeq
@['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']
Other list comprehension examples:
# This code snippet will not work nim 1.2.0 onwards. See sugar.collect instead.
import std/[sugar]
let
numList = 1 .. 10
evenNums = lc[x | (x <- numList, # lc[EXPR | (ELEM1 <- LIST1,
x mod 2 == 0), # COND),
int] # SEQ_TYPE]
echo evenNums
@[2, 4, 6, 8, 10]
With lc
now deprecated, the above example can also be written using
comp
from comprehension
package.
The LIST2 and onwards can also use one or more of the prior declared ELEMs.
# This code snippet will not work nim 1.2.0 onwards. See sugar.collect instead.
import std/[sugar]
const
n = 15
let
numList = 1 .. n
rightAngleTriangleSides =
lc[(x, y, z) | (x <- numList, # lc[EXPR | (ELEM1 <- LIST1,
y <- x .. n, # ELEM2 <- LIST2,
z <- y .. n, # ELEM3 <- LIST3,
x*x + y*y == z*z), # COND),
tuple[a, b, c: int]] # SEQ_TYPE]
echo rightAngleTriangleSides
@[(a: 3, b: 4, c: 5), (a: 5, b: 12, c: 13), (a: 6, b: 8, c: 10), (a: 9, b: 12, c: 15)]
DONE comprehension
#
This is an external library.
Installation #
nimble install https://github.com/alehander42/comprehension@#head
Sequence Comprehension #
As the name says, sequence comprehension will output seq
objects.
Use comp[ .. : <seq element> ]
.
import std/[strformat]
import comprehension
let
str = "nim lang"
cSeq = comp[for idx, val in str: val]
echo &"str of type {$type(str)} = {str}"
echo &"cSeq of type {$type(cSeq)} = {cSeq}"
str of type string = nim lang
cSeq of type seq[char] = @['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']
import std/[strformat, sequtils]
import comprehension
let
numList = 1 .. 10
numSeq = numList.toSeq
evenNums = comp[for idx, val in numSeq:
if val mod 2 == 0:
val]
echo &"numList of type {$type(numList)} = {numList}"
echo &"numSeq of type {$type(numSeq)} = {numSeq}"
echo &"evenNums of type {$type(evenNums)} = {evenNums}"
numList of type HSlice[system.int, system.int] = 1 .. 10
numSeq of type seq[int] = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evenNums of type seq[int] = @[2, 4, 6, 8, 10]
import std/[strformat, tables]
import comprehension
let
myTable = {0:0, 3:1, 4:5}.toTable()
mySeq = comp[for key, val in myTable: key + val]
echo &"myTable of type {$type(myTable)} = {myTable}"
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
myTable of type Table[system.int, system.int] = {0: 0, 3: 1, 4: 5}
mySeq of type seq[int] = @[0, 4, 9]
Set Comprehension #
As the name says, set comprehension will output sets (HashSet
objects).
Use comp{ .. : <set element> }
.
It is necessary to import std/[sets]
too, when doing set
comprehensions.
import std/[strformat, sets]
import comprehension
let
str = "nim lang"
cSet = comp{for idx, val in str: val}
echo &"str of type {$type(str)} = {str}"
echo &"cSet of type {$type(cSet)} = {cSet}"
str of type string = nim lang
cSet of type HashSet[system.char] = {'g', ' ', 'l', 'n', 'i', 'a', 'm'}
import std/[strformat, sets]
import comprehension
let
mySeq = @[1, 2, 3, 4, 5, 100]
mySet = comp{for key, val in mySeq: val*val}
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"mySet of type {$type(mySet)} = {mySet}"
mySeq of type seq[int] = @[1, 2, 3, 4, 5, 100]
mySet of type HashSet[system.int] = {4, 16, 1, 9, 25, 10000}
As it can be seen from these examples, do not expect the outputs of set comprehension to have elements in any particular order.
import std/[strformat, sets, tables]
import comprehension
let
myTable = {1:100, 2:200, 3:300}.toTable()
mySet = comp{for key, val in myTable: val*val}
echo &"myTable of type {$type(myTable)} = {myTable}"
echo &"mySet of type {$type(mySet)} = {mySet}"
myTable of type Table[system.int, system.int] = {3: 300, 2: 200, 1: 100}
mySet of type HashSet[system.int] = {90000, 40000, 10000}
Table Comprehension #
As the name says, table comprehension will output Table
objects.
Use comp{ .. : <table element> }
.
It is necessary to import std/[tables]
too, when doing table
comprehensions.
import std/[strformat, tables]
import comprehension
let
str = "nim lang"
cTable = comp{for idx, val in str: {idx: val}}
echo &"str of type {$type(str)} = {str}"
echo &"cTable of type {$type(cTable)} = {cTable}"
str of type string = nim lang
cTable of type Table[system.int, system.char] = {4: 'l', 5: 'a', 7: 'g', 3: ' ', 2: 'm', 6: 'n', 0: 'n', 1: 'i'}
import std/[strformat, tables]
import comprehension
let
myTable1 = {0:0, 3:1, 4:5}.toTable()
myTable2 = comp{for key, val in myTable1: {key : key + val}}
echo &"myTable1 of type {$type(myTable1)} = {myTable1}"
echo &"myTable2 of type {$type(myTable2)} = {myTable2}"
myTable1 of type Table[system.int, system.int] = {0: 0, 3: 1, 4: 5}
myTable2 of type Table[system.int, system.int] = {4: 9, 3: 4, 0: 0}
As it can be seen from these examples, do not expect the outputs of table comprehension to have elements in any particular order.
import std/[strformat, tables]
import comprehension
let
mySeq = @[1, 2, 3]
myTable = comp{for key, val in mySeq: {key : val*val}}
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"myTable of type {$type(myTable)} = {myTable}"
mySeq of type seq[int] = @[1, 2, 3]
myTable of type Table[system.int, system.int] = {2: 9, 0: 1, 1: 4}
Below example originated from a question on Nim Gitter.
import std/[strutils, strformat, tables]
import comprehension
let
myStr = "abc=def ghi=jkl"
mySeq = myStr.split(' ')
myTable = comp{for _, val in mySeq: {val.split('=')[0] : val.split('=')[1]}}
echo &"myStr of type {$type(myStr)} = {myStr}"
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"myTable of type {$type(myTable)} = {myTable}"
myStr of type string = abc=def ghi=jkl
mySeq of type seq[string] = @["abc=def", "ghi=jkl"]
myTable of type Table[system.string, system.string] = {"ghi": "jkl", "abc": "def"}
Here’s another way to do the same using String Tables (credit @awr1 from GitHub from here):
import std/[strtabs, strutils, strformat]
let
myStr = "abc=def ghi=jkl"
mySeq = myStr.split({'=', ' '})
myStrTable = mySeq.newStringTable(modeCaseSensitive)
echo &"myStr of type {$type(myStr)} = {myStr}"
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"myStrTable of type {$type(myStrTable)} = {myStrTable}"
myStr of type string = abc=def ghi=jkl
mySeq of type seq[string] = @["abc", "def", "ghi", "jkl"]
myStrTable of type StringTableRef = {ghi: jkl, abc: def}
parsetoml
#
This is an external library.
Installation #
nimble install parsetoml
Extracting all keys of a TOML table #
import parsetoml
let
cfg = parsetoml.parseString("""
[tmux]
req_env_vars = ["STOW_PKGS_TARGET"]
[tmux.set_env_vars]
CFLAGS = "-fgnu89-inline -I${STOW_PKGS_TARGET}/include -I${STOW_PKGS_TARGET}/include/ncursesw"
LDFLAGS = "-L${STOW_PKGS_TARGET}/lib"
""").getTable()
pkg = "tmux"
for key, val in pairs(cfg[pkg].getTable()):
echo key
req_env_vars
set_env_vars
strfmt
(Yep, this is different from strformat
) #
- My Fork
- Source | Documentation
- To install:
nimble install https://github.com/kaushalmodi/strfmt
- To install:
The original repo named strfmt
by a bitbucket.org user lyro
does
not exist any more (as of ).
Thanks to the tip by @Yardanico from GitHub, the strfmt
module by
lyro allows using a string formatting syntax that’s similar to
Python’s Format Specification Mini-Language (.format()
).
Unfortunately though, the strfmt
developer has stopped using Nim and
has also removed their repo from bitbucket.org. Also the fmt
identifier from this package clashes with the fmt
in the strformat
module that got added to Nim 0.18.0, if both strfmt
and strformat
modules are imported in a Nim project. So from the aspect of future
support, it might be better to use just the strformat
module. 😞
Example of .format
use in Python 3.7.0:
print('{} {}'.format(1, 2))
print('{} {}'.format('a', 'b'))
1 2
a b
Similar example using fmt
from strfmt
module:
import strfmt
echo "{} {}".fmt(1, 0)
echo "{} {}".fmt('a', 'b')
echo "{} {}".fmt("abc", "def")
echo "{0} {1} {0}".fmt(1, 0)
echo "{0.x} {0.y}".fmt((x: 1, y:"foo"))
1 0
a b
abc def
1 0 1
1 foo
bignum
#
This is an external library.
Installation #
nimble install https://github.com/kaushalmodi/bignum
bignum
Example #
import std/[strformat]
import bignum
const upper = 61
var bn: array[upper, Rat]
proc bernoulli(n: int): Rat =
var A: seq[Rat]
for i in 0 .. n:
A.add(newRat())
for i in 0 .. A.high:
discard A[i].set(1, i+1)
for j in countDown(i, 1):
A[j-1] = j*(A[j-1] - A[j])
return A[0] # (which is Bn)
for i in 0 .. bn.high:
bn[i] = bernoulli(i)
if bn[i].toFloat != 0.0:
echo &"B({i:2}) = {bn[i]:>55}"
B( 0) = 1
B( 1) = 1/2
B( 2) = 1/6
B( 4) = -1/30
B( 6) = 1/42
B( 8) = -1/30
B(10) = 5/66
B(12) = -691/2730
B(14) = 7/6
B(16) = -3617/510
B(18) = 43867/798
B(20) = -174611/330
B(22) = 854513/138
B(24) = -236364091/2730
B(26) = 8553103/6
B(28) = -23749461029/870
B(30) = 8615841276005/14322
B(32) = -7709321041217/510
B(34) = 2577687858367/6
B(36) = -26315271553053477373/1919190
B(38) = 2929993913841559/6
B(40) = -261082718496449122051/13530
B(42) = 1520097643918070802691/1806
B(44) = -27833269579301024235023/690
B(46) = 596451111593912163277961/282
B(48) = -5609403368997817686249127547/46410
B(50) = 495057205241079648212477525/66
B(52) = -801165718135489957347924991853/1590
B(54) = 29149963634884862421418123812691/798
B(56) = -2479392929313226753685415739663229/870
B(58) = 84483613348880041862046775994036021/354
B(60) = -1215233140483755572040304994079820246041491/56786730
typetraits
#
This is a standard Nim library.
This module mainly defines the name
proc which is used to get the
string name of any type. It also defines $
aliased to name
.
import std/[typetraits]
var x = 5
var y = "foo"
# Using regular proc call style
echo name(x.type), " x = ", x
echo name(y.type), " y = ", y
# Using UFCS
echo x.type.name, " x = ", x
echo y.type.name, " y = ", y
# Using $
echo $x.type, " x = ", x
echo $y.type, " y = ", y
int x = 5
string y = foo
int x = 5
string y = foo
int x = 5
string y = foo
As of 0831292863, importing typetraits
is no longer
required if you just need to print a type name. Example: echo $type(100)
or echo $(100).type
will print "int"
.
TO BE FIXED Clash between type.name
and a custom tuple field name #
Nim Issue #7975
Using $
works #
import std/[strformat, typetraits]
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {$type(person)} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using name(VAR.type)
works too #
import std/[strformat, typetraits]
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {name(person.type)} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using VAR.type.name
FAILS #
import std/[strformat, typetraits]
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {person.type.name} = {person}"
Hint: typetraits [Processing]
nim_src_QKIsPQ.nim(7, 9) template/generic instantiation of `fmt` from here
/home/kmodi/stow/pkgs/nim/devel/lib/pure/strformat.nim(260, 8) Error: type mismatch: got <string, type string>
but expected one of:
proc add(result: var string; x: int64)
first type mismatch at position: 2
required type: int64
but expression 'type(person).name' is of type: type string
proc add(x: var string; y: string)
first type mismatch at position: 2
required type: string
but expression 'type(person).name' is of type: type string
proc add(x: var string; y: cstring)
first type mismatch at position: 2
required type: cstring
but expression 'type(person).name' is of type: type string
proc add(result: var string; x: float)
first type mismatch at position: 2
required type: float
but expression 'type(person).name' is of type: type string
proc add(x: var string; y: char)
first type mismatch at position: 2
required type: char
but expression 'type(person).name' is of type: type string
2 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them
expression: add(fmtRes446027, type(person).name)
Changing the tuple field name to anything but “name” works (not practical) #
type.name
works again if I change the tuple field name to anything
but name
:
import std/[strformat, typetraits]
type Person = tuple[namex: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {person.type.name} = {person}"
Tuple person of type Person = (namex: "Peter", age: 30)
FIXED Tuple type name gets stuck to the first printed tuple var #
Nim Issue #7976
The results of the below code blocks are frozen in the incorrect output state when this bug existed. It’s not known which commit on Nim devel fixed this bug. But as of a1e268e3dccdde4df9b11a0ee87971e1143fbb43 on , this issue is verified as fixed.
In the below examples:
person1
is a tuple variable of custom typePerson
.person2
is another tuple variable of the same type, but it is not assigned thePerson
type explicitly, it is directly assigned thetuple[name: string, age: int]
type.
In the below example, I am first printing person1
type and value,
and then the same for person2
.
import std/[strformat]
type Person = tuple[name: string, age: int]
let
person1: Person = ("Peter", 30)
person2: tuple[name: string, age: int] = (name: "Peter", age: 30)
echo &"Tuple person1 of type {$person1.type} = {person1}"
echo &"Tuple person2 of type {$person2.type} = {person2}"
Tuple person1 of type Person = (name: "Peter", age: 30)
Tuple person2 of type Person = (name: "Peter", age: 30)
Above, I was expecting the person2
type to be printed as
tuple[name: string, age: int]
.
Now, below I just switch the order of printing the same tuple
variables.. this time I print person2
type and value first.
.. and now, the type name printed for both person1
and person2
is
tuple[name: string, age: int]
!
import std/[strformat]
type Person = tuple[name: string, age: int]
let
person1: Person = ("Peter", 30)
person2: tuple[name: string, age: int] = (name: "Peter", age: 30)
echo &"Tuple person2 of type {$person2.type} = {person2}"
echo &"Tuple person1 of type {$person1.type} = {person1}"
Tuple person2 of type tuple[name: string, age: int] = (name: "Peter", age: 30)
Tuple person1 of type tuple[name: string, age: int] = (name: "Peter", age: 30)
Above, I was expecting the person1
type to be printed as Person
.
tables
#
This is a standard Nim library.
From ref:
import std/[tables]
var a = {"hi": 1, "there": 2}.toTable
echo a["hi"], " ", a.len
echo "a is of type ", $type(a)
assert a.hasKey("hi")
for key, value in a:
echo key, " " ,value
1 2
a is of type Table[system.string, system.int]
hi 1
there 2
From ref:
import std/[tables]
var
hash = initTable[string, int]() # empty hash table
hash2 = {"key1": 1, "key2": 2}.toTable # hash table with two keys
hash3 = [("key1", 1), ("key2", 2)].toTable # hash table from tuple array
hash4 = @[("key1", 1), ("key2", 2)].toTable # hash table from tuple seq
value = hash2["key1"]
hash["spam"] = 1
hash["eggs"] = 2
hash.add("foo", 3)
echo "hash has ", hash.len, " elements"
echo "hash has key foo? ", hash.hasKey("foo")
echo "hash has key bar? ", hash.hasKey("bar")
echo "iterate pairs:" # iterating over (key, value) pairs
for key, value in hash:
echo key, ": ", value
echo "iterate keys:" # iterating over keys
for key in hash.keys:
echo key
echo "iterate values:" # iterating over values
for key in hash.values:
echo key
# getting values of specified keys
echo """hash["spam"] = """, hash["spam"]
# If you try to get value for an unset key, you will get
# a KeyError exception.
try:
echo """hash["key_not_set_yet"] = """, hash["key_not_set_yet"]
except KeyError:
echo """hash["key_not_set_yet"] is not yet set."""
hash has 3 elements
hash has key foo? true
hash has key bar? false
iterate pairs:
eggs: 2
spam: 1
foo: 3
iterate keys:
eggs
spam
foo
iterate values:
2
1
3
hash["spam"] = 1
hash["key_not_set_yet"] is not yet set.
Nested Tables #
import std/[tables, strformat]
type
Coord = tuple
x: int
y: int
CoordTable = Table[Coord, int]
var
t: CoordTable
let
coords = @[(1,1).Coord, (2,2), (7,8), (8,8), (2,6), (0,4), (2,2), (7,8), (7,8)]
for coord in coords:
if t.hasKey(coord):
t[coord].inc
else:
t[coord] = 1
echo t
{(x: 2, y: 2): 2, (x: 7, y: 8): 3, (x: 8, y: 8): 1, (x: 2, y: 6): 1, (x: 1, y: 1): 1, (x: 0, y: 4): 1}
critbits
#
This is a standard Nim library.
Crit-bit Trees were introduced by Adam Langley in this paper of his. This module is an implementation of that.
To me, the CritBitTree[T]
type defined by critbits
looks like a
string-indexed dictionary/hash/table/associative-array-like type.
For brevity, now on I will refer to CritBitTree as CBT.
In the documentation, the CritBitTree[T]
type is defined as:
CritBitTree[T] = object
root: Node[T]
count: int
where T
is the type of the value held by each CritBitTree “key”
(maybe it’s technically called a node?).
As I am more familiar with hashes and dictionaries, I’ll just refer to “whatever technically correct term for ‘key’ in CBT’s” as just key.
CBT Sets #
CBT’s can also be used in a
non-dictionary/hash/table/associative-array fashion.. as just sets of
the string keys. The type T
for such CBT’s is void
.
Now on, I will refer to such CBT’s, with T
as void
, as set-type
or void-type.
The other category of CBT’s will be referred to as hash-type.
Initializing #
CBT objects auto-initialize.
import std/[strformat, critbits]
var
s: CritBitTree[void]
t: CritBitTree[int]
echo &"{$type(s)} s (CBT as set) = {s}"
echo &"{$type(t)} t (CBT as hash) = {t}"
CritBitTree[system.void] s (CBT as set) = {}
CritBitTree[system.int] t (CBT as hash) = {:}
FIXED $
does not work for set-type CBT’s #
Nim Issue #7987 Fixed by @data-man from GitHub in aa7348b356.
- Older issue
- Due to this bug, the echoing of the set-type CBT
s
in above example was commented out.Uncommenting that line gave this error:
nim_src_saJsiR.nim(8, 9) template/generic instantiation from here lib/pure/strformat.nim(268, 13) template/generic instantiation from here lib/pure/collections/critbits.nim(325, 26) template/generic instantiation from here lib/pure/collections/critbits.nim(244, 43) Error: undeclared field: 'val'
Keys #
Adding keys #
For hash-type CBT’s #
If your CBT variable t
is of type CritBitTree[T]
, you assign a
value to STRING key using t[STRING] = VAL
, where VAL is of type T
.
import std/[strformat, critbits]
var t: CritBitTree[int]
t["a"] = 1
echo &"{$type(t)} t = {t}"
echo """Mutating or changing the value of t["a"] .. """
t["a"] = 2
echo &"{$type(t)} t = {t}"
CritBitTree[system.int] t = {"a": 1}
Mutating or changing the value of t["a"] ..
CritBitTree[system.int] t = {"a": 2}
Only for “int” CBT’s, you can even use the inc
to set initial
values (defaults to 1) for unassigned keys.
import std/[critbits]
var t: CritBitTree[int]
t.inc("a")
t.inc("b")
t.inc("c")
t.inc("d", 10)
echo t
{"a": 1, "b": 1, "c": 1, "d": 10}
Above code is equivalent to:
import std/[critbits]
var t: CritBitTree[int]
t["a"] = 1
t["b"] = 1
t["c"] = 1
t["d"] = 10
echo t
{"a": 1, "b": 1, "c": 1, "d": 10}
Keys can also be added using incl
.
import std/[critbits]
var
t1: CritBitTree[int]
t2: CritBitTree[string]
t1.incl("a", 12)
t1.incl("b", 34)
echo t1
t1.incl("a", 77) # Overwrite the "a" key value
echo t1
t2.incl("a", "def")
t2.incl("b", "xyz")
echo t2
{"a": 12, "b": 34}
{"a": 77, "b": 34}
{"a": "def", "b": "xyz"}
FIXED Only the first key get initialized to 1; all others init to 0
Nim Issue #7990
Thanks to @data-man from GitHub for fixing this in 12f929e582.
Older Issue : Earlier, the above output was:
text {"a": 1, "b": 0, "c": 0, "d": 0}
For set-type CBT’s #
The set-type CBT’s have only keys.. no values. So for these, you
have to use the incl
proc to add the keys or set elements.
import std/[strformat, critbits]
var s: CritBitTree[void]
echo &"Type of s: {$type(s)}"
echo """Adding "a" .."""
incl(s, "a")
echo """Adding "b" .."""
s.incl("b")
echo &" s has {s.len} elements: {s}"
echo """Adding "a" again .."""
s.incl("a")
echo &" s *still* has {s.len} elements: {s}"
Type of s: CritBitTree[system.void]
Adding "a" ..
Adding "b" ..
s has 2 elements: {"a", "b"}
Adding "a" again ..
s *still* has 2 elements: {"a", "b"}
TODO Removing keys #
Key check #
Use contains
or its alias hasKey
.
import std/[strformat, critbits]
var
s: CritBitTree[void]
t: CritBitTree[int]
s.incl("y")
t["a"] = 1
echo &"{$type(s)} s = {s}"
echo &"""s["x"] exists? {s.contains("x")}"""
echo &"""s["y"] exists? {s.hasKey("y")}"""
echo &"{$type(t)} t = {t}"
echo &"""t["a"] exists? {t.contains("a")}"""
echo &"""t["b"] exists? {t.hasKey("b")}"""
CritBitTree[system.void] s = {"y"}
s["x"] exists? false
s["y"] exists? true
CritBitTree[system.int] t = {"a": 1}
t["a"] exists? true
t["b"] exists? false
Number of keys #
Use len
.
import std/[strformat, critbits]
var
s: CritBitTree[void]
t: CritBitTree[string]
s.incl("foo")
s.incl("bar")
s.incl("zoo")
echo &"{$type(s)} s = {s}"
echo &"Number of elements in s = {s.len}"
t["a"] = "hello"
t["b"] = "world"
echo &"{$type(t)} t = {t}"
echo &"Number of elements in t = {t.len}"
CritBitTree[system.void] s = {"bar", "foo", "zoo"}
Number of elements in s = 3
CritBitTree[system.string] t = {"a": "hello", "b": "world"}
Number of elements in t = 2
Values #
Incrementing/Decrementing key values #
This works only for CBT’s of type CritBitTree[int]
.
Incrementing key values #
import std/[strformat, critbits]
var t: CritBitTree[int]
t["a"] = 10
echo &"{$type(t)} t = {t}"
inc(t, "a")
echo &"{$type(t)} t = {t}"
t.inc("a")
echo &"{$type(t)} t = {t}"
CritBitTree[system.int] t = {"a": 10}
CritBitTree[system.int] t = {"a": 11}
CritBitTree[system.int] t = {"a": 12}
Decrementing key values #
The inc
proc increments the key value by 1 by default. But if a
negative “incrementing” value is provided, it can decrement too.
import std/[strformat, critbits]
var t: CritBitTree[int]
echo &"{$type(t)} t = {t}"
echo """Initializing/incrementing "a" key to 1 .."""
t.inc("a")
echo &" {$type(t)} t = {t}"
echo """Decrementing "a" key by 1 .."""
t.inc("a", -1)
echo &" {$type(t)} t = {t}"
echo """Incrementing "a" key by 4 .."""
t.inc("a")
t.inc("a")
t.inc("a")
t.inc("a")
echo &" {$type(t)} t = {t}"
echo """Decrementing "a" key by 2 .."""
t.inc("a", -1)
t.inc("a", -1)
echo &" {$type(t)} t = {t}"
CritBitTree[system.int] t = {:}
Initializing/incrementing "a" key to 1 ..
CritBitTree[system.int] t = {"a": 1}
Decrementing "a" key by 1 ..
CritBitTree[system.int] t = {"a": 0}
Incrementing "a" key by 4 ..
CritBitTree[system.int] t = {"a": 4}
Decrementing "a" key by 2 ..
CritBitTree[system.int] t = {"a": 2}
Extending the above, you can increment/decrement by any value other than 1 too.
import std/[strformat, critbits]
var t: CritBitTree[int]
echo &"{$type(t)} t = {t}"
t.inc("a", 5)
t.inc("b", 7)
echo &"{$type(t)} t = {t}"
t.inc("a", -10)
t.inc("b", -100)
echo &"{$type(t)} t = {t}"
CritBitTree[system.int] t = {:}
CritBitTree[system.int] t = {"a": 5, "b": 7}
CritBitTree[system.int] t = {"a": -5, "b": -93}
Retrieving key values (only for hash-type CBT’s) #
This works only for hash-type CBT’s.
If you do know that an X key is present in a CBT t
, you can get
that key’s value using t[X]
.
import std/[strformat, critbits]
var t: CritBitTree[int]
t["a"] = 1
echo &"""t["a"] = {t["a"]}"""
t["a"] = 1
Trying to do t[X]
for a set-type CBT will give this error:
nim_src_LF3H1K.nim(9, 9) template/generic instantiation from here
lib/pure/strformat.nim(313, 39) template/generic instantiation from here
lib/pure/collections/critbits.nim(203, 6) Error: undeclared field: 'val'
If you don’t know whether an X key is present in a CBT t
, you need
to first check if that key exists using hasKey
.
import std/[critbits]
var t: CritBitTree[int]
t["a"] = 123
if t.hasKey("a"):
echo """t["a"] = """, t["a"]
if t.hasKey("b"):
echo """t["b"] = """, t["b"] # this will not be printed
t["a"] = 123
TODO Iterators #
TODO sequtils
#
This is a standard Nim library.
mapIt
#
mapIt
is a convenience template around the map
proc to reduce typing.
The template injects the it variable which you can use directly in an expression.
import std/[sequtils]
let
nums = @[1, 2, 3, 4]
strings = nums.mapIt($(4 * it))
echo strings
@["4", "8", "12", "16"]
Below shows the how the map
plus =>
can be rewritten a bit more
concisely using mapIt
(Thanks to the SO user Peheje for this tip):
import std/[sequtils]
let
str = "nim lang"
cSeq = str.mapIt(it)
echo cSeq
@['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']
DONE options
#
This is a standard Nim library.
Declaring Option
variables #
import std/[options]
var s: Option[string]
echo s
none(string)
An unset Option variable defaults to None[TYPE]
.
Setting Option
variables #
import std/[options]
var s: Option[float]
s = some(123.0)
echo s
# s = some(1)
# Above will give this error:
# Error: type mismatch: got <Option[system.int]> but expected 'Option[system.float]'
# So specify the type explicitly.
s = some[float](1)
echo s
s = none(float)
echo s
s = none[float]() # This is the same as none(float) used above.
echo s
some(123.0)
some(1.0)
none(float)
none(float)
Getting Option
variable values #
import std/[options]
var s: Option[int]
echo s
s = some(456)
echo s
echo s.get()
none(int)
some(456)
456
Checking if Option
is None/Some #
import std/[options, strformat]
var s: Option[string]
echo s
echo &"Is s None? {s.isNone()}, is s Some? {s.isSome()}"
s = some("")
echo s
echo &"Is s None? {s.isNone()}, is s Some? {s.isSome()}"
s = some("abc")
echo s
echo &"Is s None? {s.isNone()}, is s Some? {s.isSome()}"
none(string)
Is s None? true, is s Some? false
some("")
Is s None? false, is s Some? true
some("abc")
Is s None? false, is s Some? true
Option[string]
as a solution to “nil” string #
In Nim 0.19.0+, string
type vars cannot be set to nil. Earlier a
string was set to nil to distinguish between “string not set” and
“string set to an empty string”.
So to solve that problem, one possible solution is to use the
Option[string]
type.
- An unset
Option[string]
would beNone[string]
. - An
Option[string]
set to an empty string (some("")
) would beSome("")
.
import std/[options]
var s: Option[string]
echo s
s = some("")
echo s
s = some("abc")
echo s
for c in get(s): # Get the value of type T from an Option[T] type var
echo c
none(string)
some("")
some("abc")
a
b
c
FIXED $
for Option[string]
should wrap the output in double quotes #
Nim Issue #8658
TODO json
#
This is a standard Nim library.
Looping through keys in JSON #
import std/[json]
var js = parseJson"""
[{"mango":"green"}
, {"orange":"yellow"}
, {"peach":"red"}
, {"grape":"black"}]"""
var keys = newSeq[string]()
for obj in js:
for k, _ in obj:
keys.add k
echo keys
@["mango", "orange", "peach", "grape"]
Getting JSON via an API endpoint #
- When using the
httpclient
module to get content from an https: domain, compile the code with-d:ssl
switch.
import std/[httpclient, json]
var client = newHttpClient()
let jObj = client.getContent("https://scripter.co/jf2feed.json").parseJson()
echo jObj["author"]
echo jObj["author"].pretty()
echo jObj["author"]["name"]
{"name":"Kaushal Modi","type":"card","url":"https://scripter.co/"}
{
"name": "Kaushal Modi",
"type": "card",
"url": "https://scripter.co/"
}
"Kaushal Modi"
Empty JSON #
import std/[json, strformat]
let
emptyJArray = "[]".parseJson
echo &"emptyJArray.kind = {emptyJArray.kind}"
echo &"emptyJArray = {emptyJArray}"
echo &"emptyJArray.len = {emptyJArray.len}"
emptyJArray.kind = JArray
emptyJArray = []
emptyJArray.len = 0
Loggers #
- Logging
- https://github.com/status-im/nim-chronicles
- https://github.com/FedericoCeratto/nim-morelogging
- https://github.com/briandowns/nlog
Nim By Example #
Examples in this section are from https://nim-by-example.github.io/.
DONE Hello World #
See section Echo.
DONE Variables #
See section Variables.
DONE Result #
See section return
keyword and result
Variable.
DONE Type Casting and Inference #
Nim is a statically typed language. As such, each variable has a type associated with it.
Inferred Types #
As seen in the previous example these types are inferred in the const, let and var declarations by the compiler.
# These types are inferred.
var x = 5 # int
var y = "foo" # string
echo "x is ", $type(x)
echo "y is ", $type(y)
x is int
y is string
Assigning a value of a different type will result in a compile-time error.
var x = 5 # int
var y = "foo" # string
x = y
nim_src_PgW593.nim(7, 3) Error: type mismatch: got <string> but expected 'int'
Converting Types #
Nim Tutorial – Type Conversion
Type conversion can be done by calling the type as a proc
.
import std/[strformat]
var
x = 1.0
xType = $type(x)
echo &"x is {xType} and its value is {x}."
var
xCasted = x.int # int(x) will also work
xCastedType = $type(xCasted)
echo &"xCasted is {xCastedType} and its value is {xCasted}."
x is float64 and its value is 1.0.
xCasted is int and its value is 1.
Casting Types #
Casting is done using the cast
keyword (which is not
recommended). See section Specifying Types for an example.
Specifying Types #
You may optionally specify the type after a colon (:). In some cases the compiler will expect you to explicitly cast types, for which multiple ways are available:
type conversion, whose safety checked by the compiler
import strformat var x = int(1.0 / 3) # type conversion echo &"x is {$type(x)} and its value is {x}."
x is int and its value is 0.
annotating the variable type. In the below example,
y
is an empty seq and it needs a type specification.import strformat var y: seq[int] = @[] # empty seq needs type specification echo &"y is {$type(y)} and its value is {y}."
y is seq[int] and its value is @[].
Failing to specify the type for empty sequences will give an error like:
nim_src_ofNFSG.nim(5, 9) Error: cannot infer the type of the sequence
the
cast
keyword, which is unsafe and should be used only where you know what you are doing, such as in interfacing with Cvar z = "Foobar" proc ffi(foo: ptr array[6, char]) = echo repr(foo) let zAddr = addr z[0] zAddrCastedToPtr = cast[ptr array[6, char]](zAddr) ffi(zAddrCastedToPtr)
ptr 0x7f94ee9ac058 --> ['F', 'o', 'o', 'b', 'a', 'r']
DONE If, Else, While #
Nim has many different control flow constructs, including the standard
if
, else
, and while
. For “else if”, Nim uses elif
.
- When inside a loop,
continue
can be used to skip the rest of the loop body. - To begin the next iteration,
break
can be used to immediately leave the loop body.
import std/[strutils, random]
randomize()
let answer = rand(9) + 1 # random value in the range [1,10]
while true:
echo "I have a number from 1 to 10, what is it? "
let guess = parseInt(stdin.readLine)
if guess < answer:
echo "Too low, try again"
elif guess > answer:
echo "Too high, try again"
else:
echo "Correct!"
break
Labeled block
statement #
Along with its other uses, the block
statement can be used to create
a label so that it’s possible to break out of nested loops.
echo "I am outside"
block busyloops:
echo " I am in the busyloops block"
while true:
echo " I am in the first while"
while true:
echo " I am in the second while"
break busyloops
echo " I am in the first while again"
echo " I am in the outside the first while"
echo "I am outside again"
I am outside
I am in the busyloops block
I am in the first while
I am in the second while
I am outside again
If we wanted to use just break
instead of break BLOCK_LABEL
, we
would have need to do this:
echo "I am outside"
block busyloops:
echo " I am in the busyloops block"
while true:
echo " I am in the first while"
while true:
echo " I am in the second while"
break
echo " I am in the first while again"
break
echo " I am in the outside the first while"
echo "I am outside again"
I am outside
I am in the busyloops block
I am in the first while
I am in the second while
I am in the first while again
I am in the outside the first while
I am outside again
DONE Case Statements #
Nim also supports case statements, which are like switches in other languages.
You can use strings in the switch statement.
proc abc(str: string) = case str of "alfa": echo "A" of "bravo": echo "B" of "charlie": echo "C" else: echo "Unrecognized letter" abc("alfa") abc("bravo") abc("charlie") abc("delta")
A B C Unrecognized letter
Sets and ranges of ordinal types are also usable.
proc vowel_consonant(c: char) = case c of 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U': echo "Vowel" of 'b' .. 'd', 'f' .. 'h', 'j' .. 'n', 'p' .. 't', 'v' .. 'z', 'B' .. 'D', 'F' .. 'H', 'J' .. 'N', 'P' .. 'T', 'V' .. 'Z': echo "Consonant" else: echo "Unknown" vowel_consonant('A') vowel_consonant('c') vowel_consonant('^')
Vowel Consonant Unknown
case
statements, like most things, are actually expressions.It is required that the
of
statements cover every possible value for the case expression. If theof
statements fail to do this, you need to include theelse:
condition as an umbrella to cover all those remaining cases.Compiling below will give an error:
var b = true case b of true: echo "true"
nim_src_cCE560.nim(5, 1) Error: not all cases are covered
But the below works (
of
statements cover all cases):var b = true case b of true: echo "true" of false: echo "false"
true
And so does this (using
else
to cover remaining cases if any):var b = true case b of true: echo "true" else: echo "false"
true
Below is another example where the
else
is not needed, because the case statement covers all the possible values off
(justfooBar
in this example).type Foo = enum fooBar let f: Foo = fooBar case f of fooBar: echo "fooBar"
fooBar
FIXED Incorrect overflow/underflow error #
These issues got fixed in Nim PR #11652, thanks to @krux02 from GitHub.
Below snippet used to work in Nim 0.19.0 and older, but started failing with below error in Nim 0.20.0 and newer –
.Even though I understand that Nim 0.20.0 has overflow checked enabled
by default, it doesn’t make sense why a low(int)
and high(int)
value should trigger that error.
proc positiveOrNegative(num: int): string =
result = case num
of low(int) .. -1:
"negative"
of 0:
"zero"
of 1 .. high(int):
"positive"
echo positiveOrNegative(-1)
echo positiveOrNegative(10000000)
echo positiveOrNegative(0)
negative
positive
zero
Loose case
syntax #
For historic reasons, the colon (:
) at the end of the case
statement is optional, and requirement to indent the following of
statements is relaxed too.
So while all of the below styles compile, as per @dom96 from GitHub, the first style below is the “one-true-way” (ref).
No colon, no indented
of
case true of true: echo "yes" else: echo "no"
yes
With colon, with indented
of
Personally I like this style as it is consistent with
if
,while
, etc. But I will stick with the above officially preferred style everywhere for consistency.case true: of true: echo "yes" else: echo "no"
yes
No colon, with indented
of
case false of true: echo "yes" else: echo "no"
no
With colon, no indented
of
case false: of true: echo "yes" else: echo "no"
no
“Else If” inside Case Expression #
Case Expression has an interesting example of use of elif
as one of
the case
statement clauses. Below code is an adaption of that snippet.
import std/[strutils]
proc likes(animal: string): string =
let
food = case animal
of "dog": "bones"
of "cat": "mice"
elif animal.endsWith"whale": "plankton"
else: "ice cream"
result = animal & " likes " & food
echo "dog".likes
echo "cat".likes
echo "blue whale".likes
echo "sperm whale".likes
echo "me".likes
dog likes bones
cat likes mice
blue whale likes plankton
sperm whale likes plankton
me likes ice cream
DONE For Loops & Iterators #
Nim has first class iterators and syntax to use them — for
loops. The continue
and break
keywords also work inside for
loops.
items
and pairs
#
There are two kinds of iterator, and two special methods that
for
loops work with — items
and pairs
.
When iterating over an object with one item, Nim will call an iterator
called items
with the first parameter the type you want to iterate
over.
The same thing happens when iterating with two items, but in that
case, the pairs
iterator is called.
type
AlphaRange = object
low: int
high: int
let
foo = AlphaRange(low: 7, high: 11)
# Define single value yielding iterator for type AlphaRange
iterator items(ar: AlphaRange): int =
var i = ar.low
while i <= ar.high:
yield i
inc i
for n in foo: # uses AlphaRange.items
echo n
# Define pair value yielding iterator for type AlphaRange
iterator pairs(ar: AlphaRange): (int, char) =
for i in ar: # uses AlphaRange.items
yield (i, char(i + ord('a') - 1))
for i, c in foo: # uses AlphaRange.pairs
echo i, " ", c
7
8
9
10
11
7 g
8 h
9 i
10 j
11 k
A pairs
iterator returns a 2-field tuple.
Operators #
Iterators can also be operators in the standard way, with the name enclosed in backticks.
For example, the standard library defines the slice iterator ..
,
which allows iterating through ordinal types. Below, the same iterator
is mimicked using a different name ...
(one more dot) to avoid
conflict.
Always use a space before and after the iterator operators! See
section Always use spaces before and after ..
and ..<
.
iterator `...`[T](a: T, b: T): T =
var res: T = a
# var res: T = T(a) # same as above, but a is explicitly type-casted with T.
while res <= b:
yield res
inc res
for i in 0 ... 5:
echo i
0
1
2
3
4
5
Just for fun, below an iterator with .++
operator is created that
increments each output by 2.
This example works until Nim stable v1.6.6 but is broken on Nim v1.7.1 (63cca93ea9) – gives the error Error: identifier expected, but got ‘8’. See Nim Issue #19806.
iterator `.++`[T](a: T, b: T): T =
## Increment each output by 2
var res: T = a
while res <= b:
yield res
inc res
inc res
for i in 0 .++ 8:
echo i
Slice iterators ..
and ..<
#
The ..
iterator includes the ending value in its output. So iterating
through 0 .. 3
will iterate through 0 → 1 → 2 → 3. Think of range
[a,b]
.
for i in 0 .. 3:
echo i
0
1
2
3
The ..<
iterator does not include that ending value. So iterating
through 0 ..< 3
will iterate through 0 → 1 → 2. Think of range
[a,b)
.
for i in 0 ..< 3:
echo i
0
1
2
Inline Iterators #
Inline iterators basically take the body of the for loop and inline it into the iterator. This means that they do not have any overhead from function calling, but if carelessly created may increase code size dramatically.
iterator countTo(n: int): int =
var i = 0
while i <= n:
yield i
inc i
for i in countTo(5):
echo i
0
1
2
3
4
5
Closure Iterators #
Closure iterators are procs that return iterators.
proc countTo(n: int): iterator(): int =
## Returns an iterator with return type int
return iterator(): int =
var i = 0
while i <= n:
yield i
inc i
- Closure iterators hold on to their state and can be resumed at any time.
- The
finished()
function can be used to check if there are any more elements available in the iterator. - However, raw iterator usage is unintuitive and difficult to get right.
proc countTo(n: int): iterator(): int =
## Returns an iterator with return type int
return iterator(): int =
var i = 0
while i <= n:
yield i
inc i
let countTo20 = countTo(20)
echo countTo20()
echo countTo20()
var output = ""
# Raw iterator usage:
while true:
# 1. grab an element
let next = countTo20()
# 2. If the iterator doesn't have any more elements, break out of
# this while loop.
if finished(countTo20):
break
# 3. Loop body goes here:
output.add($next & " ") # "$next" stringifies "next"
echo output
0
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Here the same countTo
closure iterator is used to generate an
iterator with a different max value.
proc countTo(n: int): iterator(): int =
## Returns an iterator with return type int
return iterator(): int =
var i = 0
while i <= n:
yield i
inc i
var output = ""
let countTo9 = countTo(9)
for i in countTo9():
output.add($i)
echo output
0123456789
Closure Iterator: auto
return type #
As seen in Code Snippet 56, the return type of that
closure iterator is iterator(): int
. But that return type is already
defined in that closure in the return iterator(): int =
line.
The Nim auto
type can
be used to help to prevent that repetition. Here’s the same snippet
rewritten using auto
return type:
import std/[strformat]
proc countTo(n: int): auto =
## Returns an iterator with return type int
return iterator(): int =
var i = 0
while i <= n:
yield i
inc i
echo &"countTo(10) is of type `{$type(countTo(10))}'"
countTo(10) is of type `iterator (): int{.closure, noSideEffect, gcsafe, locks: 0.}'
Counting up and down #
For counting up by increments of 1, you would normally use, what I call, the dot-dot iterator.
for i in 0 .. 3:
echo i
0
1
2
3
countUp
iterator #
The same thing can be done using the
countUp
iterator too:
for i in countUp(0, 3):
echo i
0
1
2
3
This iterator, though, also allows setting the increment step (which defaults to 1):
for i in countUp(0, 3, 2):
echo i
0
2
countDown
iterator #
In the similar vein, there’s a
countDown
iterator for counting down too, which also has a decrement step
parameter that defaults to 1.
for i in countDown(3, 0):
echo i
echo ""
for i in countDown(3, 0, 2):
echo i
3
2
1
0
3
1
DONE Procs #
Procedures in Nim are declared using proc
and require their
parameter and return types be annotated. Though, that type annotation
is not needed if the parameters are assigned default values in the
proc signature itself.
After the types and parameters, an =
is used to denote the start of
the function body.
Some terminology — For a proc with a signature proc foo(a: int) = {..}
, and its call foo(100)
, a
is called a parameter, and 100 is
an argument to foo
. (Ref: Nim Tutorial – Procedures)
Semicolons and type propagation #
Semicolons are used in a procedure’s signature to separate and group parameters of the same type together. This also provides visual distinction as to where a parameter type changes.
Below foo1
and foo2
are functionally the exact same – the int
type propagates to b
and then a
, and the bool
type propagates
to d
and then c
.
# Using only commas
proc foo1(a, b: int, c, d: bool) =
echo a, b, c, d
foo1(1, 2, true, false)
# Using semicolon for visual distinction
proc foo2(a, b: int; c, d: bool) =
echo a, b, c, d
foo2(1, 2, true, false)
12truefalse
12truefalse
But incorrect use of semicolons will result in error. Below will leave
the a
parameter untyped as that “;” stopped the “int” type propagation.
proc foo3(a; b: int; c, d: bool) =
echo a, b, c, d
foo3(1, 2, true, false)
nim_src_KzhwQV.nim(4, 11) Error: typeless parameters are obsolete
See Procedures for more.
UFCS / Method Call Syntax #
Nim procedures have Uniform Function Call Syntax (UFCS), which means
that they can be called using either foo(a, b)
or a.foo(b)
.
proc fibonacci(n: int): int =
if n < 2:
result = n
else:
result = fibonacci(n - 1) + (n - 2).fibonacci
UFCS is documented in the Nim Manual as Method Call Syntax.
Command Invocation Syntax #
The Nim Command Invocation Syntax allows routines like procs to be
invoked without the ()
.
This command invocation syntax also works for expressions, but then
only a single argument may follow. This restriction means echo f 1, f 2
is parsed as echo(f(1), f(2))
and not as echo(f(1, f(2)))
.
proc oneMandArg(x: int, y: int = 0): int = x + y
proc singleArg(x: int): int = 20*x
echo oneMandArg 1, " ", singleArg 2 # prints "1 40"
1 40
Based on how the command invocation syntax was used in the above
example when calling oneMandArg
, that example runs fine — The
compiler saw that the first argument to oneMandArg
1
(an int)
was valid, but the second argument (optional) was expected to be an
int too, but instead it sees " "
(a string). So the compiler
silently uses the default value 0
of the optional second argument,
and passes the " "
to echo
.
But if you try to run the below, you get a cryptic Error: invalid indentation:
proc oneMandArg(x: int, y: int = 0): int = x + y
let fail = oneMandArg 1, oneMandArg 8 # Wrong. Too many arguments for a command call
To fix that, the method call syntax (UFCS) may be used to provide one more argument in this case:
proc oneMandArg(x: int, y: int = 0): int = x + y
let x = oneMandArg(1, oneMandArg 8) # traditional procedure call with 2 arguments
let y = 1.oneMandArg oneMandArg 8 # same thing as above
echo x, " ", y
9 9
Above, method call syntax (UFCS) + command invocation syntax is used for the first invocation of oneMandArg, and only command invocation syntax is used for the second.
The command invocation syntax also cannot have complex expressions as
arguments. For example: (anonymous procs), if
, case
or
try
. The (do notation) is limited, but usable for a single proc (see
the example that section).
Function calls with no arguments still need ()
to distinguish
between a call and the function itself as a first class value.
import std/[strformat]
proc foo(): string = "hello"
echo &"`foo' is of type `{$type(foo)}'"
echo &"`foo()` (return value of that) is of type `{$type(foo())}'"
`foo' is of type `proc (): string{.noSideEffect, gcsafe, locks: 0.}'
`foo()` (return value of that) is of type `string'
Exporting Symbols #
Encapsulation (A language mechanism for restricting direct access to
some of the object’s components.) is also supported, by annotating a
procedure with *
, which exports it and makes it available for use by
modules.
If you have a module1.nim as below:
proc foo*(): int = 2
proc bar(): int = 3
And then if you have a module2.nim (in the same directory as module1.nim) as below:
import module1
echo foo() # Valid
echo bar() # will not compile
it will fail compilation with this error:
module2.nim(3, 6) Error: undeclared identifier: 'bar'
That’s because foo
got exported from module1 because of foo*
,
but bar
didn’t (because of the lack of *
).
Side effect analysis #
Nim provides support for functional programming and so includes the
{.noSideEffect.}
pragma, which statically ensures there are no side
effects.
Below will compile fine as it has no side effects:
proc sum(x, y: int): int {. noSideEffect .} =
x + y
But below will fail:
proc minus(x, y: int): int {. noSideEffect .} =
echo x, " ", y
x - y
discard minus(4, 2)
with the error:
nim_src_8RNhaJ.nim(4, 6) Error: 'minus' can have side effects
DONE Func #
The func
keyword is a shortcut for a proc
with {. noSideEffect .}
.
So Code Snippet 58 can be rewritten as:
func sum(x, y: int): int =
x + y
Similarly, rewriting Code Snippet 59 with func
will
give the same error:
func minus(x, y: int): int =
echo x, " ", y
x - y
discard minus(4, 2)
No side effect debug #
We see in Code Snippet 59 and Code Snippet 61 that we
cannot use echo
as side effects are not allowed in those.
But there is a way to still use echo statements in no-side-effect
procs (aka funcs) –
debugEcho
.
Same as
echo
, but as a special semantic rule,debugEcho
pretends to be free of side effects, so that it can be used for debugging routines marked as noSideEffect.
So by simply replacing echo
with debugEcho
, Code Snippet 61 will
compile (and so will Code Snippet 59):
func minus(x, y: int): int =
debugEcho x, " ", y
x - y
discard minus(4, 2)
4 2
Operators #
To create an operator, the symbols that are to be used must be encased
inside backquotes (`
) to signify that they are operators.
proc `$`(twoDArray: array[2, array[2, int]]): string =
result = ""
for arr_elem in twoDArray:
for elem in arr_elem:
result.add($elem & ", ")
result.add("\n")
echo([[1, 2], [3, 4]])
1, 2,
3, 4,
See Varargs for how echo
works in the above snippet.
And you even go crazy with operator names..
proc `^&*^@%`(a, b: string): string =
## A confusingly named useless operator
result = a[0] & b[high(b)]
echo "foo" ^&*^@% "bar"
fr
Generic Functions (Generics) #
From Nim Tutorial 2:
Generics are Nim’s means to parametrize procs, iterators or types with type parameters. They are most useful for efficient type safe containers.
I think of generics as parameterized classes in SystemVerilog.
Below is a badly implemented example. While "a" * 10
works, 10 * "a"
will fail compilation. But the example gives a good idea of what
a generic function looks like.
proc `+`(a, b: string): string =
a & b
proc `*`[T](a: T, b: int): T =
result = ""
for i in 0 .. b-1:
result = result + a # calls `+` proc for strings defined above
echo("a" * 10) # This works.
# echo(10 * "a") # But this won't.
aaaaaaaaaa
Forward Declaration #
To forward-declare a Nim proc
, simply type the proc
signature
without the =
at the end.
proc foo(x: string): int =
x.len
echo foo("abc")
3
Below code snippet is the same as above, but with forward declaration.
proc foo(x: string): int # forward declaration
echo foo("abc")
proc foo(x: string): int =
x.len
3
DONE First Class Functions #
From MDN web docs:
A programming language is said to have First-class functions when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.
import std/[sequtils] # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]
proc greaterThan4(x: int): bool =
x > 4
echo powersOfTwo.filter(greaterThan4)
@[8, 16, 32]
Closures #
From Closures:
Procedures can appear at the top level in a module as well as inside other scopes, in which case they are called nested procs.
A nested proc can access local variables from its enclosing scope and if it does so it becomes a closure. Any captured variables are stored in a hidden additional argument to the closure (its environment) and they are accessed by reference by both the closure and its enclosing scope (i.e. any modifications made to them are visible in both places). The closure environment may be allocated on the heap or on the stack if the compiler determines that this would be safe.
Two different syntaxes are available for closures:
- Anonymous procedures
- Do notation
Anonymous procedures #
These use the proc
syntax identical to regular procedure syntax, but
without the procedure name.
I think of anonymous procedures as the bare lambdas in Emacs Lisp.
(cl-remove-if-not (lambda (x)
(> x 4))
'(1 2 4 8 16 32))
(8 16 32)
import std/[sequtils] # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]
# Using procname(arg1, arg2) syntax
echo filter(powersOfTwo, proc (x: int): bool = x > 4)
# Using UFCS (Method Call Syntax): arg1.procname(arg2)
echo powersOfTwo.filter(proc (x: int): bool = x > 4)
@[8, 16, 32]
@[8, 16, 32]
Also see Lambdas (=>
).
Anonymous procs in Generics #
If you need to define an anon proc taking a generic type
(typically represented using T
in the signature), use auto
for
type specification in the anon proc definition.
Thanks to @zah from GitHub for this tip. – ref
proc foo[T](input: T; op: proc(x: T): string): string =
op(input)
echo foo(100, proc (x: auto): string = $x) # Use `auto` where there was `T` in the signature
echo foo("abc", proc (x: auto): string = $x)
100
abc
Do notation #
Experimental feature
- Ref
- Do notation from manual
As a special more convenient notation, proc expressions involved in
procedure calls can use the do
keyword.
Below example is a re-written version of Code Snippet 65 using
the do
notation:
import std/[sequtils] # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]
echo(powersOfTwo.filter do (x: int) -> bool: x > 4)
# Below two do the same thing as above.
echo(powersOfTwo.filter() do (x: int) -> bool: x > 4)
echo(powersOfTwo.filter() do (x: int) -> bool:
x > 4)
@[8, 16, 32]
@[8, 16, 32]
@[8, 16, 32]
With a proc have a signature like proc foo[T](op: proc (x: T): string): string =
, here’s now an example anon proc would look in the
anon proc style vs do
notation:
- Anon Procs
foo(100, proc (x: auto): string = $x)
or100.foo(proc (x: auto): string = $x)
- Do Notation
foo(100) do (x: auto) -> string: $x
or100.foo() do (x: auto) -> string: $x
. Notice how thefoo
call looks as if it has only one parameter.
Below example is from Nim by Examples, but with a different value for
cities
. It sorts the city names from shortest names to the longest.
import std/[algorithm] # for sort
var cities = @["Frankfurt", "Ahmedabad", "Tokyo", "New York", "Kyiv", "Diu"]
sort(cities) do (x, y: string) -> int:
cmp(x.len, y.len)
echo cities
@["Diu", "Kyiv", "Tokyo", "New York", "Frankfurt", "Ahmedabad"]
(See the documentation of sort
in
algorithm module for more info.)
While it works, I noticed that the sorting didn’t work like I wanted when the string lengths were the same.. I expected “Ahmedabad” to come before “Frankfurt”.
So here’s a slight tweak to the above sort algorithm to fix that:
import std/[algorithm] # for sort
var cities = @["Frankfurt", "Ahmedabad", "Tokyo", "New York", "Kyiv", "Diu"]
sort(cities) do (x, y: string) -> int:
result = cmp(x.len, y.len)
if result == 0 and x.len > 0:
result = cmp(x[0], y[0])
echo cities
@["Diu", "Kyiv", "Tokyo", "New York", "Ahmedabad", "Frankfurt"]
Below example from the sort
documentation sorts the names by
surnames first. If the surnames match exactly, they are then
sorted by names.
import std/[algorithm] # for sort
type Person = tuple[name: string, surname: string]
var people: seq[Person] = @[("Mike", "Smith"), ("Dave", "Smith"), ("Thomas", "Edison")]
people.sort do (x, y: Person) -> int:
result = cmp(x.surname, y.surname)
if result == 0:
result = cmp(x.name, y.name)
echo people
@[(name: "Thomas", surname: "Edison"), (name: "Dave", surname: "Smith"), (name: "Mike", surname: "Smith")]
import std/[sequtils] # for map
var cities = @["Frankfurt", "Tokyo", "New York", "Kyiv"]
cities = cities.map do (x: string) -> string:
"City of " & x
echo cities
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
Summary of anonymous procs and do notation #
Below example runs the same map
proc using anonymous procs, and do
notations, with and without the Command Invocation Syntax (optional
parentheses).
import std/[sequtils] # for map
var cities1, cities2, cities3, cities4 = @["Frankfurt", "Tokyo", "New York", "Kyiv"]
# Anonymous procs
echo map(cities1, proc (x: string): string = "City of " & x)
echo cities2.map(proc (x: string): string = "City of " & x)
# Do notation
echo map(cities3) do (x: string) -> string: "City of " & x
echo cities4.map do (x: string) -> string: "City of " & x
echo cities4.map() do (x: string) -> string: "City of " & x
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
do
is written after the parentheses enclosing the regular proc
params. The proc expression represented by the do
block is appended
to them.
In calls using the command syntax, the do
block will bind to the
immediately preceeding expression, transforming it in a call.
DONE Blocks #
Blocks can be introduced in two different ways:
- by indenting statements
- with ()s.
Blocks using indented statements #
The first way is to use indenting, e.g. using if
-elif
-else
,
while
, for
statements, or the block
statement.
if true:
echo "Nim is great!"
while false:
echo "This line is never output!"
block:
echo "This line, on the other hand, is always output"
Nim is great!
This line, on the other hand, is always output
The block statement can also be labeled, making it useful for breaking out of loops.
Block statements are useful for general scoping too.
import std/[strformat]
let b = 3
echo &"Outside block: b is of type {$type(b)} of value {b}."
block: # one block
let b = "4" # shadowing is probably a dumb idea
echo &" Inside an unlabeled block: b is of type {$type(b)} of value {b}."
block: # another block
let b = 1.234 # shadowing is probably a dumb idea
echo &" Inside another unlabeled block: b is of type {$type(b)} of value {b}."
block foo: # yet another block, this one labeled
let b = [1, 2, 3, 4] # shadowing is probably a dumb idea
echo &" Inside 'foo' block: b is of type {$type(b)} of value {b}."
echo &"Outside block: Once again, b is of type {$type(b)} of value {b}."
Outside block: b is of type int of value 3.
Inside an unlabeled block: b is of type string of value 4.
Inside another unlabeled block: b is of type float64 of value 1.234.
Inside 'foo' block: b is of type array[0..3, int] of value [1, 2, 3, 4].
Outside block: Once again, b is of type int of value 3.
Blocks using parentheses #
Parentheses can be used as an expression, but they do not provide the end of statement inference. So it is necessary to place semicolons yourself.
An interesting and unexpected side effect of this syntax is that Nim is suitable even for die-hard brace purists!
While possible, it doesn’t mean it’s a good idea. Most Nim code does not use parentheses in that way, and it would not be seen as idiomatic.
proc square(inSeq: seq[float]): seq[float] = (
result = newSeq[float](len(inSeq));
for i, v in inSeq: (
result[i] = v*v;
)
)
echo @[2.0, 3, 4].square
@[4.0, 9.0, 16.0]
DONE Primitive Types #
Nim has several primitive types:
- signed integers
int8
,int16
,int32
,int64
, andint
, whereint
is the same size as a pointer (seeint
types for more)- unsigned integers
- similar, but unsigned with “u” prepended to the type
- floating point numbers
float32
,float64
, andfloat
(Note thatfloat
andfloat64
are the same, they both refer to the internalFloat
type.)- characters/bytes
char
andbyte
, which are basically an alias foruint8
To indicate the size of an integer literal, append “u” or “i” and the
size you’d like to the end. Example: 5'u8
. However, usually this is
not necessary.. unless you are dealing with two’s complement literals.
Integers can also have 0[xX]
, 0o
, 0[bB]
prepended to indicate a
hex, octal, or binary literal, respectively. Examples: 0xABCD
,
0o567
, 0b1010
.
Underscores are also valid in literals, and can help with readability.
import std/[strformat]
let
a: int8 = 0x7F
b: uint8 = 0b1111_1111
c = 0xFF # type is int
d = 0o67 # value is written as an octal, type is int
e: byte = 62
# f: uint8 = 256 # Compile time error
echo &"Value of {$type(a)} a is {a}"
echo &"Value of {$type(b)} b is {b}"
echo &"Value of {$type(c)} c is {c}"
echo &"Value of {$type(d)} d is {d}"
echo &"Value of {$type(e)} e is {e}"
Value of int8 a is 127
Value of uint8 b is 255
Value of int c is 255
Value of int d is 55
Value of byte e is 62
Signed binary representation / Two’s complement #
What is a two’s complement?
Below will give compilation error:
let minusOne: int8 = 0b1111_1111
echo minusOne
nim_src_Kpkxfl.nim(4, 22) Error: type mismatch: got <int literal(255)> but expected 'int8'
Don’t do that! Instead, do:
let
minusOne = 0b1111_1111'i8
echo minusOne
-1
Explicitly write two’s complement notations with the intended type,
like 0b1010_1010'i8
or 0xFFFF'i16
.
In Code Snippet 67, Nim simply converts binary “1111_1111” to 255 and throws the above error as that obviously cannot be stored as an 8-bit signed integer.
Whereas in Code Snippet 68, Nim directly stores binary “1111_1111” as signed 8-bit integer and auto-recognizes that as a two’s complement and evaluates that as decimal “-1”.
Integer Operators #
Instead of ^
, &
, |
, >>
, <<
in most other languages, the xor
,
and
, or
, shr
, shl
operators are used, respectively.
import std/[strformat]
let
a: int = 0b1011_1010
b: int = 0b0101_0011
echo &"{a:#010b} ^ {b:#010b} = {(a xor b):#010b}"
echo &"{a:#010b} & {b:#010b} = {(a and b):#010b}"
echo &"{a:#010b} | {b:#010b} = {(a or b):#010b}"
echo &"{a:#010b} >> 3 = {(a shr 3):#b}"
echo &"{a:#010b} << 5 = {(a shl 5):#b}"
0b10111010 ^ 0b01010011 = 0b11101001
0b10111010 & 0b01010011 = 0b00010010
0b10111010 | 0b01010011 = 0b11111011
0b10111010 >> 3 = 0b10111
0b10111010 << 5 = 0b1011101000000
DONE Type Aliases #
Types are declared inside type
sections, where multiple types can be
declared.
Note that aliased types are the same as the original types, and so you can have expressions with a mix of original and aliased types.
If type safety is desired, distinct types should be used.
type
MyInteger = int
let a: int = 2
echo a + MyInteger(4)
6
DONE Object Types #
In Nim, objects are like structs from C family languages and define a grouping of fields. They are by default traced by the garbage collector, so there is no need to explicitly free them when allocated.
type
Animal = object
name, species: string
age: int
proc sleep(a: var Animal) =
a.age += 1
proc dead(a: Animal): bool =
result = a.age > 20
var carl = Animal(name: "Carl",
species: "L. glama",
age: 12)
let joe = Animal(name: "Joe",
species: "H. sapiens",
age: 23)
assert(not carl.dead)
for i in 0 .. 10:
carl.sleep()
assert carl.dead
Object types are declared in a type
section, as usual. They can be
exported, and individual fields can also be exported.
type
Animal* = object # Export Animal object type
name*, species*: string # Export only name and species fields
age: int
Fields can be safely exported without violating encapsulation because call syntax is equivalent between them.
Initially, carl
is created on the stack and initialized to zeros, so
its value is [name = nil, species = nil, age = 0]
. It is mutable
(because declared using var
), so that means that the contents of
carl
can be changed. This also means it can be passed to functions
that require a variable parameter, like sleep()
, which can modify
its value.
joe
is also created on the stack, but its contents are immutable
(because declared using let
) and can not be changed. Attempting to
do so, say through joe.age = 57
, will fail with an error at compile
time.
Object and References #
type
Animal = object
name, species: string
age: int
proc sleep(a: var Animal) =
a.age += 1
let mittens: ref Animal = new(Animal)
# Everything initialized to 0 or nil
echo mittens[]
# Initialize all the fields
mittens[].name = "Mittens"
# When accessing fields of refs, the dereferencing operator [] is
# optional.
mittens.species = "P. leo"
mittens.age = 6
echo mittens[]
mittens[].sleep()
echo mittens[]
(name: "", species: "", age: 0)
(name: "Mittens", species: "P. leo", age: 6)
(name: "Mittens", species: "P. leo", age: 7)
mittens
is a reference to an object allocated on the heap (See
Heap and Stack). So the value of mittens
cannot be changed.
While mittens
can never point to anything else, the value that
mittens
is pointing at can be changed as shown above. The fields in
the Animal
object referenced by mittens
are changed from the
default initialization value of zeros.
As the referenced object (mittens[]
) is mutable, that can be passed
to functions like sleep
that require a variable parameter.
Below shows a more concise way of initializing reference types — by
creating a type alias AnimalRef
for ref Animal
:
import std/[strformat]
type
Animal = object
name, species: string
age: int
AnimalRef = ref Animal
proc sleep(a: var Animal) =
a.age += 1
var spot = AnimalRef(name: "Spot",
species: "C. lupus",
age: 1)
echo &"spot = {spot.repr} (type {$type(spot)})"
echo &"spot[] = {spot[]} (type {$type(spot[])})"
spot[].sleep()
echo spot[]
spot = ref 0x7f12f8735050 --> [name = 0x7f12f8735090"Spot",
species = 0x7f12f87350c0"C. lupus",
age = 1] (type AnimalRef)
spot[] = (name: "Spot", species: "C. lupus", age: 1) (type Animal)
(name: "Spot", species: "C. lupus", age: 2)
In many cases it is only wanted to have the object be a reference type
to begin with, which is possible by declaring it as a ref object
.
type
Thing = ref object
positionX, positionY: int
new
keyword #
Try not to use the new
keyword to initialize objects.
From this comment by Araq from Nim Forum:
Always prefer the syntax
student = Student(name: "Anton", age: 5, id: 2)
overnew
.
new
makes it much harder for the compiler to prove a complete initialization is done. The optimizer will also soon take advantage of this fact.
More #
Tuples vs Objects vs Ref Objects?
Thanks to yglukhov from Nim Forum for this reply:
The main conceptual difference between
smth
andref smth
is sharing. You can have several locations pointing toref smth
and if you changesmth
from one location the change will be visible through another location. That’s not the case with non -ref smth
. Soref object
is not the default – you have to consciously chose it depending on your design. Also inheritable/polymorphic hierarchies are mostly useful when they areref object
.Size of the objects (and whether it fits on stack) almost never has to be considered in reality.
Under the hood,
ref smth
implies heap allocation and an extra access indirection. Heap allocation is more expensive than stack allocation, so it might matter in performance critical code paths. But then again,refs
are generally faster to assign to each other, as they take up a single machine word.Tuples are almost like objects. Tuple fields are always public. Tuples can be anonymous, and they can’t be inherited. Generally they are used to represent some short living values that don’t deserve their own named type.
DONE Enum Types #
Enums in Nim are type-checked.
type
CompassDirections = enum
cdNorth, cdEast, cdSouth, cdWest
Signals = enum # enum with holes
sigQuit = 3, # these commas are optional
sigAbort = 6,
sigKill = 9
Colors {.pure.} = enum
Red = "FF0000" # no commas here
Green = (1, "00FF00")
Blue = "0000FF"
var
dir: CompassDirections = cdSouth
sig: Signals = sigQuit
colRed = Colors.Red # qualified enum value reference
colGreen = Colors.Green # qualified enum value reference
echo dir
echo sig
echo colRed
echo colGreen
cdSouth
sigQuit
FF0000
00FF00
Notice that each element in CompassDirections
is prepended with cd
to avoid name conflicts since references to the enum value do not need
to be qualified.
Enums can be given custom values as shown by Signals
, or even given
stringified values, as shown by Colors
. Enums with
non-incrementing and non-continuous values like in this Signals
example are called “enums with holes”.
The {.pure.}
pragma that Colors
has, requires that all references
to Colors
’s values be qualified, therefore making a prefix
unnecessary. (Note a bug on Nim devel with {.pure.}
enums –
Nim Issue #8066).
Ordinals #
Ordinals have
low
,
high
,
pred
,
succ
,
dec
,
inc
and
ord
methods defined, where:
low
- gives the lowest possible value
high
- gives the highest possible value
pred
- gives the ordinal value that’s n “steps” away from the current value in decreasing order (predecessor)
succ
- gives the ordinal value that’s n “steps” away from the current value in increasing order (successor)
dec
- decrements the ordinal by n “steps”
inc
- increments the ordinal by n “steps”
ord
- gives the integer value of the ordinal
import std/[strformat]
var
someOrdinal = 'A' # char is an ordinal
echo &"someOrdinal ({$type(someOrdinal)}) = {someOrdinal}"
echo &"low value of someOrdinal: {repr(someOrdinal.low)}"
echo &"high value of someOrdinal: {repr(someOrdinal.high)}"
echo &"value of someOrdinal 1 step away in decrementing order: {someOrdinal.pred} (curr value: {someOrdinal})"
echo &"value of someOrdinal 3 steps away in decrementing order: {someOrdinal.pred(3)} (curr value: {someOrdinal})"
echo &"value of someOrdinal 1 step away in incrementing order: {someOrdinal.succ} (curr value: {someOrdinal})"
echo &"value of someOrdinal 3 steps away in incrementing order: {someOrdinal.succ(3)} (curr value: {someOrdinal})"
inc someOrdinal
echo &"someOrdinal after incrementing once: {someOrdinal}"
someOrdinal.inc(2)
echo &"someOrdinal after incrementing twice: {someOrdinal}"
someOrdinal.dec
echo &"someOrdinal after decrementing once: {someOrdinal}"
dec(someOrdinal, 4)
echo &"someOrdinal after decrementing 4 times: {someOrdinal}"
echo &"value of someOrdinal: {someOrdinal}"
echo &"integer value of someOrdinal: {ord(someOrdinal)}"
someOrdinal (char) = A
low value of someOrdinal: '\0'
high value of someOrdinal: '\255'
value of someOrdinal 1 step away in decrementing order: @ (curr value: A)
value of someOrdinal 3 steps away in decrementing order: > (curr value: A)
value of someOrdinal 1 step away in incrementing order: B (curr value: A)
value of someOrdinal 3 steps away in incrementing order: D (curr value: A)
someOrdinal after incrementing once: B
someOrdinal after incrementing twice: D
someOrdinal after decrementing once: C
someOrdinal after decrementing 4 times: ?
value of someOrdinal: ?
integer value of someOrdinal: 63
Enums are ordinals too if their values are incrementing and continuous (incrementing by 1, no holes). So all of those methods for ordinals would work for such enums too.
import std/[strformat]
type
CompassDirections = enum
cdNorth, cdEast, cdSouth, cdWest
for direction in ord(low(CompassDirections)) .. ord(high(CompassDirections)):
echo &"{CompassDirections(direction)} ({$type(CompassDirections(direction))}), " &
&"ord: {direction} ({$type(direction)})"
let cDir = cdEast
echo cDir.pred # int arg defaults to 1
echo cDir.succ(2) # int arg defaults to 1
cdNorth (CompassDirections), ord: 0 (int)
cdEast (CompassDirections), ord: 1 (int)
cdSouth (CompassDirections), ord: 2 (int)
cdWest (CompassDirections), ord: 3 (int)
cdNorth
cdWest
CompassDirections(direction)
is a type conversion that gives the
CompassDirections
enum from the integer direction
.
It is possible to iterate through all possible values of ordinal
enums, either as shown above, or cdNorth .. cdWest
, which is
equivalent.
Enums with holes #
Enums can also have disjoint values i.e. their values do not have to be incrementing by 1 in succession — I call them “holey enums”.
Enums with holes should not be used for any other reason than compatibility with C because it breaks the idea that enums are ordinals!
The Signals
enum example in Code Snippet 69 is not an ordinal type
enum as its values are non-continuous. As per the
Nim Manual:
An explicit ordered enum can have holes.
However, it is then not an ordinal anymore, so it is not possible to use these enums as an index type for arrays. The procedures
inc
,dec
,succ
andpred
are not available for them either.
FIXED Ordinal methods on enums with holes #
Thanks to @LemonBoy from GitHub for fixing this in Nim PR #8264.
Now when trying to run the below snippet:
type
Signals = enum
sigQuit = 3, sigAbort = 6, sigKill = 9
var nonOrdinal = sigQuit
inc nonOrdinal
will give this error:
nim_src_xNvIoW.nim(9, 5) Error: type mismatch: got <Signals>
but expected one of:
proc inc[T: Ordinal | uint | uint64](x: var T; y = 1)
expression: inc nonOrdinal
- Older issue
- Contrary to the above quote from the Nim manual,
below shows that
inc
,dec
,succ
,pred
procedures do work with such “holey enums”; albeit printing “invalid data!” when those operations end up on holes – Nim Issue #8262, Nim Issue #1239.type Signals = enum sigQuit = 3, sigAbort = 6, sigKill = 9 var nonOrdinal = sigQuit echo " ", nonOrdinal echo "inc on holey enums" inc nonOrdinal echo " ", nonOrdinal inc nonOrdinal echo " ", nonOrdinal inc nonOrdinal echo " ", nonOrdinal inc nonOrdinal echo " ", nonOrdinal echo "dec on holey enums" dec nonOrdinal echo " ", nonOrdinal dec nonOrdinal echo " ", nonOrdinal echo "succ on holey enums" echo " ", nonOrdinal.succ echo " ", nonOrdinal.succ(2) echo "pred on holey enums" echo " ", nonOrdinal.pred
sigQuit inc on holey enums 4 (invalid data!) 5 (invalid data!) sigAbort 7 (invalid data!) dec on holey enums sigAbort 5 (invalid data!) succ on holey enums sigAbort 7 (invalid data!) pred on holey enums 4 (invalid data!)
DONE Distinct Types #
Distinct types are like type aliases, but they provide type safety so that it is impossible to coerce a distinct type into its base type without explicit conversion.
Below does not compile even if a
is being assigned a float value,
because the type of a
is a distinct type alias of float
. So
float
and Dollars
are technically distinct types even if they
are aliases.
type
Dollars = distinct float
var a = Dollars(20)
a = 25.0 # Doesn't compile
nim_src_YftC2H.nim(8, 3) Error: type mismatch: got <float64> but expected 'Dollars = distinct float'
But the below compiles:
type
Dollars = distinct float
var a = Dollars(20)
a = Dollars(25.0)
echo float(a)
25.0
Notice that in the above snippet, I used echo float(a)
. That’s
because echo a
will not work automatically!
None of the base type’s procedures will work for the distinct
type –
for example echo
, +
, *
. As Dollars
is a distinct
type, its
$
needs to be defined too!
So the below works:
type
Dollars = distinct float
proc `$`(d: Dollars): string =
$float(d) # convert Dollars to float and return stringified float
var a = Dollars(20)
a = Dollars(25.0)
echo a
25.0
borrow
pragma #
borrow
for procs #
If we do this, we will end up writing lots of thin wrappers for all
such commonly used procedures. So another way is to use the
{.borrow.}
pragma instead, which automates the generation of such
procedures borrowed from the base types.
import std/[strformat]
type
Dollars = distinct float
proc `$`(d: Dollars): string {.borrow.}
proc `*`(a, b: Dollars): Dollars {.borrow.}
proc `+`(a, b: Dollars): Dollars {.borrow.}
var a = Dollars(25.0)
echo &"a of type {$type(a)} = {a}"
a = 10.Dollars * (20.Dollars + 1.Dollars)
echo &"a of type {$type(a)} = {a}"
a of type Dollars = 25.0
a of type Dollars = 210.0
The above example failed to compile in Nim 0.19.0, but was soon fixed in 959e3a08 by @LemonBoy from GitHub.
borrow
for object fields #
When creating a distinct
type from an object type, none of its fields
are carried over. If the fields are wanted, they can be brought over
through an overloading of the {.borrow.}
pragma. If they are not
borrowed, they cannot be accessed.
Below example basically borrows the .
operator from the base type
Foo
so that field access for the derived distinct
type MyFoo
works too.
type
Foo = object
a: int
MyFoo {.borrow: `.`.} = distinct Foo
var value: MyFoo
value.a = 100
echo value.a
100
Without that “dot” borrow, you would get this error:
nim_src_PlBYmp.nim(10, 11) Error: undeclared field: 'a'
DONE Strings #
There are several types of string literals:
- Quoted Strings
- Created by wrapping the body in triple quotes
(
""" .. """
). They never interpret escape codes.echo """ <html> <head> </head>\n\n <body> </body> </html> """
<html> <head> </head>\n\n <body> </body> </html>
- Raw Strings
- Created by prefixing the string with an
r
. They do not interpret escape sequences either, except for""
, which is interpreted as"
. This means thatr"\b[a-z]\b"
is interpreted literally as\b[a-z]\b
.echo r".""." echo r"\b[a-z]\b"
.". \b[a-z]\b
- Proc Strings
- Same as raw strings, but the proc name is prefixed
directly to the string, so that
foo"12"
impliesfoo(r"12")
. Note that the proc has to be called asPROC"STRING"
i.e. no parentheses.proc foo(s: string): string = s echo foo".""." # echo foo("."".") # This will not work; will give compilation error. echo foo"\b[a-z]++\b"
.". \b[a-z]++\b
Strings are null-terminated, so that cstring("foo")
requires zero
copying. However, you should be careful that the lifetime of the
cstring does not exceed the lifetime of the string it is based upon.
Strings can also almost be thought of as seq[char]
with respect to
assignment semantics. See Seqs.
DONE Arrays #
This section has more examples for Nim arrays. See Arrays for more info.
The size of arrays in Nim has to be specified at compile-time and cannot be given or changed at runtime.
The size of the array is encoded in its type and cannot be accidentally lost. Therefore, a procedure taking an array of variable length must encode the length in its type parameters. Alternatively, such procedures can also use openArrray as the parameter type, which the array length does not need to be specified.
type
ThreeStringAddress = array[3, string]
let names: ThreeStringAddress = ["Jasmine", "Ktisztina", "Kristof"]
let addresses: ThreeStringAddress = ["101 Betburweg", "66 Bellion Drive", "194 Laarderweg"]
echo names
echo addresses
proc zip[I, T](a, b: array[I, T]):
array[I, tuple[a, b: T]] =
for i in low(a) .. high(a):
result[i] = (a[i], b[i])
let nameAndAddresses = names.zip(addresses)
echo nameAndAddresses
["Jasmine", "Ktisztina", "Kristof"]
["101 Betburweg", "66 Bellion Drive", "194 Laarderweg"]
[(a: "Jasmine", b: "101 Betburweg"), (a: "Ktisztina", b: "66 Bellion Drive"), (a: "Kristof", b: "194 Laarderweg")]
The first type parameter of an array is actually a range like 0 .. N-1
(just a value “3” as in the above example is syntactic sugar
for 0 .. 2
).
It’s also possible to use ordinal values to index an array, effectively creating a lookup table. See Arrays with enum as length for more.
Nested Array #
type
Matrix[W, H: static[int]] = array[1 .. W, array[1 .. H, int]]
let
mat1: Matrix[2, 2] = [[1, 0],
[0, 2]]
mat2: Matrix[2, 2] = [[0, 3],
[4, 0]]
proc `+`[W, H](a, b: Matrix[W, H]): Matrix[W, H] =
for i in 1 .. high(a):
for j in 1 .. high(a[0]):
result[i][j] = a[i][j] + b[i][j]
echo mat1 + mat2
[[1, 3], [4, 2]]
static[int]
in the above example is a static type. Thestatic[T]
construct is needed as the array size needs to be known at compile time. See Nim Manual –static{T}
for more.
DONE Seqs #
Sequences (Seqs for short) provide dynamically expandable storage.
- There are two ways to create sequences:
- with the
@
operator, and - with the
newSeq[T](n: int)
method
- with the
- Once a sequence is created, it can be modified using methods like
add(item: T)
anddelete(idx: int)
. - The length of a seq can be found through
len: int
, and the maximum index throughhigh: int
. - The standard
items: T
andpairs: tuple[i: int, v: T]
iterators are also available.
var
a = @[1, 2, 3]
b = newSeq[int](3)
echo a
for i, v in a:
b[i] = v*v
echo b
for i in 4 .. 15:
b.add(i * i)
echo b
b.delete(0) # takes O(n) time
echo b
b = a[0] & b # Same as original b
echo b
@[1, 2, 3]
@[1, 4, 9]
@[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
@[4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
@[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
Immutability #
Sequences are dynamically allocated (i.e. allocated on the heap, not
the stack), but they are immutable (like any other Nim variable)
unless marked as var
.
So the below will fail to compile as a
cannot be assigned to:
let a = @[1, 2, 3]
a.add(4)
However, the below will work without any problem:
var b = @[1, 2, 3]
b.add(4)
echo b
@[1, 2, 3, 4]
Sequences passed as “argument by value” are not modifiable. For example, the following will fail to compile:
proc doSomething(mySeq: seq[int]) =
mySeq[0] = 2 # Error: 'mySeq[0]' cannot be assigned to
Sequence arguments can be mutable if they are passed as “argument by
reference”, i.e. the parameter is annotated with var
or ref
:
proc foo(mySeq: var seq[int]) =
mySeq[9] = 999
var thisSeq = newSeq[int](10)
foo(thisSeq)
echo thisSeq
@[0, 0, 0, 0, 0, 0, 0, 0, 0, 999]
If the sequence needs to be passed as value, and not reference, you can first copy that sequence inside the proc and then modify:
proc doSomething(mySeq: seq[int]) =
var varMySeq = mySeq # copy the seq
varMySeq[0] = 999
echo "Inside the proc: varMySeq = ", varMySeq
var testSeq = @[1, 2, 3]
echo "Before the proc: testSeq = ", testSeq
doSomething(testSeq)
echo "After the proc: testSeq = ", testSeq
Before the proc: testSeq = @[1, 2, 3]
Inside the proc: varMySeq = @[999, 2, 3]
After the proc: testSeq = @[1, 2, 3]
Above, as the doSomething
proc is not returning the modified value
varMySeq
, the input argument sequence testSeq
remains unmodified.
More about sequences #
See Sequences.
DONE Bitsets (Set type) #
Nim comes with a built-in way to build a set of ordinal types.
In order for a type to be usable in a bitset, it must be an ordinal
and high(T)
< 216. For sets of non-ordinal types, see the
sets
module, which contains hashsets.
However, the best practice is to keep bitset size as small as possible since each possible element in the set consumes one bit, therefore a bitset of 216 elements will consume 64KiB.
Bitsets have all the useful operations of mathematical sets:
Syntax | Description | Example Code |
---|---|---|
A + B | union of sets A and B | {'a' .. 'm'} + {'n' .. 'z'} == {'a' .. 'z'} |
A * B | intersection of sets A and B | {'a' .. 'm'} * {'c' .. 'z'} == {'c' .. 'm'} |
A - B | difference of two sets (A with B’s elements) | {'a' .. 'z'} - {'b' .. 'd'} == {'a', 'e' .. 'z'} |
A == B | are both sets A and B equal? | {'a' .. 'c'} == {'a' .. 'c'} |
A <= B | is set A a subset of set B, or are both equal ? | {'a' .. 'c'} <= {'a' .. 'z'} |
A < B | is set A a strict subset of set B (i.e. A != B)? | {'b' .. 'c'} < {'a' .. 'z'} |
e in A | is e an element of set A? | 'd' in {'a' .. 'z'} |
contains(A, e) | same as above | contains({'a' .. 'z'}, 'd') |
card(A) | the cardinality of A (number of elements in A) | card({'a' .. 'z'}) |
incl(A, e) | same as A = A + {e} , but when using incl , A has to be a var | var foo = {'b' .. 'z'}; incl(foo, 'a'); foo == {'a' .. 'z'} |
excl(A, e) | same as A = A - {e} , but when using excl , A has to be a var | var foo = {'a' .. 'z'}; excl(foo, 'a'); foo == {'b' .. 'z'} |
echo "Union: ", {'a' .. 'd'} + {'e' .. 'h'}
echo "Intersection: ", {'a' .. 'd'} * {'c' .. 'z'}
echo "Difference: ", {'a' .. 'h'} - {'b' .. 'd'}
Union: {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}
Intersection: {'c', 'd'}
Difference: {'a', 'e', 'f', 'g', 'h'}
echo "Equal: ", {'a' .. 'c'} == {'a' .. 'c'}, " ", {'a' .. 'c'} == {'a' .. 'b'}
echo "Subset or Equal: ", {'a' .. 'c'} <= {'a' .. 'c'}, " ", {'a' .. 'b'} <= {'a' .. 'c'}, " ", {'a' .. 'd'} <= {'a' .. 'c'}
echo "Subset: ", {'a' .. 'c'} < {'a' .. 'c'}, " ", {'a' .. 'b'} < {'a' .. 'c'}, " ", {'a' .. 'd'} < {'a' .. 'c'}
Equal: true false
Subset or Equal: true true false
Subset: false true false
echo "in operator: ", 'd' in {'a' .. 'z'}, " ", '1' in {'a' .. 'z'}
echo "contains: ", contains({'a' .. 'z'}, 'd'), " ", contains({'a' .. 'z'}, '1')
echo "card: ", card({'a' .. 'z'})
var foo = {'b' .. 'd'}
echo "Before incl 'a': ", foo
incl(foo, 'a')
echo "After incl 'a': ", foo
echo "Before excl 'a': ", foo
excl(foo, 'a')
echo "After excl 'a': ", foo
in operator: true false
contains: true false
card: 26
Before incl 'a': {'b', 'c', 'd'}
After incl 'a': {'a', 'b', 'c', 'd'}
Before excl 'a': {'a', 'b', 'c', 'd'}
After excl 'a': {'b', 'c', 'd'}
Check if a character is in a character set (in
) #
in
is a binary operator.
echo "'d' is lower? ", 'd' in {'a' .. 'z'}
echo "'D' is lower? ", 'D' in {'a' .. 'z'}
echo "'D' is upper? ", 'D' in {'A' .. 'Z'}
'd' is lower? true
'D' is lower? false
'D' is upper? true
Check if a character is not in a character set (notin
) #
notin
is a binary operator.
echo "'d' is not lower? ", 'd' notin {'a' .. 'z'}
echo "'D' is not lower? ", 'D' notin {'a' .. 'z'}
echo "'D' is not upper? ", 'D' notin {'A' .. 'Z'}
echo "'.' is neither upper not lower? ", '.' notin {'A' .. 'Z', 'a' .. 'z'}
'd' is not lower? false
'D' is not lower? true
'D' is not upper? false
'.' is neither upper not lower? true
DONE Files #
Reading from a File #
Suppose we have a file nim_files/kittens.txt
relative to the current
directory with the following contents:
Spitfire
Vivian
Motor
We can use the readFile
proc to read the entire file into memory:
let
# This will read the entire file into the string entireFile
entireFile = readFile("nim_files/kittens.txt")
echo entireFile # prints the entire file
Spitfire
Vivian
Motor
We can also read the lines of a file by opening a File
object and
using the readLine
proc to read individual lines.
let
f = open("nim_files/kittens.txt")
firstLine = f.readLine()
echo firstLine # prints Spitfire
# Close the file object when you are done with it.
f.close()
Spitfire
Writing to a File #
We can write a string to a file using the writeFile
proc.
let
fileName = "nim_files/cats.txt"
text = "Cats are very cool!"
writeFile(fileName, text)
# Now read back that written file.
echo readFile(fileName)
Cats are very cool!
This will create a file on the system named nim_files/cats.txt
containing “Cats are very cool!”.
We can also write a file line by line using a File
object and the
writeLine
proc.
let
fileName = "nim_files/cat_activities.txt"
# The fmWrite constant specifies that we are opening the file for writing.
f = open(fileName, fmWrite)
lines_text = ["Play", "Eat", "Sleep"]
for line in lines_text:
f.writeLine(line)
f.close()
# Now read back that written file.
echo readFile(fileName)
Play
Eat
Sleep
TODO JSON #
DONE Varargs #
Standard varargs
simply allows you to pass multiple parameters to your
function.
proc printThings(things: varargs[string]) =
for thing in things:
echo thing
printThings "words", "to", "print"
words
to
print
The compiler converts the list of arguments to an array implicitly.
This transformation is only done if the varargs
parameter is the
last parameter in the procedure header.
However, trying to run:
printThings 1, "string", @[1, 2, 3]
will fail to compile because the compiler won’t coerce the non-string
arguments 1
and @[1, 2, 3]
into strings, and the printThings
proc needs the varargs to be strings.
Luckily enough, there is a tool to fix this; it is also possible to perform type conversions in this context:
proc printThings(things: varargs[string, `$`]) =
for thing in things:
echo thing
printThings "thing 1", 2, @[4, 5, 6]
thing 1
2
@[4, 5, 6]
In this example $
is applied to any argument that is passed to the
parameter things
. (Note that $
applied to strings is a nop.)
Note that an explicit array constructor passed to a varargs
parameter is not wrapped in another implicit array construction:
import std/[strformat]
proc takeV[T](a: varargs[T]) =
echo &"a (type: {$type(a)}) = {a}"
for elem in a:
echo elem
takeV([123, 2, 1])
a (type: varargs[int]) = [123, 2, 1]
123
2
1
Varargs Typed #
varargs[typed]
is treated specially — It matches a variable list
of arguments of arbitrary type but always constructs an implicit
array.
This is required so that the builtin
echo
proc
does what is expected. (The signature of echo
is proc echo*(x: varargs[typed, `$`]) {...}
).
So echo @[1, 2, 3]
prints "@[1, 2, 3]"
and not "123"
.
TODO Object Oriented Programming #
TODO Macros #
Nim’s syntax is incredibly versatile, and macros can be used to rewrite the abstract syntax tree (AST) of a program. The general process for writing a macro consists of two steps that are repeated until the desired results are achieved:
Nim in Action #
Threads #
6.2.1 The threads
module and GC safety #
Problematic code #
var data = "Hello World"
proc showData() {.thread.} =
echo(data)
var thread: Thread[void]
createThread[void](thread, showData)
joinThread(thread)
nim_src_I3YWGm.nim(6, 6) Error: 'showData' is not GC-safe as it
accesses 'data' which is a global using GC'ed memory
Remove :eval no
to see the above error.
Fixed code #
var data = "Hello World"
proc showData(param: string) {.thread.} =
echo(param)
var thread: Thread[string]
createThread[string](thread, showData, data)
joinThread(thread)
Hello World
Parsing #
6.3.2 Parsing the Wikipedia page counts format #
Using regular expressions #
import std/[re]
let pattern = re"([^\s]+)\s([^\s]+)\s(\d+)\s(\d+)"
var line = "en Nim_(programming_language) 1 70231"
var matches: array[4, string]
let start = find(line, pattern, matches)
doAssert start == 0
doAssert matches[0] == "en"
doAssert matches[1] == "Nim_(programming_language)"
doAssert matches[2] == "1"
doAssert matches[3] == "70231"
echo "Parsed successfully!"
Parsed successfully!
Parsing the data manually using split
#
import std/[strutils]
var line = "en Nim_(programming_language) 1 70231"
var matches = line.split()
doAssert matches[0] == "en"
doAssert matches[1] == "Nim_(programming_language)"
doAssert matches[2] == "1"
doAssert matches[3] == "70231"
echo "Parsed successfully!"
Parsed successfully!
Parsing the data manually using parseutils
#
import std/[parseutils]
var line = "en Nim_(programming_language) 1 70231"
var i = 0
var domainCode = ""
# parseUntil(s, token, until, start)
# returns the number of parsed characters
i.inc parseUntil(line, domainCode, {' '}, i)
i.inc
var pageTitle = ""
i.inc parseUntil(line, pageTitle, {' '}, i)
i.inc
var countViews = 0
i.inc parseInt(line, countViews, i)
i.inc
var totalSize = 0
i.inc parseInt(line, totalSize, i)
i.inc
doAssert domainCode == "en"
doAssert pageTitle == "Nim_(programming_language)"
doAssert countViews == 1
doAssert totalSize == 70231
echo "Parsed successfully!"
Parsed successfully!
Unit testing #
Using plain assert
in isMainModule
#
type
Card = object
rank: Rank
suit: Suit
Rank = enum
crSeven
crEight
crNine
crTen
crJack
crQueen
crKing
crAce
Suit = enum
csClubs = "♧"
csDiamonds = "♢"
csHearts = "♡"
csSpades = "♤"
proc `<`(a,b: Card): bool = a.rank < b.rank
when isMainModule:
let
aceDiamonds = Card(rank: crAce, suit: csDiamonds)
kingClubs = Card(rank: crKing, suit: csClubs)
aceClubs = Card(rank: crAce, suit: csClubs)
assert aceDiamonds > kingClubs
assert aceDiamonds == aceClubs
Evaluating above will throw this error:
Error: unhandled exception: aceDiamonds == aceClubs [AssertionError]
Above <
is implemented for Cards
. The >
operator is just sugar for
backwards <
.
isMainModule
is used to do some quick tests. It means that if this
module were to be imported and used in some application, that logic
wouldn’t even be compiled into the binary, let alone evaluated.
Using the unittest
module #
type
Card = object
rank: Rank
suit: Suit
Rank = enum
crSeven
crEight
crNine
crTen
crJack
crQueen
crKing
crAce
Suit = enum
csClubs = "♧"
csDiamonds = "♢"
csHearts = "♡"
csSpades = "♤"
proc `<`(a,b: Card): bool = a.rank < b.rank
when isMainModule:
import std/[unittest]
suite "test card relations":
setup:
let
aceDiamonds = Card(rank: crAce, suit: csDiamonds)
kingClubs = Card(rank: crKing, suit: csClubs)
aceClubs = Card(rank: crAce, suit: csClubs)
test "greater than":
check:
aceDiamonds > kingClubs
aceClubs > kingClubs
test "equal to":
check aceDiamonds == aceClubs
Breakdown of the unittest
code #
We first define the test suite name.
suite "test card relations":
In that test suite, we do some initial setup, that defines the variables, etc. that will be used in the tests.
setup:
let
aceDiamonds = Card(rank: crAce, suit: csDiamonds)
kingClubs = Card(rank: crKing, suit: csClubs)
aceClubs = Card(rank: crAce, suit: csClubs)
Then we define tests using test "test name":
, and use the check
statements nested under those to do the checks, which look similar to
the earlier assert
approach.
# test 1
test "greater than":
check:
aceDiamonds > kingClubs
aceClubs > kingClubs
# test 2
test "equal to":
check aceDiamonds == aceClubs
Failure output #
unittest
produces a more informative failure output as shown below.
TO BE FIXED [ob-nim or unittest] Above does not create the unittest
error when evaluated using ob-nim #
But as you see in the Corrected code section, the unittest
generated
“pass” message gets output fine.
Issue filed at https://github.com/Lompik/ob-nim/issues/4.
For now the error message returned by unittest
is pasted below
manually, until I start getting it correctly on stderr via
unittest
.
[Suite] test card relations
[OK] greater than
cardsuite.nim(39, 24): Check failed: aceDiamonds == aceClubs
aceDiamonds was (rank: crAce, suit: ♢)
aceClubs was (rank: crAce, suit: ♧)
[FAILED] equal to
Corrected code #
From the error, we see that only the “equal to” test fails. As the
==
proc is not defined for Card
, Nim used the default ==
for
object comparison, and so that check aceDiamonds == aceClubs
test
failed.
type
Card = object
rank: Rank
suit: Suit
Rank = enum
crSeven
crEight
crNine
crTen
crJack
crQueen
crKing
crAce
Suit = enum
csClubs = "♧"
csDiamonds = "♢"
csHearts = "♡"
csSpades = "♤"
proc `<`(a,b: Card): bool = a.rank < b.rank
proc `==`(a,b: Card): bool = a.rank == b.rank
when isMainModule:
import std/[unittest]
suite "test card relations":
setup:
let
aceDiamonds = Card(rank: crAce, suit: csDiamonds)
kingClubs = Card(rank: crKing, suit: csClubs)
aceClubs = Card(rank: crAce, suit: csClubs)
test "greater than":
check:
aceDiamonds > kingClubs
aceClubs > kingClubs
test "equal to":
check aceDiamonds == aceClubs
[Suite] test card relations
[OK] greater than
[OK] equal to
About DSL and unittest
#
Nim is extremely effective at creating DSLs in ways that other
languages simply don’t have at their disposal. Statements like check
and suite
in this unittest
module almost act like additions to the
syntax of the language itself; they’re not functions or classes,
operating at runtime; they operate at compilation, accepting the tests
written by the programmer as AST objects and manipulating them until
the resulting code is quite different indeed.
Skipping tests #
Put skip
or skip()
at the end of the test
block you want to
skip.
import std/[unittest]
suite "something":
test "passing test":
check:
true == true
test "failing test to be ignored":
check:
true == false
skip
[Suite] something
[OK] passing test
/tmp/babel-sQr7ZH/nim_src_WMFvaB.nim(13, 11): Check failed: true == false
true was true
false was false
[SKIPPED] failing test to be ignored
Expecting failures (expect
) #
import std/[unittest]
suite "something":
test "passing test":
check:
true == true
test "test expected to fail":
expect AssertionError:
assert true == false
[Suite] something
[OK] passing test
[OK] test expected to fail
Thanks to @mratsim from GitHub for this tip.
Also see #
Reference #
unittest.nim
– Source | Documentation- https://blog.zdsmith.com/posts/unit-testing-in-nim.html#unittestinginnim
Documentation #
TODO runnableExamples
#
See itertools
library as an example.
One Liners #
As Nim syntax is indentation dependent, one-lining any arbitrary Nim code might not be possible. But it certainly is, for many Nim constructs.
One-lining Nim code should be possible in general if the below rules are followed:
- Replace newlines after each top-level indentation Nim code with
;
(see the below example to understand this better). - Do not use
;
to end the one-liner. - You can define only one
proc
in a Nim one-liner. And if you need to define that oneproc
, you need to forward declare it, and then define theproc
at the very end. - Wrap
if
..else
,for
, etc. constructs in parentheses.
Let’s take the below example that you’d like to one-line.
proc foo(x: string): int =
if x == "":
-1
else:
<