hestia tutorial

hestia is a small, functional language built for scripting. This is a short, incomplete walkthrough of the language. Feel free to try things in the REPL below:


data types

hestia has no user-defined data types. While this limits expressive power, it also limits complexity. Here are the current data types in hestia, with examples:

Note that commas in hestia are whitespace, so (eq? 1000 1,000), (eq? {a: 1 b: 2} {a: 1, b: 2}), and (eq? [1, 2, 3] [1 2 3]) all evaluate to true.

Also note that {a: 1} is syntactic sugar (shorthand) for {'a: 1}, a map that contains one key-value pair: from the symbol 'a to the integer 1.

All data structures in hestia are immutable.There are no functions that modify data structures in-place. Instead, functions return NEW data structures.

named values

To globally name a value, use (def x 100). Later code can then reference x.

To locally name a value, use (let ([x 100]) x). Only code within the let expression can reference such names.

Global functions are just named functions, e.g., (def add1 {|x| (add 1 x)})

map and reduce

Probably the most important functions in hestia are lmap and lreduce. The l stands for list. For instance, to add 1 to every integer in a list, you call (lmap add1 [1 2 3]) which evaluates to [2 3 4]. lmap takes a function and a list as its arguments, and is defined as follows:

(def lmap { |f list|
  (if (empty? list)
      (cons (f (first list))
            (lmap f (rest list))))})

Let's break this down. First, an (if test then else) expression checks if test is true and if so, evaluates to then; if false, it evaluates to else.

Thus, lmap first checks if the given list is empty, and if so, it simple returns an empty list. If not, it calls the given function on the first element of the list, and then recursively calls lmap on the rest of the list. It then uses cons, which takes a value and a list and returns a new list with that value prepended to the list, to return a new updated list.

However, all functions in hestia are automatically curried. To show what this means, consider that we could write the adding lmap equivalently as follows: (lmap (add 1) [1 2 3]) because add requires at least 2 arguments---when it receives 1 argument, it returns a function that takes (at least) 1 argument and adds 1 to it.

To sum all integers in a list, use (lreduce 0 add [1 2 3]). lreduce takes a "base" value, a function, and a list. Here's the source:

(def lreduce { |acc f list|
  (if (empty? list)
      (lreduce (f acc (first list)) f (rest list)))})

In our case, lreduce adds "pairs" of integers, with the first pair being 0 and 1, the second pair being the sum of the first pair and 2, and so on.

© semaj 2023