Last active
July 22, 2022 14:26
-
-
Save rscircus/b3873b508a29137c34418a059507ce2c to your computer and use it in GitHub Desktop.
I'm falling in love with nim...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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