Blog - 0x00


Explorando Nim

2022/12/27

Tabla de contenidos


#

Nim es un lenguaje de programación y, mientras navegaba por Reddit, alguien lo sugirió en los comentarios como una alternativa a Python porque la sintaxis era similar, así que decidí revisarlo.

Según el sitio web de Nim, la descripción es:

Nim es un lenguaje de programación de sistemas compilado y de tipado estático. Combina conceptos exitosos de lenguajes maduros como Python, Ada y Modula.

La eficiencia es una de las prioridades de Nim.

Style-insensitive #

Una de las características controvertidas de Nim es la falta de sensibilidad al estilo (style-insensitive) para variables, funciones y nombres de tipos. Es la primera vez que veo un lenguaje de programación con esto y es extraño. Este es un archivo nim válido que compila y se ejecuta sin errores.

1
2
3
4
5
6
7
let foobar = true

assert(fOOBAR)
assert(f_o_o_b_a_r)
assert(foo_bar)
assert(foo_ba_r)
assert(fO_OB_AR)

Esta característica se puede desactivar para que puedas programar igual que en cualquier otro lenguaje con la bandera --styleCheck; por defecto esta opción está desactivada, pero puedes configurarla con hint, lo que compilará el programa pero mostrará un mensaje de aviso si las variables se ven iguales, o con error, en cuyo caso el programa no compilará si tienes variables que son similares pero no idénticas.

$ nim --styleCheck:error c vars.nim
vars.nim(3, 8) Error: 'fOOBAR' should be: 'foobar' [let declared in vars.nim(1, 5)]

Tipos de variables #

En Nim tienes diferentes tipos de variables y el compilador puede inferir el tipo de la variable basándose en su valor, lo que lo convierte en un lenguaje de tipado suave (loosely typed).

let #

El tipo de variable let es inmutable, lo que significa que una vez definido su valor no puede ser cambiado ni redefinido, lo que significa:

1
2
3
4
5
6
7
import std/strformat

let x = 1
echo(fmt"The value of x is {x}")

x = 2
echo(fmt"The new value of x is {x}")
$ nim -r c letvars.nim
letvars.nim(6, 1) Error: 'x' cannot be assigned to

Y si cambiamos en la línea 6 a let x = 2 obtenemos el siguiente error:

$ nim -r c letvars.nim
letvars.nim(6, 5) Error: redefinition of 'x'; previous declaration here: typevars.nim(3, 5)    

var #

Este tipo de variable es mutable, lo que significa que el valor asignado a una variable se puede cambiar más tarde.

1
2
3
4
5
6
7
import std/strformat

var x = 1
echo(fmt"The value of x is {x}")

x = 2
echo(fmt"The new value of x is {x}")
$ nim -r c varvars.nim
The value of x is 1
The value of x is 2

const #

Finalmente tenemos las constantes, como su nombre indica, son constantes e inmutables. La diferencia con el tipo de variable let es que las const se evalúan en tiempo de compilación, mientras que las expresiones let se evalúan en tiempo de ejecución. Así que, si sabes que no vas a cambiar el valor, deberías usar const.

1
2
3
4
import std/strformat

const x = 1
echo(fmt"The value of x is {x}")
$ nim -r c constvars.nim
The value of x is 1

Estructuras de datos #

Una de las estructuras de datos básicas utilizadas comúnmente en Python son las listas. De manera similar, Nim ofrece 2 estructuras de datos como las listas, cuyas características explicaré a continuación.

Sequences #

Este tipo de estructura de datos se comporta de manera similar a las listas en Python. Puedes crear una secuencia y añadir o eliminar elementos de la misma; el tamaño es dinámico. Sin embargo, no puedes mezclar diferentes tipos dentro de una lista como en Python, donde puedes tener enteros y strings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#[ 
  Definir explícitamente el tipo para la secuencia
  y añadir el número 7 a la secuencia
]#
var ages: seq[int] = @[3, 4, 2]
ages.add(7)

#[
  Usa la inferencia de tipos de nim
  para crear una secuencia y eliminar el elemento
  en la posición 1. (Segundo elemento)
]#
var fruits = @["apple", "watermelon", "pineapple"]
fruits.delete(1)

#[
  Lanza un error en tiempo de compilación 
  ya que las variables definidas con el tipo `let`
  son inmutables
]#
let woods = @["birchwood", "cedarwood", "cherry"]
woods.add("mahogany")

echo(ages[3]) # `7`
echo(fruits[1]) # `pineapple`

#[
  Lanza un error en tiempo de ejecución
  `Error: unhandled exception: index 4 not in 0 .. 3 [IndexDefect]`
]#
echo(ages[4])

#[
  Al igual que en Python, puedes iterar sobre una secuencia
  usando la sintaxis `for i in seq`
]#
for fruit in fruits:
  echo(fruit)

En lenguajes como C, una vez que los arrays se han inicializado, su tamaño no se puede modificar fácilmente, por lo que si defines un array de 10 elementos, para añadir otro elemento tienes que crear un nuevo array de tamaño 11 e incluir el nuevo elemento, o lidiar manualmente con la memoria, aunque hay una razón por la cual rust se está volviendo popular: porque impone la seguridad de memoria, algo que no es fácil de manejar.

Arrays #

En Nim, los arrays se comportan de manera similar a como lo hacen en C; tienen un tamaño estático y, una vez inicializados, su tamaño no se puede cambiar.

Una de las características de los arrays en Nim es que comprueba el acceso fuera de los límites en tiempo de compilación, a diferencia de C, que lanzará un error en tiempo de ejecución.

1
2
3
4
let fruits: array[3, string] = ["apple", "watermelon", "pineapple"]

echo(fruits[0]) # `apple`
echo(fruits[4]) # `se.nim(4, 6) Error: index 4 not in 0 .. 2`

Compilación #

El código Nim, como se mencionó en los primeros párrafos, puede ser compilado por el compilador de Nim en un ejecutable. Lo hace convirtiendo el código Nim a código C y luego utiliza un compilador de C para convertirlo en un ejecutable.

Por ejemplo, supongamos que tenemos un archivo llamado hello.nim con el siguiente código:

1
echo("Hello, World!")

Para compilarlo ejecutamos $ nim c hello.nim y obtenemos un ejecutable hello. Los archivos C generados se guardarán en $HOME/.cache/nim/hello_d/ y si revisamos los archivos generados en ese directorio, deberías ver esto:

$ ls .cache/nim/hello_d/
hello.json
@mhello.nim.c
@mhello.nim.c.o
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@sstd@sprivate@sdigitsutils.nim.c
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@sstd@sprivate@sdigitsutils.nim.c.o
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c.o
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c.o
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c
@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c.o

