Skip to content

Instantly share code, notes, and snippets.

@rscircus
Last active July 22, 2022 14:26
Show Gist options
  • Save rscircus/b3873b508a29137c34418a059507ce2c to your computer and use it in GitHub Desktop.
Save rscircus/b3873b508a29137c34418a059507ce2c to your computer and use it in GitHub Desktop.
I'm falling in love with nim...
# Nim (1.6.6) - let's get started
# These are my notes from nim-by-example :)
# that's a comment :)
echo "Hello, World!"
# Compiling:
# ==========
#
# You can define build tasks in 'tasks.json'
# in case you are developing in VSCodium or VSCode,
# else use this to compile the files:
#
# nim c -r --verbosity:0 <filename>
# c | compiles
# -r | runs
# --verbosity:0 | don't print debug output
#[ that's a
multiline comment]#
# Variables:
# ==========
#
# Supported are: 'let, var, & const'
# let's introduce them using functions,
# or procs/procedures as they are called in nim. :)
# First we define a proc to print into the console
# what we are focusing on right now:
proc printTopic(topic: string) =
echo ""
echo "========="
echo topic
echo "========="
echo ""
# Variables it is for now:
printTopic("Variables")
proc getAlphabet(): string =
var accm = ""
for letter in 'a'..'z':
accm.add(letter)
return accm
# above we introduced a few things between the lines :)
#
# - for loops
# - iterators (`..`)
# - a var is an object (`.add(letter)`)
# computed at compile time
const alphabet = getAlphabet()
# print the alphabet
echo alphabet
# mutable variables
# - specifying a type is optional
# - default initializations for primitive types exist
var
a = "foo"
b = 0
c: int
d: float
# immutable variables (is it still a variable? ;)
let
e = "foo"
f = 5
g: float = 3.14159
# g: float
# ^ would throw an error as the let symbol requires an initialization
# let's investigate what we got
a.add("bar")
echo a
# Let's create some errors just to see how it works.
# Play also with the --verbosity:n parameter.
# n = 2 is very interesting ;)
#
# uncomment the following lines
# alhpabet = "abc"
# d.add("bar")
# e.add("bar")
printTopic("Procedures")
# Procedures:
# ===========
# We introduced them already, as we needed them.
# Procedures have a 'return' trick, a special variable called result. Let's reimplement getAlphabet():
proc getAlphabet2(): string =
for letter in 'a'..'z':
result.add(letter)
echo getAlphabet2()
# Be aware to not overwrite it with introducing 'var result' as the semantics will stop working then.
printTopic("Type Casting")
# Casting:
# ========
var x = 5 #int
var y = "foo" #string
# will throw a compile-time error:
# x = y
# also this
# var x = 6
var x2 = int(1.0 / 3) #explicit
echo x2
var x3 = 1.0 / 3 #implicit
echo x3
var y_seq: seq[int] = @[] #empty sec needs type specification!
var z = "Foobar"
# A complex type casting example:
proc ffi(foo: ptr array[6, char]) =
echo repr(foo)
echo ""
ffi(cast[ptr array[6, char]](addr z[0]))
# Control Structures:
# ===================
printTopic("Control structures")
# Alongside control structures we will introduce a bunch of other topics.
# - blocks
# - labels
# we import two modules
import strutils, random
# initialize random number generator using a bunch of seeds from the OS
randomize()
let answer = rand(10) # random integer in 0..10
# blocks and if, elif, else
while false: # uncomment this, as this code will never execute otherwise :)
block ablock:
while true: #dangerous
echo "Guess the number in {0,...,10} I know?"
let guess = parseInt(readline(stdin))
if guess < answer:
echo "Too low, try again..."
elif guess > answer:
echo "Too high, try again..."
else:
echo "Correct"
break ablock # leaves the loop AND the block
# a single break would just quit the while-loop
# note how similar this 'switch/case' like structure is to if, elif, else
case "charlie":
of "alfa":
echo "A"
of "bravo":
echo "B"
of "charlie":
echo "C"
else:
echo "unrecognized letter"
# making use of ranges
case 'h':
of 'a', 'e', 'i', 'o', 'u':
echo "Vowel"
of '\127'..'\255':
echo "Unknown"
else:
echo "Consonant"
# the structure can return values
proc posOrNeg(num: int): string =
result = case num
of low(int).. -1:
"negative"
of 0:
"zero"
of 1..high(int):
"positive"
else: # this is unreachable in fact, a good linter would complain
"impossible"
echo posOrNeg(-1)
# Loops & Iterators:
# ==================
printTopic("Loops & Iterators")
# As with most things in nim even iterators are first class.
# The keywords continue and break work here as above.
# Let's create a dictionary:
type
CustomRange = object
low: int
high: int
iterator items(range: CustomRange): int =
var i = range.low
while i <= range.high:
yield i
inc(i)
iterator pairs(range: CustomRange): tuple[a: int, b: char] =
for i in range: # this uses CustomRange.items under the hood
yield (i, char(ord('a') + i))
for i, c in CustomRange(low: 0, high: 3):
echo "key:", i, " value:", c
# Now we leave the comfort zone. :)
# We define a new iterator here using generics.
# This is a reimplementation of '..' and we name it '...'.
# In this (and other ways) we can expand nim as language and introduce new language features on the go. Ain't this awesome? `:)
iterator `...`*[T](a: T, b:T): T =
var res: T = T(a)
while res <= b:
yield res
inc(res)
# We defined a new operator in that way - hence the backticks
# This works as if we would iterate through the range 0..5
for i in 0...5:
echo i
# One can also create inline iterators, however they are not as elegant, hence, I won't introduce them here.
# Closure iterators
# They hold their state and can be resumed any time.
proc cntTo(n: int): iterator(): int =
return iterator(): int =
var i = 0
while i <= n:
yield i
inc(i)
let cntTo20 = cntTo(20)
# slice of 0
echo cntTo20()
var output = ""
# raw usage to get 1..20
while true:
let next = cntTo20()
if finished(cntTo20): # watch closely, this is tricky - finished checks if there are items left in the iterator
break # out of while true:
output.add($next & " ")
echo output
# reset and demonstrate inline usage
output = ""
let cntTo9 = cntTo(9)
for i in cntTo9():
output.add($i)
echo output
# Procs
# =====
printTopic("Procs")
# Even though the compiler inferes a lot, parameters and return types need to be annotated
proc fibo(n: int): int =
if n < 2:
result = n
else:
result = fibo(n - 1) + (n - 2).fibo # this is called 'uniform function call syntax'
# it means that foo(a, b) is equivalent to
# a.foo(b)
echo "Fibonacci number 10: ", fibo(10)
# Encapsulation
# -------------
# Again, via annotation `*`. This exports it and makes it availabe for use by modules
# module1:
proc foo*(): int = 2
proc bar(): int = 3
# module2:
echo foo() # valid
echo bar() # will not compile - however work here. Try it yourself with two modules. :)
# Side-effect analysis
# --------------------
# nim supports functional programming.
# We have pragma(s) to control the compiler a bit. A pragma is a language construct that specifies how a compiler should process its input.
# We enforce this function to have no side effects
proc sum(x, y: int): int {.noSideEffect.} =
x + y
# Meanwhile we have the func keyword for a no-sideffect proc
func mult(x, y: int): int =
x * y
# We enforce this function to have no side effects
proc minus(x, y: int): int {.noSideEffect.} =
#echo x # this is a side effect, hence, it will throw an error -- comment it in to see :)
x - y
# Operators
# ---------
# Now the official introduction, ` signify an operator:
proc `$`(a: array[2, array[2, int]]): string =
result = ""
for v in a:
for vx in v:
result.add($vx & ", ")
result.add("\n")
echo([[1, 2], [3, 4]])
# ugly useless operator
proc `^&^@%`(a, b: string): string =
result = a[0] & b[high(b)]
# Is this true? Why?
assert("foo" ^&^@% "bar" == "fr")
# ^ what happens here? :)
# Generics
# --------
# Like C++ templates and have the same statically checked duck-typing semantics
let zero =""
proc `+`(a, b: string): string =
a & b
proc `*`[T](a: T, b: int): T =
result = zero
for i in 0..b-1:
result = result + a # calls + from above
# is this true? Add an 'a' somewhere.
assert("a" * 10 == "aaaaaaaaaa")
# First Class Functions:
# ======================
printTopic("First Class Functions:")
import sugar # get syntactic sugar
# -> via sugar
proc map(str: string, fun: (char) -> char): string =
for c in str:
result &= fun(c)
# => via sugar
echo "foo".map((c) => char(ord(c) + 1)) # That's what you might be used to from JavaScript
echo "foo".map(proc (c: char): char = char(ord(c) + 1))
# There are two more options to use closures
# one is using the `do` keywords, which I just ignore because it's not really a benefit
# and the other one is demonstrated here:
import sequtils
let powersOfTwo = @[1,2,4,8,16,32,64,128,256]
echo powersOfTwo.filter(proc (x:int): bool = x > 32)
# However, that is even more concise and elegant with sugar
echo powersOfTwo.filter((x) => x > 32)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment