A Tour of Eisen's Features - blancas/eisen GitHub Wiki

The following sections illustrate Eisen features and their correspondence to Clojure data and constructs, which are marked in bold.

We first load the Eisen core namespace, initialize the API and launch the Eisen repo.

(use 'blancas.eisen.core)
(init-eisen)
(eisen-repl)

After the last command we get the Eisen repo prompt. The repo will keep reading after hitting Enter. It will evaluate the code after it gets an empty line. To evaluate 3 + 4:

user: 3 + 4 <Enter>
> <Enter>
7
user: _

The following code snippets won't show the Eisen prompts for the sake of clarity. Copy and paste a snippet into the Eisen repo and hit Enter. The result of the evaluation is shown with the double-dash line comment.

Comments

Block comments are delimited by (* and *). Line comments start with a double dash -- and continue until the end of the line.

Expressions

Like Clojure, Eisen is an expression language, with no distinction between expressions and statements. Arithmetic and Logical expressions use the familiar infix notation, with the usual precedence and associativity.

3 * 4 + 10 * 3
-- 42
2 ** 8 - 1
-- 255

Integer division is denoted by a backslash \ to set it apart from Rations.

3 * 4 + 8 \\ 2
-- 16
3 / 4
-- 3/4

Function calls start with the name of the function followed by any arguments without parenthesis or commas. Their priority is higher than arithmetic and logical operators.

inc 5 ** 2
-- 36

Use parenthesis to change the order of evaluation.

inc (5 ** 2)
-- 26
map (fn x => x * x) [1 8]
-- (1 4 9 16 25 36 49 64)

Sequenced Expressions

A sequenced expression is the equivalent of a do expression in Clojure, where multiple, sequential expressions are used (for side-effects) and the last one is the value of the whole expression. In Eisen, sequenced expression are written separated by semicolons. If the last one has a semicolon it will just be ignored. The most common form is to write them inside do-end or similar delimiters.

if true then do
   println "side-effect";
   flush;
   100
end
-- side-effect
-- 100

Reserved Words

The following words are reserved for the Eisen syntax:

_,  asString, cond, case, declare, do, doseq, dosync, else, 
end, fn, for, fun, if, import, in, io!, let, letrec, locking,
loop, module, of, open, setq, setv, sync, then, val, when, 
whenFirst, while, with, withOpen, withString

Value Definition

A constant value is specified with a val declaration; similar to Clojure's def. A name in a val declaration should start with a letter or underscore, followed by alphanumeric characters or a single quote, exclamation point or question mark.

val size = 100
-- #'user/size

Multiple val declarations don't need a separator.

val base = 0
val north = base + 1
val south = base + 2
-- #'user/south

The semicolon is the expression separator. A single val declaration can be used like so:

val base = 0;
    north = base + 1;
    south = base + 2
-- #'user/south

Note that the last definition doesn't need to end in a semicolon, but one can be given.

The expression in a value declaration may be an anonymous function.

val square =  fn x => x * x
-- #'user/square
square 8
-- 64

Function Definition

A function definition looks like an equation. First there's the function name, then optional parameters, the equal sign, and a simple or sequenced expression as the function body.

(* A simple function *)
fun square x  =  x * x
-- #'user/square
square 12
-- 144

All Eisen functions are automatically curried. As a result, variable arguments or multiple arities are not supported.

fun scale x y n  =  n * x / y
-- #'user/scale
val twentyPercent = scale 20 100
val threeQuarters = scale 3 4
-- #'user/threeQuarters
twentyPercent 50
-- 10
threeQuarters 500
-- 375

Functions as Operators

Any function of two parameters may be used as a binary operator by writing it between dots.

fun plus x y =  x + y
-- #'user/add
35 .plus. 15
-- 50

Using Clojure Names

Not all Clojure names are valid Eisen names. While it is idiomatic to use dashes in Clojure names, this makes Eisen name ambiguous: first-step could mean a subtraction. To use Clojure names that don't follow the rules of Eisen identifiers simply write them in backquotes.

`take-while` even? [2,4,6,7,8,9]
-- (2 4 6)

Conditional Expression

Like the if expression but uses then after the test expression. The else part is optional. It results in nil if the test expression is false and no else expression is used.

(* A simple factorial function *)
fun fact n =
  if n == 0 then 1
  else n * fact (n-1)
-- #'user/fact
fact 5
-- 120

Cond Expression

This corresponds to a cond form. Clauses are separated by semicolons. Within a clause, expressions are separated by an arrow.

cond (1 > 3) => "one";
     (2 > 3) => "two";
     (3 > 3) => "three";
     (4 > 3) => "four"
end
-- four

When Expression

Works like the when macro. The do-end block may contain multiple expressions separated by semicolons.

when true do
   println "will add";
   3 + 4
end

When First Expression

Works like the when-first macro. The do-end block may contain multiple expressions separated by semicolons.

whenFirst x <- [747] do
    println "got a flight";
    println "it is a" x
end
-- got a flight
-- it is a 747

Let Expression

Works like let. Bindings are separated (though need not be ended) by a semicolon. The in-end block may contain multiple expressions separated by semicolons.

let base = 10.0;
    height = 15.0
in
    (base * height) / 2
end
-- 75.0

Letrec Expression

A letrec expression is similar to letfn, but it also accepts regular values. Use letrec when you need local functions that recursive or mutually recursive.

letrec 
   base = 0;
   isOdd? n  =  if n == base then false
                else isEven? (n - 1);
   isEven? n =  if n == base then true
                else isOdd? (n - 1)
in
   isOdd? 13
end
-- true

Loop Expression

Similar in syntax to let, this corresponds to loop and recur.

loop i = 0;
     x = 2
in
   if i < 8 then recur (i + 1) (x * 2)
   else x
end
-- 512

List Comprehension

The for reserved word introduces a list comprehension expression. It's followed by the generator(s) and filter(s) in square brackets; and finally an expression for list elements. The filters when, while and let work like their counterparts :when, :while and :let, and don't need separators.

for [ n <- [1 10]] n*n
-- (1 4 9 16 25 36 49 64 81 100)
for [ x <- [1 10], y <- [1 10] when x > y while x+y < 8] [x,y]
-- ((2 1) (3 1) (3 2) (4 1) (4 2) (4 3) (5 1) (5 2) (6 1))

Doseq Expression

This is similar to for but intended for evaluating a block of expressions for side effects. Instead of a single expression it accepts a in-end sequenced expression.

doseq [ n <- [1 5]]
   print n;
   if odd? n then println " is odd" 
   else println " is even"
end
-- 1 is odd
-- 2 is even
-- 3 is odd
-- 4 is even
-- 5 is odd

Case Expression

A case expression will match the result of an expression to values and evaluate the corresponding single or sequenced expression.

case 1+1 of
   0 => "0";
   1 => "1";
   2 => "2";
   3 => "3"
end
-- 2
case 10:#[20] of
  #[20,10] => "first";
  #[20,20] => "second";
  :else   => "last"
end
-- first

case can also match wildcards and most of what is available in clojure.core.match.

case 10:#[20] of
  #[20,_]  => "first";
  #[20,20] => "second";
  :else   => "last"
end
-- first

With Open Expression

Similar to a let, this corresponds to the macro with-open. Bindings for the open files follow the opening reserved word. One or more expressions go inside a do-end block.

withOpen rdr = clojure.java.io/reader "src/main/resources/hello/hello.clj"
do
   doseq [s <- `line-seq` rdr]
      println s
   end
end
-- (println "hello, world!")

As String Expression

Printed data is returned in a string, like the macro with-out-str. The starting reserved words may be followed by any number of sequenced expressions, then an 'end'.

asString
   print "Your equipment: ";
   print 737
end
-- Your equipment: 737

With String Expression

Uses the given string as stdin, like the macro with-in-str. Following the starting reserved words may be a single value or an expression. After that, any number of sequenced expressions go in a do-end block.

withString "787"
do
   print "Your equipment: ";
   let n = `read-line` in
       println n
   end
end
-- Your equipment: 787

While Expression

Works like the while macro. The do-end block may contain multiple expressions separated by semicolons.

val n = atom 0
while deref n < 5 do
  println (deref n);
  swap! n inc
end
-- 0
-- 1
-- 2
-- 3
-- 4
⚠️ **GitHub.com Fallback** ⚠️