Tutorial on Using Explang with Algebraic Syntax
Expressions
An Explang program is a sequence of one or more expressions which are evaluated from the beginning to the end.
The language works with expressions that return values. There are no statements.
Expressions are separated by ';' and white-space is not significant;
The simplest expressions are things like numbers, booleans or strings, which evaluate to themselves.
> 10
=> 10
> true
=> true
> "foo bar"
=> foo bar
Explang supports regular algebraic syntax for building complex expressions using operators and calls to functions:
10 * (1 + sqrt(2))
=> 24.14213562373095
All the operators like '+', '-', '*', etc are also available for use
in functional form, for example, the same expression using +
and *
in functional form:
*(10, +(1,sqrt(2)))
=> => 24.14213562373095
The advantage of functional form is that you can call operators not with just two, but with any number of arguments:
+(1,2,3)
=> 6
+(1)
=>1
+()
=>0
Comments
A line comment starts with the #
character and runs until end of the line:
> "foo" # a comment
=> "foo"
Explang supports block comments as well, they are started by #=
and run until
first occurrence of =#
:
> 1 + #= a block comment =# 2;
=> 3
Symbols and their Values
The expressions +
and *
here are Symbols.
Symbols don't evaluate to themselves but return values they have been assigned.
If we give b
the value 10, it will return 10 when evaluated:
> b:=10
=> 10
> b
=> 10
If we try to evaluate a Symbol that has not been assigned a value we'll get error[^1].
Note that evaluation of b:=10
when b
still was unassigned did
not cause an error.
The symbols that start with letter ':' are special, they are
keywords
-- symbols that are always assigned values of themselves.
> :something
=> :something
They may be used for different kinds of identifiers: keys in map, names of parameters, etc.
Creating lists
To create a list of values use the [ ]
operator
> [1, 2 + 3, "foo", :b]
=> [1, 5, foo, b]
This operator is equivalent to function list
:
> list(1, 2 + 3, "foo", :b);
=> [1, 5, foo, b]
Defining Functions
We've already seen some functions: +
, *
. You can define new ones
with the function
operator. Here's a function that takes two numbers
and returns their average:
> function average (x, y)
(x + y) / 2;
end;
=> io.opsit.explang.Compiler$LAMBDA$1@27fe3806
The operator expects a symbol to use as the name, a list of symbols that describe the parameters, and then zero or more expressions called the body.
When the function is called, those expressions will be evaluated in order with the parameter symbols in the body temporarily set ('bound') to the corresponding argument values. Whatever the last expression returns will be returned as the value of the call.
The body of the function consists of one expression, (x + y) / 2
.
It is common for functions to consist of one expression; in purely
functional code (code with no side-effects) they always do.
What is the object returned as the value of the function
expression?
That's the Java Object that represents the function.
Now the symbol average
is assigned function value and you can invoke
it in the same way you invoke the built-in functions like +
or *
:
> average(100,200);
=> 150
As the literal representation of a string is a series of characters
surrounded by double quotes, the literal representation of a function
is the language keyword function
, followed by its
parameters, followed by its body. So you could represent a function to
return the average of two numbers as:
> function (x, y) (x + y) / 2; end;
=> io.opsit.explang.Compiler$LAMBDA$1@37a71e93
And you can call a literal function just like you can call it using the symbol, e.g.
> (function (x,y) (x+y)/2; end)(100,200)
=> 150
Short Lambda Definitions
When the function body consists only of one function there is a second, shorter syntax for defining an anonymous function in Explang.
The traditional syntax demonstrated above is equivalent to the shorter version:
> (x,y)-> (x+y)/2
=> io.opsit.explang.Compiler$LAMBDA$1@71d15f18
And when there is only one argument you can use even simpler form:
x -> x * 2
=> io.opsit.explang.Compiler$LAMBDA$1@38364841
Function Values
Notice that if you try to get the function object simply by referencing the average
symbol you get an error.
> average
EXECUTION ERROR: java.lang.RuntimeException: variable 'average' does not exist in this context at:
This is because of an important property of Explang: named functions
and ordinary data variables are living in separate namespaces, that is
you may assign value to variable average
without overwriting the
function average
:
> average:=100;
=> 100
> average(average, 200)
=> 150
To reference the value of the defined function one must use special syntax:
> f"average"
=> io.opsit.explang.Compiler$LAMBDA$1@5e9f23b4
To set the function binding one can use the fset
function, all the
'function' operator does is basically:
> fset( :average, (x,y)-> (x+y)/2);
=> io.opsit.explang.Compiler$LAMBDA$1@28c4711c
Getting Help on Functions
To get short description of a function or a special operator use
describe_function
.
> describe_function("random")
=> random is a built-in function defined at class io.opsit.explang.Funcs$RANDOM
Arguments: limit
Documentation:
Produce Pseudo-Random Number. Returns a pseudo-random number that is a non-negative number less than limit and of the same numeric type as limit. Implemented using Java Math.random()
Package: base.math
describe_function
works both with the built-in as well with the
user-defined functions. For the latter ones the function description
is the value of so called documentation strings - "docstrings". if
the first expression in the function is a string literal it is
considered to be documentation string:
> function average (x,y)
"Compute average of two numbers.";
(x + y) / 2;
end;
=> io.opsit.explang.Compiler$LAMBDA$1@37a71e93
> describe_function("average")
Arguments: x, y
Documentation:
Compute average of two numbers.
Package: user
To get list of all the available function use functions_names
:
> functions_names()
=> [%, *, +, -, ->, ->>, ., .N, .S, /, <, <=, =, ==, ===, >, >=, @->, AND, APPEND
...
Printing values
So far we've only had things printed out implicitly as a result of
their evaluation. The standard way to print things out in the middle
of evaluation is with print
or println
. They take multiple
arguments and print them in order; println
also prints a newline at
the end. Here's a variant of average that tells us what its arguments
were:
> function average (x,y)
println("my arguments were: ",x,", ",y, "\n");
(x + y) / 2;
end;
> average(100,500);
my arguments were: 100, 500
=> 300
Return Value of the Function
From the previous example we see that when a function contains several expressions the return value of the function is the value of the last expression in the function. Previous expressions are evaluated and their value is discarded.
If you want to return value from function without reaching the last
expression use the return
operator.
In the following example we define function that solves quadratic equations. In the case when determinant is negative we print an error message and exit prematurely returning an empty list.
> function quadratic_eq_roots(a,b,c)
"Finds real roots of an quadratic equation";
discriminant:= b * b - 4 * a * c;
if discriminant < 0
println("Error: the equation has no real solutions!");
return [];
end;
[ ((- b) - sqrt(discriminant)) / (2 * a) ,
((- b) + sqrt(discriminant)) / (2 * a)];
end;
> quadratic_eq_roots(2, 4, -4)
=> [-2.732050807568877, 0.7320508075688772]
> quadratic_eq_roots(1, 2, 3)
Error: the equation has no real solutions!
=> []
Optional and Vararg Arguments for Functions
We've seen above some functions like list
and +
that take varying
numbers of arguments. Now we'll learn to define such functions.
Function parameters list can contain extra directives that specify how the following parameters will be handled.
To make the parameters optional set its default value
using the assignment :=
operator.
function greet(name, o := NIL)
str("hello ", name, o);
end;
=> io.opsit.explang.Compiler$LAMBDA$1@2e4b8173
> greet("Joe", "!!!! :-)")
=> hello Joe!!!! :-)
> greet("Joe")
=> hello Joe
The expression for the default value does not have to be a constant. It can be any expression that can refer to values of preceding parameters:
> function greet(name,
t1:="",
t2:= if t1=="Mr." ", Sir" else if t1=="Mrs." ", Ma'am" end; end)
str("Hello ", t1, " ", name, "! Nice to see you", t2, "!");
end;
> greet("Figman", "Mrs.");
=> Hello Mrs. Figman! Nice to see you, Ma'am!
> greet("Alice");
=> Hello Alice! Nice to see you!
Sometimes it is convenient to create functions that can take any number of arguments. Such functions are often called "varargs - variable number of arguments" functions. In Explang you can make such a function by following the last positional argument by ellipsis:
> function foo(x, y, z...)
[x, y, z];
end;
=> io.opsit.explang.Compiler$LAMBDA$1@37f8bb67
> foo(1+2, 3 + 4, 5 + 6, 7 + 8)
=> [3, 7, [11, 15]]
To supply a list of arguments to a function we can use the apply function:
L:=[1, 2, 3];
=> [1, 2, 3]
> apply(f"+", L)
=> 6
Now that we have rest parameters and apply, we can write a version of average that takes any number of arguments.
function average(args...)
apply(f"+", args) / length(args);
end;
=> io.opsit.explang.Compiler$LAMBDA$1@3d646c37
> average(1, 2 ,3)
=> 2
Keyword arguments
Keyword arguments are used when we want to pass a specific value to a specific argument. Consider a scenario where there are 3 optional arguments, what if the user wants to pass just a second optional argument's value, here the first arguments even though is optional it will be mandatory for him to pass its value making it a required argument.
To solve this problem keyword arguments are used where the caller has a choice to specify for argument values to which particular argument they will be passed.
To define keyword arguments in a function, add ';' after defining the positional arguments (if any). All the arguments after ';' will be keyword arguments.
> function keyword_example (;x, y, z)
[x, y, z]
end;
=> io.opsit.explang.Compiler$LAMBDA$1@41cf53f9
Keyword argument are passed as assignments of arguments and their values:
> keyword_example(z:=3, x:=11, y:= 2)
=> [11, 2, 3]
The keyword arguments are optional, you can call this function without
setting all the arguments, the default argument value is NIL
.
> keyword_example();
=> [null, null, null]
> keyword_example(x:=1, y:=2)
=> [1, 2, null]
You can set default value of the keyword arguments just like with optional positional arguments:
> function keyword_example (; x:=1, y:=2, z:=x + y)
[x, y, z];
end;
=> io.opsit.explang.Compiler$LAMBDA$1@2ef1e4fa
> keyword_example();
=> [1, 2, 3]
Very much like with the positional vararg functions you can allow functions to get any number of keyword parameters in addition to those that are named in the argument list.
In the following example the 'r' argument, which is marked with an ellipsis is used to collect pairs of argument names (keywords) and their values.
> function otherkeys_example (x ; y, r...)
[x, y, r];
end;
=> io.opsit.explang.Compiler$LAMBDA$1@5cb0d902
> otherkeys_example(1)
=> [1, null, []]
> otherkeys_example (1, y:=2, p:=3, q:=4);
> [1, 2, [:p, 3, :q, 4]]
Data Types and Values
Explang is a dynamically typed language. There are no type definitions in the language: each value carries its own type and those types are Java types.
The language can work with Java Objects of any type and it has built-in support for standard data types such as Strings, Numbers, Arrays, etc.
To get type of a value we use function type_of
:
> type_of("foo")
=> class java.lang.String
> type_of(1.0)
=> class java.lang.Double
To check if object is of some specific type use the typep
operator:
> typep("abc", "java.lang.String")
=> true
Base Java type from the java.lang
can be specified without package:
> typep("abc", "String")
=> true
The check returns true for the specific class as well as for class parents and the interfaces that it implements:
> typep("abc", "CharSequence") # String implements CharSequence
=> true
The second arguments can be a string as well as symbol:
> typep("abc", "Object") # String implements CharSequence
=> true
The type of NIL is NIL.
> typep(NIL, NIL);
=> NIL
NIL values
NIL represents absence of a useful value, it is implemented as Java null
.
It is not really a data type.
type_of(NIL)
=> NIL
Booleans
The Boolean type has two values, false
and true
. It is implemented using
as Java Boolean objects.
Numeric Types And Values
Explang works with Java numeric types.
Here table of types with examples of literals,
- 1b, 0b -- Byte
- 1s, 2s -- Short (16 bit signed integer)
- 1, -2 -- Integer (32 bit signed integer)
- 1L 0L -- Long literals (64 bit signed integer)
- 0.5f 1.2e-2f -- Float literals (32 bit floating point)
- 0.5 1.2e-2 -- Double literals (64 bit floating point)
> type_of(1)
=> class java.lang.Integer
> type_of(1b)
=> class java.lang.Byte
> type_of(0.1e4)
=> class java.lang.Double
Numeric Types Promotions
When one performs arithmetic operations between different numeric types Explang performs automatic promotion of numeric types, i.e. the result of the operation will be of the type of the argument with highest precision. Operations between integer types and floating types are promoted to floating point types:
> type_of(1b + 1s)
=> class java.lang.Short
> type_of(1b + 1s + 1 + 1L + 1.0)
=> class java.lang.Double
Note that Explang does not perform promotion for overflows between operations:
> 100s * 1000s # 100000 won't fit into 16 bit Short Integer,
# so we'll get wrong result
=> -31072
Converting to Numeric Types
The operators byte
, double
, float
, int
, long
, short
convert to corresponding
numeric types from other numeric types as well as from string representation:
> double(1L)
=> 1.0
> double("1E4")
=> 10000.0
Strings
Explang strings are Java Strings. Strings are constant, that is one cannot change an existing string but have to create new one.
In code we can delimit literal strings by matching double quotes "
:
> " a line"
=> a line
String literals can have embedded newlines, tabs and any other characters except "
:
> "line1
line2
line3"
=> line1
line2
line3
"
one need to escape it using the \
escape character:
> "quoted word \"foo\""
=> quoted word "foo"
There are other escape characters supported:
- \b - backspace 0x08
- \t - tab 0x09
- \n - newline 0x0a
- \f - form feed 0x0c
- \r - carriage return 0x0d
Other characters when escaped will be inserted as they are:
> "\\t means tab '\t'\n"
=> \t means tab ' '
To convert any object into its string representation use
the string
operator:
> string(1 + 2)
=> 3
> string(NIL)
=> NIL
There are a couple of operators for building strings. The most general is string, which takes any number of arguments and mushes them into a string.
The most general is str
, which takes any number of arguments and
concatenates them into a string. Every argument will appear as it would
look if printed out by pr, except nil, which is ignored.
> str(99, " bottles of ", :bee, 'r')
=> "99 bottles of beer"
To control exactly the way the objects are converted to string use
the format()
function, which converts its argument to string
using the first argument as format specification:
x:=9786;
format("Decimal: %d, hexadecimal: %x octal: %o char: %c \n",x,x,x,x);
=> Decimal: 9786, hexadecimal: 263a octal: 23072 char: ☺
uppercase()
and lowercase()
can be used to change case of a string:
Strings in Explang are considered a kind of indexed sequences
and can be operated upon using generic functions that work with sequences,
for example to get substring one should use subseq()
,
to get a specific character one uses subscript operator or accessor functions
like get
, see below on how to work with sequences.
String Interpolation
Constructing strings using the format
function or string concatenation
may become cumbersome. To reduce need for these verbose calls and to allow
string representation in code be more similar to the program output Explang
allows string interpolation using the $( expression )
syntax in
string literals that are prefixed with flag i
:
> a := 1; b:= 2; str:=i"$(a) + $(b) = $(a+b)";
=> 1
=> 2
=> 1 + 2 = 3
Note that string interpolation is strictly a syntax level feature: one cannot
build interpolated string dynamically at runtime (unless using eval
to build
and evaluate the code). Another interesting property of string interpolation
is that one does not have to escape the double quotes in the interpolated
expressions, the following interpolated string literal may look strange, but
it is completely valid, note recursive use of interpolation inside the if
expression:
> result:="oops";
=> oops
> i"Result: $(if result=="ok" "Alright" else i"too bad: the value is $(result)"; end)";
=> Result: too bad: the value is oops
Character Type
Explang Characters represent Unicode 16-bit characters and implemented using Java java.lang.Character.
Character literals are delimited using the 'c', '\UHEX','\uHEX' or '\ESCAPECHAR' notation:
> [ 'H', 'e', 'l', 'l', 'o', '\n', '\u263a' ];
=> [H, e, l, l, o,
, ☺]
The char
operator allows to create Characters from their numeric code:
> char(33)
=> !
Characters can be used as operand of arithmetic expressions:
char(1 + 'A')
=> B
Collection and Sequence types
Explang attempts to provide operators that can be used on sequence and collection like objects of different types: lists, arrays, sets, hash maps and so on.
Lists
Lists contains entries (objects or null) in slots that can be addressed by their zero-based index. Lists do not have fixed size, they grow/shrink dynamically when elements are added/removed.
The list
operator creates and initializes a list. By default the
lists created are implemented by
java.util.ArrayList.
> list(1, 2, list( 3, 4));
[1, 2, [3, 4]]
Call to list()
may be abbreviated using the '[ ]' syntax:
> [1, 2, [3, 4]]
=> [1, 2, [3, 4]]
Maps
Use the hashmap
to create and initialize a Map
(implemented by java.util.HashMap).
> hashmap("a", 1, "b", "foo", "c", hashmap("d", "e"))
=> {a=1, b=foo, c={d=e}}
Call to hashmap()
may be abbreviated using the '{}' syntax:
> { "a" : 1, "b" : "foo", "c" : { "d" : "e"}}
=> {a=1, b=foo, c={d=e}}
Note, that [] and {} syntaxes together allow to initialize data structures using a JSON compatible notation.
Sets
A Set is a collection that contains no duplicate elements. More
formally, sets contain no pair of elements e1 and e2 such that e1 == e2
would return true.
As implied by its name, this type models the mathematical set abstraction.
The hashset
operator creates a set and initializes it with the specified elements:
hashset(1, 2, 3, 7)
=> [1, 2, 3, 7]
hashset(1, 1, [1, 2, 3], [1, 2, 3], NIL, NIL)
=> [null, 1, [1, 2, 3]]
Arrays
Arrays are areas of memory that contain entries in slots that can be addressed by their zero-based index. Arrays have fixed size that determined at their creation time. Unlike lists arrays are created to contain objects or primitive values of given type.
The make_array
operator creates array and initializes it with
specified elements.
By defaults make_array
creates array of type Object that can contain any java object.
> make_array(1,"123", list(1,2), NIL) # array with specified objects
=> [1, 123, [1, 2], null]
> make_array(size:=5) # array of specified size
=> [null, null, null, null, null]
> make_array(size:=5, elementType:="double") # array for storing primitive double values
=> [0.0, 0.0, 0.0, 0.0, 0.0]
> make_array(size:=10, ; array of size 10
elementType:="char", ; for storing primitive character values
'H', 'E', 'L', 'L', 'O') ; partially filled
=> [H, E, L, L, O,