Playing around with Sibilant

PUBLISHED ON JULY 4, 2020

I was recently playing around with Sibilant, a language that is stylistically similar to Lisp and compiles to JavaScript, and figured that I would give a brief overview of some of the interesting concepts. There are a whole bunch of languages that compile to JS, and even some others that are also stylistically similar to Lisp like ClojureScript.

Getting started

Let’s see some example Sibilant code and see how it compiles to get a better understanding of how it works. You can also follow along by installing Sibilant and starting a REPL in your console. If you have Node.js installed, you can just install using npm. You can check by running

node -v && \
npm -v

in the console. If both versions are not printed, then you will have to install. Alternatively, follow the instructions below to install and if you run into an error start backtracking. The official recommendation is to use a version manager for Node.js, and I would personally look into nvm. So, assuming that you have npm installed, you can install sibilant with

npm install -g sibilant

(The -g will update if you already have it installed) Since you may need to run as root, I would recommend double checking the official documentation before you run anything with sudo. Who trusts running commands as root from random people?
Once you have successfully installed it, you can start the REPL with a simple

sibilant

Some Examples

In the following examples, we are running code in the sibilant interpreter. I have also included what the interpreter returns so that you can see how to Sibilant code is compiled into JS. s

Hello World

If we want to print “Hello World!”, we can run

sibilant> (console.log "Hello World!")
console.log("Hello World!");
"Hello World"

Variables

Just as in JS, we can create variables to store data. It will look something like this

sibilant> (var i 10)
var i = 10;

It’s also possible to use strings

sibilant> (var myString "This is my string")
var myString = 'This is my string";

or undefined variables

sibilant> (var x)
var x = undefined;

If we have a lot to create at once, that is also okay

sibilant> (var a 1, b 2, c 3)
var a = 1,
    b = 2,
    c = 3;

If we need to modify a variable, we can use the assign macro.

sibilant> (assign a 2000)
a = 2000;

Data Structures

Arrays and objects are supported. Instantiating an array is done like this

sibilant> [1 2 3 4 5]
[1, 2, 3, 4, 5]
result: [1, 2, 3, 4, 5]

It can also easily be set as a variable

sibilant> (var a [1 2 3 4 5])
var a = [1, 2, 3, 4, 5];

Similarly with objects,

sibilant> (var o {key1 101, key2 "my string", key3 [1 2 3]})
var o = {
  key1: 101,
  key2: "my string",
  key3: [ 1, 2, 3 ]
};

To modify the structures, use set or get

sibilant> (set o["key2"] "a new value")
result: 'a new value'
sibilant> (get a[1])
result: 2

Functions

functions can be defined as follows

sibilant> (def printName (myName)
            (var intro (+ "Hello, my name is " myName ))
            (console.log intro)
            intro)
var printName = (function printName$(myName) {
  /* printName eval.sibilant:1:0 */

  var intro = ("Hello, my name is " + myName);
  console.log(intro);
  return intro;
});

If no parameters are passed to the function, the parenthesis can be left empty.

Flow Control

When you have conditionals, the macros if, when, and will come in handy. For a single condition, we can use when.

sibilant> (when (= clock.minutes zero)
            clock.chime(clock.hour))
(function() {
  if (clock.minutes === 0) {
    return clock.chime(clock.hour);
  }
}).call(this)
result: "Chirp chirp chirp"

If we have different conditions to consider where, we can use if to create an “if/else if/else” pattern.

sibilant> (if coffeepot.full) 
            (drink size.large)
          (coffeepot.half) # else if
            (drink size.small)
          (coffeepot.quarter) #else if
            (do 
              (drink size.small)
              (make coffee))
          (coffeepot.refill) #else

Note the usage of the do macro above when multiple statements are required.

Iteration

We can iterate through arrays and other iterables

sibilant> (each (value) [1 2 3 4 5]
            (console.log value))
[ 1, 2, 3, 4, 5 ].forEach((function(value) {
  /* eval.sibilant:1:0 */

  return console.log(value);
}))
1
2
3
4
5

Prefix Notation

Since this is Lisp-like, all functions are done using prefix notation, where the operator (+, -, =, etc) come before the operands.

A Final Challenge

To put it all together, we could set up a tiny exercise.

Let’s say that the goal of the exercise is to take an array of strings and print a list of each distinct phrase and the number of occurrences in decreasing frequency. I will link to my answer so that you can think of how to do it or implement it without spoilers.

(def sort (input_strings)
  ;; Code goes here
  )
;; Here we can test some edge cases
(sort ["single string test"])
(sort [])
(sort [1 1 2 3 4])
(sort ["a" "a" "c" "b" "c" "b" "e" "a" "z" "e" "e" "e" "e" "e"])

You can find the link to a solution that I implemented here and you can find more documentation at the official website.

TAGS: TECHNOLOGY