El archivo hello.json es generado y utilizado por el compilador de nim y contiene la información para compilar el programa con un compilador de C; en este caso GCC, aquí está la salida (reemplazé la ruta completa del home con $HOME):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
  "cacheVersion": "B10310425T123831",
  "outputFile": "$HOME/hello-nim/hello",
  "compile": [
    [
      "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@sstd@sprivate@sdigitsutils.nim.c",
      "gcc -c  -w -fmax-errors=3   -I$Hchoosenim/toolchains/nim-1.6.10/lib -I$HOME/hello-nim -o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@sstd@sprivate@sdigitsutils.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c"
    ],
    [
      "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c",
      "gcc -c  -w -fmax-errors=3   -I$HOME/choosenim/toolchains/nim-1.6.10/lib -I$HOME/hello-nim -o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c"
    ],
    [
      "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c",
      "gcc -c  -w -fmax-errors=3   -I$HOME/choosenim/toolchains/nim-1.6.10/lib -I$HOME/hello-nim -o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c"
    ],
    [
      "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c",
      "gcc -c  -w -fmax-errors=3   -I$HOME/choosenim/toolchains/nim-1.6.10/lib -I$HOME/hello-nim -o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c"
    ],
    [
      "$HOME/cache/nim/hello_d/@mhello.nim.c",
      "gcc -c  -w -fmax-errors=3   -I$HOME/choosenim/toolchains/nim-1.6.10/lib -I$HOME/hello-nim -o $HOME/cache/nim/hello_d/@mhello.nim.c.o $HOME/cache/nim/hello_d@mhello.nim.c"
    ]
  ],
  "link": [
    "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@sstd@sprivate@sdigitsutils.nim.c.o",
    "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c.o",
    "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c.o",
    "$HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c.o",
    "$HOME/cache/nim/hello_d/@mhello.nim.c.o"
  ],
  "linkcmd": "gcc   -o $HOME/hello-nim/hello $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@sstd@sprivate@sdigitsutils.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sdollars.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem@sio.nim.c.o $HOME/cache/nim/hello_d/@m..@s..@s.choosenim@stoolchains@snim-1.6.10@slib@ssystem.nim.c.o $HOME/cache/nim/hello_d@mhello.nim.c.o    -ldl",
  "extraCmds": [],
  "configFiles": [
    "$HOME/choosenim/toolchains/nim-1.6.10/config/nim.cfg",
    "$HOME/choosenim/toolchains/nim-1.6.10/config/config.nims"
  ],
  "stdinInput": false,
  "projectIsCmd": false,
  "cmdInput": "",
  "currentDir": "$HOME/hello-nim",
  "cmdline": "",
  "depfiles": [],
  "nimexe": ""
}

El archivo @mhello.nim.c es el que contiene el string “Hello, World!”

Por defecto, Nim compila en “modo debug”, lo que, como su nombre indica, activa ciertas comprobaciones y, cuando falla, te da un seguimiento de dónde falló en el código. Si activas el modo release mediante la bandera -d:release al compilador, se desactivarán las comprobaciones (lo que podría acelerar tu programa) y no se mostrará un seguimiento cuando falle.

Por ejemplo, supongamos que tenemos este programa, que tiene una función que toma una secuencia y, dada una secuencia y un número, imprimirá el índice + 1 de esa secuencia:

1
2
3
4
proc printFruits(listFruits: seq[string], i: int) =
  var x = 1
  x = x + i
  echo(listFruits[x])
$ nim c fruit.nim
26651 lines; 0.260s; 31.629MiB peakmem; proj: fruit.nim; out: fruit [SuccessX]
$ ./fruit
fruit.nim(9) fruit
fruit.nim(4) printFruits
~/.choosenim/toolchains/nim-1.6.10/lib/system/fatal.nim(54) sysFatal
Error: unhandled exception: index 5 not in 0 .. 2 [IndexDefect]
$ nim -d:release c fruit.nim
26651 lines; 0.248s; 31.645MiB peakmem; proj: fruit.nim; out: fruit [SuccessX]
$ ./fruit
fatal.nim(54)            sysFatal
Error: unhandled exception: index 5 not in 0 .. 2 [IndexDefect]

Como puedes ver, el modo debug muestra el seguimiento, primero el fruit.nim(9), que es la primera línea que llama a la función printFruits, y luego muestra la línea y el nombre de la función donde falló fruit.nim(4) printFruits. En modo release simplemente indica el error que intentó acceder al índice 5 cuando solo llega de 0 a 2, pero no dónde.

Reflexiones finales #

Nim parece ser un lenguaje muy prometedor, con una sintaxis similar a Python pero compila a C con herramientas como gcc, lo que lo hace portable.

Aunque el lenguaje es estable (en la versión 1.x.x), se están realizando cambios y discusiones para una versión 2.0.

Les recomiendo estar atentos a Nim y echarle un vistazo de nuevo una vez que lancen la versión 2.0.