Nim
- Nim Help
- Echo
- Syntax
- Templates and Macros
- Representing one type in another
- Data Types
- String Stuff
- Math
- Variable “Types”
- Types
- TODO Pointers and References
- 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
- TODO Bitsets
- TODO Varargs
- TODO Object Oriented Programming
- TODO OOP Macro
- Nim in Action
- Unit testing
- Documentation
- Miscellaneous
- NEED TO UNDERSTAND Pragmas
- Questions/Doubts
- References
Nim Help #
If on the devel
branch and on a commit newer than
9cc8fec370, 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
0.18.1
0
18
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 = 4, ## underscored text
styleBlink, ## blinking/bold text
styleReverse = 7, ## 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 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 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 ""
stdout.styledWrite
to echo styled string without trailing newline^[[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 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 the snippet in 1,
except that the low-level procs setForeGroundColor
,
resetAttributes
and stdout.write
are used instead of
stdout.styledWrite
.
import 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
NEED TO UNDERSTAND When to use stdout.flushFile
? #
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
The untyped
is optional, but using it as in the above example is recommended.
template foo(body) =
body
foo(): # special colon
1
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)
.
TODO Quote Helper #
NEED TO UNDERSTAND Term Rewriting Macros #
Nim Manual – Term Rewriting Macros
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 | - | 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 strformat, typetraits
let
b_seq = @[true, false]
for b in b_seq:
var b_int = b.int
echo fmt"Value of {b.type.name} b is {b}"
echo fmt"Value of {b_int.type.name} 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 strformat, typetraits
let b_seq = @[true, false]
for b in b_seq:
var b_float = b.float
echo fmt"Value of {b.type.name} b is {b}"
echo fmt"Value of {b_float.type.name} 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 strformat, typetraits
let b_seq = @[true, false]
for b in b_seq:
var b_string1 = $b
var b_string2 = fmt"{b}"
var b_string3 = if b: "true" else: "false"
echo fmt"Value of {b.type.name} b is {b}"
echo fmt" Value of {b_string1.type.name} b_string1 is {b_string1}"
echo fmt" Value of {b_string2.type.name} b_string2 is {b_string2}"
echo fmt" Value of {b_string3.type.name} 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 strformat, typetraits
let
c = 'A'
c_int = c.int
echo fmt"Value of {c.type.name} c is {repr(c)}"
echo fmt"Value of {c_int.type.name} c_int is {c_int}"
Value of char c is 'A'
Value of int c_int is 65
DONE char
to float
#
import strformat, typetraits
let
c = 'A'
c_float = c.float
echo fmt"Value of {c.type.name} c is {repr(c)}"
echo fmt"Value of {c_float.type.name} c_float is {c_float}"
Value of char c is 'A'
Value of float c_float is 65.0
DONE char
to string
#
import strformat, typetraits
from strutils import join
let c_seq = @['A', 'b', '@']
for c in c_seq:
let
c_string1 = $c
c_string2 = fmt"{c}"
c_string3 = @[c].join("")
var c_string4 = newString(1)
c_string4[0] = c
echo fmt"Value of {c.type.name} c is {repr(c)}"
echo fmt" Value of {c_string1.type.name} c_string1 is {c_string1}"
echo fmt" Value of {c_string2.type.name} c_string2 is {c_string2}"
echo fmt" Value of {c_string3.type.name} c_string3 is {c_string3}"
echo fmt" Value of {c_string4.type.name} 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 strformat, typetraits
from strutils import join
let
c_seq = @['a', 'b', 'c', 'd']
str = c_seq.join("")
echo fmt"{str} is the stringified form of {c_seq.type.name} {c_seq}"
abcd is the stringified form of seq[char] @['a', 'b', 'c', 'd']
From int
#
DONE int
to bool
#
- The int value needs to be either 0 or 1.
- RangeError exception is thrown for any other int value.
import strformat, typetraits
let
i_seq = @[-1, 0, 1, 2]
for i in i_seq:
echo fmt"Value of {i.type.name} i is {i}"
var i_bool: bool
try:
i_bool = i.bool
echo fmt" Value of {i_bool.type.name} i_bool is {i_bool}"
except:
echo fmt" [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1
[Error] RangeError: value out of range: -1
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
[Error] RangeError: value out of range: 2
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 strformat, typetraits
let i_seq = @[-1000, 0, 1, 1000]
for i in i_seq:
echo fmt"Value of {i.type.name} i is {i}"
var i_bool: bool
try:
i_bool = i.float.bool
echo fmt" Value of {i_bool.type.name} i_bool is {i_bool}"
except:
echo fmt" [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. - RangeError exception is thrown if that value is outside that range.
import strformat, typetraits
let i_seq: seq[int] = @[-1, 0, 62, 256]
for i in i_seq:
var i_char1, i_char2: char
echo fmt"Value of {i.type.name} i is {i}"
try:
i_char1 = chr(i) # or i.chr
echo fmt" Value of {i_char1.type.name} i_char1 is {repr(i_char1)}"
except:
echo fmt" [Error] {getCurrentException().name}: {getCurrentException().msg}"
try:
i_char2 = i.char # or char(i)
echo fmt" Value of {i_char2.type.name} i_char2 is {repr(i_char2)}"
except:
echo fmt" [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1
[Error] RangeError: value out of range: -1
[Error] RangeError: value out of range: -1
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] RangeError: value out of range: 256
[Error] RangeError: value out of range: 256
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 strformat, typetraits
let i_seq = @[-2, -1, 0, 1, 254, 255, 256, 257]
for i in i_seq:
var i_char = i.float.char
echo fmt"Value of {i.type.name} i is {i}"
echo fmt" Value of {i_char.type.name} 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 strformat, typetraits
let i_seq: seq[int] = @[-1, 0, 1000]
for i in i_seq:
var i_float = i.float
echo fmt"Value of {i.type.name} i is {i}"
echo fmt"Value of {i_float.type.name} 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 strformat, strutils, typetraits
let i_seq: seq[int] = @[-1000, 0, 1000]
for i in i_seq:
echo fmt"Value of {i.type.name} i is {i}"
var
i_str1 = $i
i_str2 = fmt"{i}"
i_str3 = intToStr(i) # strutils
echo fmt" Value of {i_str1.type.name} i_str1 is {i_str1}"
echo fmt" Value of {i_str2.type.name} i_str2 is {i_str2}"
echo fmt" Value of {i_str3.type.name} 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 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 fmt
function from strformat
. See below to see what I
mean by “similar”.
import strformat
let i_arr = [-100, -50, 0, 0, 123, 1000]
for i in i_arr:
echo fmt"{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
#
- Any
float
value between(-1.0, 1.0)
isfalse
. - All other
float
values aretrue
.
import strformat, typetraits
let
f_seq = @[-1.0, -0.99, -0.1, 0.0, 0.3, 0.5, 0.8, 1.0, 1.1, 2.0]
for f in f_seq:
var f_bool = f.bool
echo fmt"Value of {f.type.name} f is {f}"
echo fmt"Value of {f_bool.type.name} f_bool is {f_bool}"
Value of float f is -1.0
Value of bool f_bool is true
Value of float f is -0.99
Value of bool f_bool is false
Value of float f is -0.1
Value of bool f_bool is false
Value of float f is 0.0
Value of bool f_bool is false
Value of float f is 0.3
Value of bool f_bool is false
Value of float f is 0.5
Value of bool f_bool is false
Value of float f is 0.8
Value of bool f_bool is false
Value of float f is 1.0
Value of bool f_bool is true
Value of float f is 1.1
Value of bool f_bool is true
Value of float f is 2.0
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 strformat, typetraits
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 fmt"Value of {f.type.name} f is {f}"
echo fmt"Value of {f_char.type.name} 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 strformat, typetraits, 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 fmt"Value of {f.type.name} f is {f}"
echo fmt"Value of {f_int.type.name} 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 strformat, typetraits
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 = fmt"{f}"
echo fmt"Value of {f.type.name} f is {f}"
echo fmt" Value of {f_string1.type.name} f_string1 is {f_string1}"
echo fmt" Value of {f_string2.type.name} 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 strformat, strutils, typetraits
let s_seq = @["true", "True", "tRuE", "false", "False", "FaLsE"]
for s in s_seq:
var s_bool = parseBool(s)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {s_bool.type.name} 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 strformat, typetraits
let s = "abcd"
var c_seq: seq[char]
for c in s:
c_seq.add(c)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {c_seq.type.name} c_seq is {c_seq}"
Value of string s is abcd
Value of seq[char] c_seq is @['a', 'b', 'c', 'd']
DONE string
to int
#
import strformat, strutils, typetraits
let
s = "1212"
s_int = parseInt(s)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {s_int.type.name} s_int is {s_int}"
Value of string s is 1212
Value of int s_int is 1212
DONE string
to float
#
import strformat, strutils, typetraits
let
s = "12.12"
s_float = parseFloat(s)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {s_float.type.name} 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 strformat
echo fmt"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 strformat
echo fmt"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 strformat, typetraits
var
aInt: int = 1
aInt8: int8 = 2
aInt16: int16 = 3
aInt32: int32 = 4
aInt64: int64 = 5
echo fmt"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt64 = aInt8 # works
aInt64 = aInt16 # works
aInt64 = aInt32 # works
aInt64 = aInt # works
echo fmt"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 fmt"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 fmt"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 fmt"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.int, " = ", 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 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 fmt"int: {low(int)} (-2^{int_low_2power}) -> {high(int)} (2^{int_low_2power} - 1)"
echo fmt"int32: {low(int32)} (-2^{int32_low_2power}) -> {high(int32)} (2^{int32_low_2power} - 1)"
echo fmt"int64: {low(int64)} (-2^{int64_low_2power}) -> {high(int64)} (2^{int64_low_2power} - 1)"
echo fmt"float: {low(float)} -> {high(float)}"
echo fmt"float32: {low(float32)} -> {high(float32)}"
echo fmt"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 strformat
echo fmt"Size of bool is {sizeof(bool)} byte"
echo fmt"Size of char is {sizeof(char)} byte"
echo fmt"Size of int is {sizeof(int)} bytes"
echo fmt"Size of int32 is {sizeof(int32)} bytes"
echo fmt"Size of int64 is {sizeof(int64)} bytes"
echo fmt"Size of float is {sizeof(float)} bytes"
echo fmt"Size of float32 is {sizeof(float32)} bytes"
echo fmt"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
on string #
import strformat, typetraits
var s: string
echo fmt"initial value of {s.type.name} s = `{s}', isNil? {s.isNil}"
initial value of string s = `', isNil? true
isNil
non-string primitive types #
isNil
does not work for non-string primitive types like bool
,
char
, int
and float
.
import strformat, typetraits
var
b: bool
c: char
i: int
f: float
echo fmt"initial value of {b.type.name} b = {b}, isNil? {b.isNil}"
echo fmt"initial value of {c.type.name} c = {c}, isNil? {c.isNil}"
echo fmt"initial value of {i.type.name} i = {i}, isNil? {i.isNil}"
echo fmt"initial value of {f.type.name} 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(313, 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)
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
Rune #
- The functions from
strutils
likeisAlphaNumeric
,isAlphaAscii
work on chars and strings. - The functions from
unicode
likeisAlpha
,isLower
,isUpper
, etc. work on Runes and strings.
from strutils import isAlphaNumeric, isAlphaAscii
import unicode
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
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 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 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.
import strformat
let
a = 100
b = "abc"
echo fmt"a = {a}, b = {b}"
a = 100, b = abc
TO BE FIXED Use string variable containing message formatting for fmt
#
Below does not work:
import 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:
echo NimVersion
assert NimVersion >= "0.18.0"
assert NimVersion < "00.18.0" # Surprise!
assert NimVersion >= "0.09.0"
assert NimVersion < "0.9.0" # Surprise!
0.18.1
See Tuple comparison instead for Nim version comparison.
Math #
Log #
log
#
Credit for the below log
function goes to @Paalon from GitHub [ref]:
import 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 random, strformat
echo fmt"log10(111) = {log(111.float, 10.float)}"
echo fmt"log2(8) = {log(8.float, 2.float)}"
echo fmt"ln(8) = {log(8.float, E)}"
echo fmt"ln(8) = {log(8.float)}"
log10(111) = 2.045322978786657
log2(8) = 3.0
ln(8) = 2.079441541679836
ln(8) = 2.079441541679836
log2
#
import math, strformat
var
val = 8
power2 = log2(val.float) # need to cast val to a float
echo fmt"2^{power2} = {val}"
2^3.0 = 8
log10
#
import math, strformat
var
val = 100
power10 = log10(val.float) # need to cast val to a float
echo fmt"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 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 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 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 math
echo pow(2.0, 1.0)
echo pow(2.float, 1.float) # same as above
2.0
2.0
Random #
Random bool
#
import random
proc randBool(): bool =
result = rand(1).bool
for _ in 0 .. 5:
echo randBool()
true
false
true
true
false
false
Random range #
Use rand(LOWER .. UPPER)
.
import random
for _ in 0 .. 5:
echo rand(4 .. 5)
5
4
5
5
4
4
Works with negatives too:
import random
for _ in 0 .. 5:
echo rand(-5 .. 5)
-4
5
0
0
2
-2
Call randomize
before rand
#
If you call just the rand
function without first calling
randomize
, you will get the very same random value each time.
import random
echo rand(high(int))
rand
without randomize
4292486321577947087
In order to get truly random output, first call randomize
and then
rand
. If you evaluate the below snippet, you will very likely get a
different output each time, unlike the above snippet.
import random
randomize()
echo rand(high(int))
rand
with randomize
, no seed1682709059413540781
Specifying randomization seed #
You can also have a use-case where you want to specify a particular
randomization seed. In that case, pass that integer seed to the
randomize
function.
Re-evaluating below will result in the same output each time, like in 2, but with the difference that you can control the randomization seed.
import random
randomize(123)
echo rand(high(int))
rand
with randomize
, with seed8452497653883
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.
import 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))
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
rand
range #
import random
let k = 3
randomize(2) # arbitrarily picked seed
for _ in 0 .. (2*k):
echo rand(k)
2
2
0
3
2
0
1
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 random, strformat
echo fmt"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 random, strformat
echo fmt"rand(9223372036854775807.int) = {rand(9223372036854775807.int)}"
rand(9223372036854775807.int) = 4292486321577947087
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 strformat
echo "divisor = 3"
for i in -4 .. 4:
echo fmt" {i:2} div 3 = {i div 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
echo fmt" {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 strformat
echo "divisor = 3"
for i in -4 .. 4:
echo fmt" {i:2} mod 3 = {i mod 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
echo fmt" {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
Variable “Types” #
var
vs let
vs const
Keyword | Variable type | Must be initialized? | Can be set during runtime? | Should be evallable 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
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()
getAlphabet
function implementation without using result
variableabcdefghijklmnopqrstuvwxyz
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()
getAlphabet
function implementation using result
variableabcdefghijklmnopqrstuvwxyz
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': nil
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
noinit
pragma does not work for global variablesValue of uninitialized variable 'ui_i': [0, 0, 0]
Pollute Stack #
A little function PolluteStack
to help test if {.noinit.}
works.
import random, math
proc polluteStack() =
var
c: char = (rand(high(char).int)).char
i32: int32 = (rand(high(int32).int)).int32
i64: int64 = (rand(high(int64).int)).int64
f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rand(high(int64).int)).float64
b: bool = 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 = (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(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 random, math
proc polluteStack() =
var
c: char = (rand(high(char).int)).char
i32: int32 = (rand(high(int32).int)).int32
i64: int64 = (rand(high(int64).int)).int64
f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rand(high(int64).int)).float64
b: bool = rand(1).bool
echo repr(c)
echo i32
echo i64
echo f32
echo f64
echo b
randomize(123) # arbitarily picked seed
polluteStack()
'{'
805341723
3469772516323406999
4.753479106664858e+17
7.875093343280577e+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 random, math
proc polluteStack() =
var
c: char = (rand(high(char).int)).char
i32: int32 = (rand(high(int32).int)).int32
i64: int64 = (rand(high(int64).int)).int64
f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rand(high(int64).int)).float64
b: bool = rand(1).bool
# local var in block
block foo:
randomize(123) # arbitarily picked seed
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 random, math
proc polluteStack() =
var
c: char = (rand(high(char).int)).char
i32: int32 = (rand(high(int32).int)).int32
i64: int64 = (rand(high(int64).int)).int64
f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rand(high(int64).int)).float64
b: bool = 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
randomize(123) # arbitarily picked seed
polluteStack()
a()
noinit
pragma works for local variablesValue of uninitialized variable 'ui_i1': 123
Value of uninitialized variable 'ui_i2': [3458916362389291184, 805341723, 8863084066665201664]
- 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 random, math
proc polluteStack() =
var
c: char = (rand(high(char).int)).char
i32: int32 = (rand(high(int32).int)).int32
i64: int64 = (rand(high(int64).int)).int64
f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rand(high(int64).int)).float64
b: bool = 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
randomize(123) # arbitarily picked seed
polluteStack()
a()
[3458916362389291184, 805341723, 8863084066665201664]
['\x97', '\xB4', '\xDE']
[true, true, true]
[7.29112201955677e-304, 4.940656458412465e-324, 7.875093343280577e+18]
- 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 random, math
proc polluteStack() =
var
c: char = (rand(high(char).int)).char
i32: int32 = (rand(high(int32).int)).int32
i64: int64 = (rand(high(int64).int)).int64
f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
f64: float64 = (rand(high(int64).int)).float64
b: bool = 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
randomize(123) # arbitarily picked seed
polluteStack()
bar()
i32 (auto-init) = 0
i64 (auto-init) = 0
f32 (auto-init) = 0.0
f64 (auto-init) = 0.0
i32ni (no init) = 807869368
i64ni (no init) = 3469772516323406999
f32ni (no init) = 5.746757700654961e-35
f64ni (no init) = 1.421349011415116e+139
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
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 strformat, typetraits
let
arr1 = [1.0, 2, 3, 4]
arr2 = ['a', 'b', 'c', 'd']
arr3 = ["a", "bc", "def", "ghij", "klmno"]
echo fmt"arr1 is of type {arr1.type.name} with value {arr1}"
echo fmt"arr2 is of type {arr2.type.name} with value {arr2}"
echo fmt"arr3 is of type {arr3.type.name} 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"]
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 strformat, typetraits
let
i_seq1:seq[int] = @[1, 2, 3, 4, 5, 6]
i_seq2 = @[7.0, 8, 9]
echo fmt"i_seq1 is of type {i_seq1.type.name} with value {i_seq1}"
echo fmt"i_seq2 is of type {i_seq2.type.name} 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 strformat, typetraits
var i_seq = @[1, 2, 3]
echo fmt"i_seq is of type {i_seq.type.name} with value {i_seq}"
echo fmt"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 fmt" 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 fmt" 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 fmt" popped_elem = {popped_elem}"
echo fmt" 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
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 strformat, typetraits
proc testOpenArray(x: openArray[int]) =
echo fmt"x = {x} (type: {x.type.name}, 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 strformat
proc foo(x: openArray[int]) =
echo fmt"{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
(Field0: "Peter", Field1: 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 strformat, typetraits
let person: (string, int) = ("Peter", 30)
echo fmt"Tuple person of type {person.type.name} = {person}"
echo person[0]
echo person[1]
Tuple person of type (string, int) = (Field0: "Peter", Field1: 30)
Peter
30
Named tuples #
import strformat, typetraits
type
Person = tuple
name: string
age: int
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {name(person.type)} = {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
See Clash between type.name
and a custom tuple field name on why I am using name(person.type)
in the above
code snippet instead of person.type.name
.
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 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 #
Different 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)
Even though you don’t need to declare a type for a tuple to use it, tuples created with different field names will be considered different objects despite having the same field 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]'
From the following example though, 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 strformat, typetraits
type Person = tuple[name: string, age: int]
let personAnon1 = ("Peter", 30)
var
personNamed: Person
personAnon2: (string, int)
echo fmt"Tuple personAnon1 of type {personAnon1.type.name} = {personAnon1}"
echo "Assigning an anonymous tuple to a named tuple .."
personNamed = personAnon1
echo fmt"Tuple personNamed of type {name(personNamed.type)} = {personNamed}"
echo "Assigning a named tuple to an anonymous tuple .."
personAnon2 = personNamed
echo fmt"Tuple personAnon2 of type {personAnon2.type.name} = {personAnon2}"
Tuple personAnon1 of type (string, int) = (Field0: "Peter", Field1: 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) = (Field0: "Peter", Field1: 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 person1
didn’t get the field
names. But all other person* variables did.
Tuple comparison #
let NimVersionT = (major: NimMajor, minor: NimMinor, patch: NimPatch)
echo NimVersionT
assert (0, 18, 0) < NimVersionT
assert (0, 18, 1) == NimVersionT
assert (0, 18, 2) > NimVersionT
assert (0, 19, 0) > NimVersionT
assert (1, 0, 0) > NimVersionT
(major: 0, minor: 18, patch: 1)
TODO Objects #
TODO Pointers and References #
TODO Pointer (ptr
) #
Dereferencing #
import strformat, typetraits
type Foo = ref object
x, y: float
var f: Foo
new f
# Accessing the reference
echo fmt"Type of f = {$type(f)}"
echo fmt"{$type(f[])} f[] = {f[]}"
f[].y = 12
echo fmt"{$type(f[])} f[] = {f[]}"
# When accessing values the dereference operator [] can be left out.
f.x = 13.5
echo fmt"{$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.
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 os
let
numParams = paramCount()
params = commandLineParams()
echo "Number of command line params: ", numParams
echo "Command line params: ", params
echo "Param 1 = ", params[0]
echo "Param 2 = ", params[1]
echo "Param 3 = ", params[2]
Number of command line params: 3
Command line params: @["foo", "bar", "zoo car"]
Param 1 = foo
Param 2 = bar
Param 3 = zoo car
For serious longopt and shortopt command line parameter parsing using the cligen library.
Terminal #
Hello World animation #
- Credit
- Below code is taken from this comment by /u/psychotic_primes from Reddit.
import 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
Shell #
Environment Variables #
- Use
putEnv
to set environment variable - Use
getEnv
to get environment variable
import os
let
envVar = "NIM_TEMP"
envVarVal = "foo"
putEnv(envVar, envVarVal)
echo getEnv(envVar)
foo
File paths #
File base name #
import 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
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 os, strformat
let
fileName = "/tmp/foo/bar/zoo.txt"
dataStr = "abc"
(dir, _, _) = splitFile(fileName)
echo fmt"{dir} exists? {dirExists(dir)}"
if (not dirExists(dir)):
echo fmt" creating {dir} .."
createDir(dir)
echo fmt"{dir} exists now? {dirExists(dir)}"
writeFile(fileName, dataStr)
removeDir("/tmp/foo/bar/")
/tmp/foo/bar exists? false
creating /tmp/foo/bar ..
/tmp/foo/bar exists now? true
File Permissions #
Get/read file permissions #
import 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 os
proc parseFilePermissions(octals: string): set[FilePermission] =
## Converts the input permissions octal string to a Nim set for FilePermission type.
# https://devdocs.io/nim/os#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 random, strformat
randomize(1)
for _ in 0 .. 10:
let perm = fmt"{rand(7)}{rand(7)}{rand(7)}"
echo perm, " = ", parseFilePermissions(perm)
112 = {fpUserExec, fpGroupExec, fpOthersWrite}
114 = {fpUserExec, fpGroupExec, fpOthersRead}
436 = {fpUserRead, fpGroupExec, fpGroupWrite, fpOthersWrite, fpOthersRead}
752 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpGroupRead, fpOthersWrite}
727 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupWrite, fpOthersExec, fpOthersWrite, fpOthersRead}
101 = {fpUserExec, fpOthersExec}
236 = {fpUserWrite, fpGroupExec, fpGroupWrite, fpOthersWrite, fpOthersRead}
522 = {fpUserExec, fpUserRead, fpGroupWrite, fpOthersWrite}
425 = {fpUserRead, fpGroupWrite, fpOthersExec, fpOthersRead}
273 = {fpUserWrite, fpGroupExec, fpGroupWrite, fpGroupRead, fpOthersExec, fpOthersWrite}
304 = {fpUserExec, fpUserWrite, fpOthersRead}
Exceptions #
Exception Hierarchy #
https://devdocs.io/nim/manual#exception-handling-exception-hierarchy
All exceptions are objects of type Exception
.
Exception
AccessViolationError
ArithmeticError
DivByZeroError
OverflowError
AssertionError
DeadThreadError
FloatingPointError
FloatDivByZeroError
FloatInexactError
FloatInvalidOpError
FloatOverflowError
FloatUnderflowError
FieldError
IndexError
ObjectAssignmentError
ObjectConversionError
ValueError
KeyError
ReraiseError
RangeError
OutOfMemoryError
ResourceExhaustedError
StackOverflowError
SystemError
IOError
OSError
LibraryError
Custom Exceptions #
type
MyError = object of Exception
Raising Exceptions #
type
MyError = object of Exception
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 Exception
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 strformat
type
MyError = object of Exception
try:
raise newException(MyError, "details about what went wrong")
except MyError:
echo fmt"[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 macros
import sequtils
import strutils
import future
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 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 https://nim-lang.org/docs/tut1.html#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
(future
on Nim 0.18.0 and older) #
This is a standard Nim library.
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))
lambda
in Emacs LispHere’s the same using the =>
macro in Nim:
import 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
=>
macro3
- 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 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 the snippet in 11 can be rewritten using ->
as
shown below:
import 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 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 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
DONE List Comprehension ([]
and lc
) #
List comprehension is implemented in this 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:
import strformat, typetraits, sugar
let
str = "nim lang"
cSeq = lc[c | (c <- str), # lc[EXPR | (ELEM1 <- LIST1),
char] # SEQ_TYPE]
echo fmt"str of type {$str.type} = {str}"
echo fmt"cSeq of type {$cSeq.type} (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']
Though, the above example can be written more concisely using map
and =>
:
import sequtils, sugar
let
str = "nim lang"
cSeq = str.map(c => c)
echo cSeq
@['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']
Other list comprehension examples:
import 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]
The LIST2 and onwards can also use one or more of the prior declared ELEMs.
import 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)]
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"
""")
pkg = "tmux"
let tmux = cfg.getTable(pkg)
for key, val in tmux.pairs:
echo key
req_env_vars
set_env_vars
Older ways to do the same #
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"
""")
pkg = "tmux"
for key, val in cfg.pairs:
if key == "tmux":
for key, val in val.tableVal.pairs:
echo key
req_env_vars
set_env_vars
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"
""")
pkg = "tmux"
let tmux = cfg.getValueFromFullAddr(pkg)
for key, val in tmux.tableVal.pairs:
echo key
req_env_vars
set_env_vars
strfmt
(Yep, this is different from strformat
) #
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()
).
You will need to install it first by doing nimble install strfmt
.
Unfortunately though, the strfmt
developer has stopped using
Nim. 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 bignum
Examples #
import strformat, 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 fmt"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 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
TO BE FIXED Clash between type.name
and a custom tuple field name #
Nim Issue #7975
Using $
works #
import strformat, typetraits
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {$person.type} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using name(VAR.type)
works too #
import strformat, typetraits
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {name(person.type)} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using VAR.type.name
FAILS #
import strformat, typetraits
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {person.type.name} = {person}"
Hint: typetraits [Processing]
nim_src_2f8wWD.nim(7, 9) template/generic instantiation from here
lib/pure/strformat.nim(260, 8) Error: type mismatch: got <string, type string>
but expected one of:
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: char)
first type mismatch at position: 2
required type: char
but expression 'type(person).name' is of type: type string
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(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: cstring)
first type mismatch at position: 2
required type: cstring
but expression 'type(person).name' is of type: type string
proc add[T](x: var seq[T]; y: openArray[T])
first type mismatch at position: 1
required type: var seq[T]
but expression 'fmtRes218041' is of type: string
proc add[T](x: var seq[T]; y: T)
first type mismatch at position: 1
required type: var seq[T]
but expression 'fmtRes218041' is of type: string
expression: add(fmtRes218041, 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 strformat, typetraits
type Person = tuple[namex: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {person.type.name} = {person}"
Tuple person of type Person = (namex: "Peter", age: 30)
TO BE FIXED Tuple type name gets stuck to the first printed tuple var #
Nim Issue #7976
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 strformat, typetraits
type Person = tuple[name: string, age: int]
let
person1: Person = ("Peter", 30)
person2: tuple[name: string, age: int] = (name : "Peter", age : 30)
echo fmt"Tuple person1 of type {$person1.type} = {person1}"
echo fmt"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 strformat, typetraits
type Person = tuple[name: string, age: int]
let
person1: Person = ("Peter", 30)
person2: tuple[name: string, age: int] = (name : "Peter", age : 30)
echo fmt"Tuple person2 of type {$person2.type} = {person2}"
echo fmt"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 tables, typetraits
var a = {"hi": 1, "there": 2}.toTable
echo a["hi"], " ", a.len
echo "a is of type ", a.type.name
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 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
foo: 3
spam: 1
iterate keys:
eggs
foo
spam
iterate values:
2
3
1
hash["spam"] = 1
hash["key_not_set_yet"] is not yet set.
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 strformat, typetraits, critbits
var
s: CritBitTree[void]
t: CritBitTree[int]
echo fmt"{s.type.name} s (CBT as set) = {s}"
echo fmt"{t.type.name} 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 strformat, typetraits, critbits
var t: CritBitTree[int]
t["a"] = 1
echo fmt"{t.type.name} t = {t}"
echo """Mutating or changing the value of t["a"] .. """
t["a"] = 2
echo fmt"{t.type.name} 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 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 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 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:
{"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 strformat, typetraits, critbits
var s: CritBitTree[void]
echo fmt"Type of s: {s.type.name}"
echo """Adding "a" .."""
incl(s, "a")
echo """Adding "b" .."""
s.incl("b")
echo fmt" s has {s.len} elements: {s}"
echo """Adding "a" again .."""
s.incl("a")
echo fmt" 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 strformat, typetraits, critbits
var
s: CritBitTree[void]
t: CritBitTree[int]
s.incl("y")
t["a"] = 1
echo fmt"{s.type.name} s = {s}"
echo fmt"""s["x"] exists? {s.contains("x")}"""
echo fmt"""s["y"] exists? {s.hasKey("y")}"""
echo fmt"{t.type.name} t = {t}"
echo fmt"""t["a"] exists? {t.contains("a")}"""
echo fmt"""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 strformat, typetraits, critbits
var
s: CritBitTree[void]
t: CritBitTree[string]
s.incl("foo")
s.incl("bar")
s.incl("zoo")
echo fmt"{s.type.name} s = {s}"
echo fmt"Number of elements in s = {s.len}"
t["a"] = "hello"
t["b"] = "world"
echo fmt"{t.type.name} t = {t}"
echo fmt"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 strformat, typetraits, critbits
var t: CritBitTree[int]
t["a"] = 10
echo fmt"{t.type.name} t = {t}"
inc(t, "a")
echo fmt"{t.type.name} t = {t}"
t.inc("a")
echo fmt"{t.type.name} 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 strformat, typetraits, critbits
var t: CritBitTree[int]
echo fmt"{t.type.name} t = {t}"
echo """Initializing/incrementing "a" key to 1 .."""
t.inc("a")
echo fmt" {t.type.name} t = {t}"
echo """Decrementing "a" key by 1 .."""
t.inc("a", -1)
echo fmt" {t.type.name} t = {t}"
echo """Incrementing "a" key by 4 .."""
t.inc("a")
t.inc("a")
t.inc("a")
t.inc("a")
echo fmt" {t.type.name} t = {t}"
echo """Decrementing "a" key by 2 .."""
t.inc("a", -1)
t.inc("a", -1)
echo fmt" {t.type.name} 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 strformat, typetraits, critbits
var t: CritBitTree[int]
echo fmt"{t.type.name} t = {t}"
t.inc("a", 5)
t.inc("b", 7)
echo fmt"{t.type.name} t = {t}"
t.inc("a", -10)
t.inc("b", -100)
echo fmt"{t.type.name} 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 strformat, typetraits, critbits
var t: CritBitTree[int]
t["a"] = 1
echo fmt"""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 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 json
#
Looping through keys in JSON #
import 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"]
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
import typetraits
echo "x is ", x.type.name
echo "y is ", y.type.name
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 #
Type conversion can be done by calling the type as a proc
.
import typetraits, strformat
var
x = 1.0
xType = x.type.name
echo fmt"x is {xType} and its value is {x}."
var
xCasted = x.int # int(x) will also work
xCastedType = xCasted.type.name
echo fmt"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 typetraits, strformat var x = int(1.0 / 3) # type conversion echo fmt"x is {x.type.name} 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 typetraits, strformat var y: seq[int] = @[] # empty seq needs type specification echo fmt"y is {y.type.name} 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)
ref 0x7f6274536058 --> ['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 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.proc positiveOrNegative(num: int): string = result = case num of low(int) .. -1: "negative" of 0: "zero" of 1 .. high(int): "positive" else: "impossible" echo positiveOrNegative(-1) echo positiveOrNegative(10000000) echo positiveOrNegative(0)
negative positive zero
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
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
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
iterator items(range: AlphaRange): int =
var i = range.low
while i <= range.high:
yield i
inc i
iterator pairs(range: AlphaRange): tuple[a: int, b: char] =
for i in range: # uses AlphaRange.items
yield (i, char(i + ord('a') - 1))
for i, c in AlphaRange(low: 7, high: 11): # uses AlphaRange.pairs
echo i, " ", c
7 g
8 h
9 i
10 j
11 k
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.
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
0
2
4
6
8
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
countTo
closure iterator defined with explicit return type- 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 13, 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 strformat, typetraits
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 fmt"countTo(10) is of type `{$countTo(10).type}'"
countTo
closure iterator defined with auto
return typecountTo(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 / Command Invocation 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
See also Command Invocation Syntax.
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
x - y
with the error:
nim_src_8RNhaJ.nim(4, 6) Error: 'minus' can have side effects
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
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 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 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 Command Invocation Syntax: arg1.procname(arg2)
echo powersOfTwo.filter(proc (x: int): bool = x > 4)
@[8, 16, 32]
@[8, 16, 32]
Also see Lambdas (=>
).
Do notation #
- 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 snippet 15
using the do
notation:
import sequtils # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]
echo(powersOfTwo.filter do (x: int) -> bool: x > 4)
# Below does the same thing as above.
echo(powersOfTwo.filter do (x: int) -> bool:
x > 4)
@[8, 16, 32]
@[8, 16, 32]
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 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 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 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 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.
import 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
@["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 strformat, typetraits
let b = 3
echo fmt"Outside block: b is of type {b.type.name} of value {b}."
block:
let b = "4" # shadowing is probably a dumb idea
echo fmt" Inside block: b is of type {b.type.name} of value {b}."
echo fmt"Outside block: Once again, b is of type {b.type.name} of value {b}."
Outside block: b is of type int of value 3.
Inside block: b is of type string of value 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 strformat, typetraits
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 fmt"Value of {a.type.name} a is {a}"
echo fmt"Value of {b.type.name} b is {b}"
echo fmt"Value of {c.type.name} c is {c}"
echo fmt"Value of {d.type.name} d is {d}"
echo fmt"Value of {e.type.name} 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 16, 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 17, 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 strformat, typetraits
let
a: int = 0b1011_1010
b: int = 0b0101_0011
echo fmt"{a:#010b} ^ {b:#010b} = {(a xor b):#010b}"
echo fmt"{a:#010b} & {b:#010b} = {(a and b):#010b}"
echo fmt"{a:#010b} | {b:#010b} = {(a or b):#010b}"
echo fmt"{a:#010b} >> 3 = {(a shr 3):#b}"
echo fmt"{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: nil, species: nil, 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
:
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[].sleep()
echo spot[]
(name: "Spot", species: "C. lupus", age: 1)
(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 #
- When to use ‘ref object’ vs plain ‘object’
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
sigQuit = 3, sigAbort = 6, sigKill = 9 # enum with holes
Colors {.pure.} = enum
Red = "FF0000", Green = (1, "00FF00"), Blue = "0000FF"
var
dir: CompassDirections = cdNorth
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
cdNorth
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 strformat, typetraits
var
someOrdinal = 'A' # char is an ordinal
echo fmt"someOrdinal ({$type(someOrdinal)}) = {someOrdinal}"
echo fmt"low value of someOrdinal: {repr(someOrdinal.low)}"
echo fmt"high value of someOrdinal: {repr(someOrdinal.high)}"
echo fmt"value of someOrdinal 1 step away in decrementing order: {someOrdinal.pred} (curr value: {someOrdinal})"
echo fmt"value of someOrdinal 3 steps away in decrementing order: {someOrdinal.pred(3)} (curr value: {someOrdinal})"
echo fmt"value of someOrdinal 1 step away in incrementing order: {someOrdinal.succ} (curr value: {someOrdinal})"
echo fmt"value of someOrdinal 3 steps away in incrementing order: {someOrdinal.succ(3)} (curr value: {someOrdinal})"
inc someOrdinal
echo fmt"someOrdinal after incrementing once: {someOrdinal}"
someOrdinal.inc(2)
echo fmt"someOrdinal after incrementing twice: {someOrdinal}"
someOrdinal.dec
echo fmt"someOrdinal after decrementing once: {someOrdinal}"
dec(someOrdinal, 4)
echo fmt"someOrdinal after decrementing 4 times: {someOrdinal}"
echo fmt"value of someOrdinal: {someOrdinal}"
echo fmt"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 strformat, typetraits
type
CompassDirections = enum
cdNorth, cdEast, cdSouth, cdWest
for direction in ord(low(CompassDirections)) .. ord(high(CompassDirections)):
echo fmt"{CompassDirections(direction)} ({$type(CompassDirections(direction))}), " &
fmt"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 18 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.
TO BE FIXED Ordinal methods on enums with holes #
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 "inc on holey enums"
echo " ", nonOrdinal
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
inc on holey enums
sigQuit
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
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.
type
Dollars = distinct float
proc `$`(d: Dollars): string {.borrow.}
proc `*` *(a, b: Dollars): Dollars {.borrow.}
proc `+` *(a, b: Dollars): Dollars {.borrow.}
var a = Dollars(20)
a = Dollars(25.0)
echo a
a = 10.Dollars * (20.Dollars + 1.Dollars)
echo a
25.0
210.0
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
echo value.a
0
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"
1.". 1\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.
A note about Unicode #
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 strformat
let str = "કૌશલ"
echo fmt"str is just one unicode char {str}"
echo "Here is how it looks when str is parses char-by-char:"
echo fmt" str = {str}, number of chars = {str.len}"
for i, c in str:
echo fmt" char {i} = {repr(c)}"
import unicode
echo "Here is how it looks when str is parses rune-by-rune:"
echo fmt" str = {str}, number of Runes = {str.runeLen}"
for i, r in str.toRunes:
echo fmt" Rune {i} = {r}"
str is just one unicode char કૌશલ
Here is how it looks when str is parses char-by-char:
str = કૌશલ, number of chars = 12
char 0 = '\224'
char 1 = '\170'
char 2 = '\149'
char 3 = '\224'
char 4 = '\171'
char 5 = '\140'
char 6 = '\224'
char 7 = '\170'
char 8 = '\182'
char 9 = '\224'
char 10 = '\170'
char 11 = '\178'
Here is how it looks when str is parses 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.
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.
TODO Bitsets #
Check if a character is in a character set (in
) #
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
) #
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
TODO Varargs #
TODO Object Oriented Programming #
TODO OOP Macro #
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 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 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 parseutils # https://nim-lang.org/docs/parseutils.html
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 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 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 unittest
suite "something":
test "passing test":
check:
true == true
test "failing test to be ignored":
check:
true == false
skip
[Suite] something
[OK] passing test
nim_src_bl1JT1.nim(13, 11): Check failed: true == false
true was true
false was false
[SKIPPED] failing test to be ignored
Expecting failures (expect
) #
import 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.
Miscellaneous #
Checking if something compiles (compiles
) #
From docs:
Special compile-time procedure that checks whether x can be compiled without any semantic error. This can be used to check whether a type supports some operation.
when compiles(int):
echo "int type exists"
when not compiles(Foo):
echo "Foo type does not exist"
when compiles(3 + 4):
echo "'+' for integers is available"
when not compiles(3 ... 4):
echo "'...' for integers is not available"
int type exists
Foo type does not exist
'+' for integers is available
'...' for integers is not available
Thanks to @mratsim from GitHub for this tip.
Checking if an identifier is declared #
From the docs:
Special compile-time procedure that checks whether x is declared. x has to be an identifier or a qualified identifier. This can be used to check whether a library provides a certain feature or not.
The compile-time procedure declared
returns true
if a
variable/type/proc/etc. is declared.
declared
and variable #
declared(foo)
returns true even if foo
is not explicitly
initialized.
var
foo: bool
bar: bool = true
echo "Is the uninitialized variable 'foo' declared? ", declared(foo)
echo "Is the initialized variable 'bar' declared? ", declared(bar)
echo "Is the undeclared variable 'zoo' declared (duh)? ", declared(zoo)
Is the uninitialized variable 'foo' declared? true
Is the initialized variable 'bar' declared? true
Is the undeclared variable 'zoo' declared (duh)? false
declared
and type #
declared
is also used to check if something is a valid type.
type
Foo = int
echo "Is 'Foo' declared? ", declared(Foo)
Is 'Foo' declared? true
This is useful to check for a type that could be introduced in a new
Nim version – SomeFloat
replaced the SomeReal
type in Nim
0.18.1. So the below code can be used to define the new SomeFloat
type only for older versions (without needing to do explicit version
checks):
when not declared SomeFloat:
type
SomeFloat = SomeReal
Also see compiles
.
declared
and proc #
.. and the same for procs.
proc Foo() = discard
echo "Is 'Foo' declared? ", declared(Foo)
Is 'Foo' declared? true
Checking if a compilation switch is present (defined
) #
Special compile-time proc to check if a certain -d:foo
or
--define:foo
switch was used during compilation.
From Nim Docs – defined
:
Special compile-time procedure that checks whether x is defined. x is an external symbol introduced through the compiler’s -d:x switch to enable build time conditionals:
when not defined(release): # Do here programmer friendly expensive sanity checks. # Put here the normal code
For example, below code was compiled and run using nim c -r -d:foo
--define:zoo test_defined.nim
:
echo "Is '-d:foo' or '--define:foo' flag passed to the compiler? ", defined(foo)
echo "Is '-d:bar' or '--define:bar' flag passed to the compiler? ", defined(bar)
echo "Is '-d:zoo' or '--define:zoo' flag passed to the compiler? ", defined(zoo)
Is '-d:foo' or '--define:foo' flag passed to the compiler? true
Is '-d:bar' or '--define:bar' flag passed to the compiler? false
Is '-d:zoo' or '--define:zoo' flag passed to the compiler? true
Thanks to @Yardanico from GitHub and @mratsim from GitHub to help understand this proc’s use.
Changing the nimcache/
directory #
Below tip by @zah from GitHub shows how to create the nimcache/
directory
in /tmp
by default.
You can place this in <nim folder>/config/nim.cfg
or in
~/.config/nim.cfg
:
@if unix:
@if not release:
nimcache = "/tmp/nimcache/d/$projectName"
@else:
nimcache = "/tmp/nimcache/r/$projectName"
@end
@end
@if windows:
@if not release:
nimcache = r"C:\Temp\nimcache\d\$projectName"
@else:
nimcache = r"C:\Temp\nimcache\r\$projectName"
@end
@end
Compiling in 32-bit mode #
Thanks to this tip by @Yardanico from GitHub, a Nim program can be compiled in 32-bit mode by doing:
nim c --cpu:i386 --passC:-m32 --passL:-m32 foo.nim
He provides a disclaimer to that saying “assuming your GCC is multilib”. I believe that’s the case – I don’t know! – because that worked 😄
From int
types, one way to check if the compilation is happening in
32-bit vs 64-bit is to look at the size of the int type.
In 32-bit compilation:
import strformat
echo fmt"Size of int = {sizeof(int)} bytes"
Size of int = 4 bytes
In 64-bit compilation:
import strformat
echo fmt"Size of int = {sizeof(int)} bytes"
Size of int = 8 bytes
Splitting Org header args #
import strutils
let args = """#+begin_src nim :tangle "foo.nim" :shebang "#!/usr/bin/env bash""""
echo args.split(" ") # parses arg vals with spaces incorrectly
echo args.split(":") # better
echo args.split(":")[0].strip.split(" ")
@["#+begin_src", "nim", ":tangle", "\"foo.nim\"", ":shebang", "\"#!/usr/bin/env", "bash\""]
@["#+begin_src nim ", "tangle \"foo.nim\" ", "shebang \"#!/usr/bin/env bash\""]
@["#+begin_src", "nim"]
NEED TO UNDERSTAND Pragmas #
noinit
#
Prevents auto-initialization of local variables. So such variables would then hold random values.
proc a() =
var ai {.noinit.}: array[5, int]
echo ai
a()
[0, 4254186, 0, 140737207612416, 0]
See Uninitialized variables for details.
dirty
#
I got curious why {.dirty.}
pragma was added in this parsetoml
commit:
template defineGetProcDefault(name: untyped,
t: typeDesc,
doccomment: untyped) {.dirty.} =
proc name*(table: TomlTableRef,
address: string,
default: t): t =
..
and @PMunch from GitHub gave this nice explanation:
Normally a
template
is what you call hygienic. This means that none of the symbols in the template is visible outside of it, and will not clutter the namespace of the context it is called in or interfere with anything. Imagine calling a template twice which instantiates a temporary variable for example, this would fail if the template wasn’t hygienic as the variable would already exist when the template was called the second time.In this case we only use the template to generate some procedures, and we want those to be injected into the main scope as is. The problem before was that the
table
,address
, anddefault
arguments would get changed to something liketable243829
,address231984
, anddefault290432
in the generated code. This would mean that if you wanted to name your arguments you would have to use those names, not very pretty. Plus the documentation used those names and looked over-all a bit ugly. By marking the template as{.dirty.}
we stop this behaviour and the argument names stay the same. Since all these templates does is create new procedures, which we are sure don’t collide with any name, we can do this safely.
borrow
#
See Distinct Types.
Questions/Doubts #
NEED TO UNDERSTAND What are ptr
and addr
? #
See the cast
example in section Specifying Types where those get
used.
DONE Better understand the Nim “do notation” syntax #
See the example in First Class Functions. Does that notation always have to be on one line? Can it be broken across multiple lines for better readability?
See Do notation.
References #
- If using Nim 0.18.0 or older, use
nim --advanced
. [return] - The manual uses this pragma exchangeably as
{.noInit.}
and{.noinit.}
at various places in documentation and Nim core code.. that was confusing. Actually that case of the innerI
does not matter. I will just use{.noinit.}
in this document for consistency. [return]