(λ (x) (create x) '(knowledge))

Shiny New Lang

Oooh shiny moments with Nim · August 18th, 2022

I recently stumbled on the language Nim. And while normally I am unwont to enjoy a space delineated language I am really digging Nim. It's a little statically typed, statically compiling language. You can extend the built in libraries without thrashing them, which gives you a quasi-lispy hack-ability, and it's easy enough to read. Okay, maybe a little less easy to read than say Golang, but it's good enough, some {} wouldn't kill anyone. But seriously it's a cool little language!

Probably more exciting than that though is that it's fast, and it builds wicked small binaries. The compiler is nice and verbose and errors out early, so you don't have to guess which of the 18 errors the compiler reported come from the one actual issue (lookin at you Golang). And it can do some weird things like compile to Javascript! I don't know when I'd use that, but I feel like that's got to be a pretty useful feature right?

I kind of just dove right into Nim after reading the Learn Nim in Y Minutes guide, it was good enough to start reading code, but I found myself quickly reading through the official Nim tutorial guides to get a better understanding of how the language works. It's just different enough that you sort of need to grok the literature that's out there, but once you do it's a breeze to work with. I ended up immediately rewriting my little battery percentage calculator for my Droid. See I had previously written a really quick on in Golang, but the binary for it is 1.9m, and I'm nearly the point where my poor Droid is running out of space. All of these 6MB+ Golang binaries (or the 30MB+ common lisp ones) that do really stupidly simple things are really unnecessary here. Sure 1.9M isn't a big deal, but, well just look I feel like I'm really not wasting space with Nim.


batt|>> du -h *-battery
1.9M    go-battery
88K     nim-battery

Shaved off more than a megabyte! And it's also ever so fractionally faster than the Golang version too.


batt|>> time ./go-battery
79%
real    0m 0.03s
user    0m 0.00s
sys     0m 0.00s

batt|>> time ./nim-battery
79.0%
real    0m 0.00s
user    0m 0.00s
sys     0m 0.00s
  

Think of what I'll do with that fractional second back and that whole 1MB of disk space! The possibilities are limitless! Err, I guess technically they're limited to 1MB and a part of a second, but in aggregate there's amazing things here I'm sure.

Side by side, the Nim version ends up only being 15 lines of code, and the Golang version is 28. I'd say the Golang version is easier to read without knowing Golang though, it's kind of the intent of the language after all. But I can't be more pleased with this simple example.


package main
/* Report droid battery, semi accurately-ish.
   Author: Will Sinatra, License: GPLv3 */

import (
	"fmt"
	"io/ioutil"
	"log"
	"strconv"
)

func main() {
	max, min := 4351000, 3100000
	/* this sys/class file returns the string "int\n" instead of just int */
	nowbyte, err := ioutil.ReadFile("/sys/class/power_supply/battery/voltage_now")

	/*we have to truncate the \n with a slice of the array */
	nowslice := nowbyte[0:7]
	now, _ := strconv.Atoi(string(nowslice))
	
	if err != nil {
		log.Fatal(err)
	}

	perc := (100 - ((max - now) * 100)/(max - min))

	fmt.Println(strconv.Itoa(perc)+"%")
}
  


import strutils, std/math

#Expand proc & to concat float & strings
proc `&` (f: float, s:string) : string = $f & s

#calculate rough battery percentage from Droid4 voltage_now file
proc batt() : float =
  let
    max = 4351000
    min = 3100000
    now = readFile("/sys/class/power_supply/battery/voltage_now").strip().parseInt()
    perc = (100 - ((max - now) * 100) / (max - min))
  return round(perc)

echo batt() & "%"
  

Obviously the Golang version does do a little bit more than the Nim one, there's better error handling, but it's really not much different. This was a simple enough starting point for me to jump into a little bit of a larger small program to get a feeling for the library ecosystem. Nim comes with a package manager called nimble, and it works as you'd expect. It's pretty close to how Golang's packaging system works, though my experience with it thus far is utterly cursory. I didn't have to reach for a lot of community libraries because just like Golang Nim has a robust built selection of libraries. All of this just means that when I went to go make a multipart form upload POST helper for my paste service I didn't need to do anything crazy.


import os, httpclient, mimetypes, strutils
#nim c -d:ssl -d:release lcp.nim

#Paste a file to https://lambdacreate.com/paste
proc lcp() : string =
  var
    client = newHttpClient()
    data = newMultipartData()

  #Set lexical variables for configuration & auth
  let
    mimes = newMimetypes()            #  <- Instantiates mimetypes
    home = getEnv("HOME")             #  <- Grabs /home/username
    conf = home & "/.config/lcp.conf" #  <- Concats path to config file
    crypt = readFile(conf).strip()    #  <- Extract crypt key for /paste auth

  #If we get more or less than 1 argument error
  if paramCount() < 1:
    return "A file must be specified for upload"

  if paramCount() > 1:
    return "Only one file is expected."

  #-F key="crypt"
  data["key"] = $crypt
  #-F upload=@file
  data.addFiles({"upload": paramStr(1)}, mimeDb = mimes)

  #http POST, strip new line on return
  client.postContent("https://lambdacreate.com/paste", multipart=data).strip()

echo lcp()
  

32 little lines of Nim later and I've got a function paste helper, and once it's compiled it comes out to a whopping 504K, absolutely minuscule. Especially considering I'm just importing whole libraries instead of picking out the functions I actually need to import into the program. Prior attempts to do this with Fennel failed miserably, I think primarily because I couldn't get luassl to format the POST payload correctly. But Nim? No problem, in fact the the official documentation for std/httpclient describes exactly how to make a multi-part POST!

Hopefully these little examples got you curious, or even better excited, to give Nim a try. For me this fits a really nice niche where I want a batteries included, very well documented language that will be fast and absolutely minuscule. I'll probably leave my prototyping to Golang, but Nim's definitely finding a home in my tool-chain, especially considering that after only a few hours of poking and very cursory reading I'm already rewriting some of my existing tools, I think I definitely had an "Ooh shiny" moment with Nim. And I tend to get very much stuck on those.

Bio

(defparameter *Will_Sinatra* '((Age . 31) (Occupation . DevOps Engineer) (FOSS-Dev . true) (Locale . Maine) (Languages . ("Lisp" "Fennel" "Lua" "Go" "Nim")) (Certs . ("LFCS"))))

"Very little indeed is needed to live a happy life." - Aurelius