Note: Things marked with (TBD) mean that feature is not yet functional.
TablaM is a language that combine relational, functional & imperative paradigms. Is expression based, meaning all code return values.
The syntax is made to be learned quickly, and be as consistent as possible.
Comments
-- Comments start with 2 'minus' symbol
---
--- Multiline? just repeat it!
---
Declarations
By default, the values are immutable except if explicitly marked as mutable (TBD). To declare a value you write:
let x := 1 -- immutable aka: final (java), const (js), it cannot be updated
var x := 1 -- mutable
Note how to assign a value is used the operator :=
. This is different to =
that is used to compare for equality.
Literals
Literals are values that are typed directly in the source code, and are the most primitive values of a programming language:
Numbers
1 --64 bit Integers
1.0 --64 bit Decimals (base 10)
1.0d --64 bit Decimals (with explicit suffix)
1.0f --64 bit floats (base 2, requiered explicit suffix)
Note how the Decimal
is the default floating point number, instead of the float
, that is more common.
This mean TablaM is tailored to do more exact arithmetic for business/financial purposes, instead of default for engineering like most languages.
With numbers it is possible to do some arithmetic and assignment operators
Math
1 + 1 -- = 2
2.0 - 1.0 -- = 1.0d
2.0f * 10.0f -- = 20.0f
10 / 2 -- = 5.0
1 + 2.0 -- Error: Type mismatch Int <> Dec
let num := 1
num += 1 -- arithmetic and assignment operators (TBD)
TablaM doesn't do invisible conversions of values. Is necessary to explicitly cast values to mix them. (TBD).
Similar to array languages/libraries like APL/kdb+/NumPy, the operators know how to work with scalars & collections like:
1 + 1 -- Scalar + Scalar
1 + [1; 2; 3] -- Scalar + Vector
[1; 2] + [3; 4] -- Vector + Vector, same rank
[1] + [3; 4] -- ERROR, different rank 1 <> 2
Booleans & Bits
true
false
1b -- Bit 1
0b -- Bit 0
010_1001b --Bit Array, can use _ for clarity
With Boolean values we can do Boolean expressions, so we can
Compare values (TBD):
1 = 1 -- = true
1 <> 1 -- = false
1 > 2 -- = false
1 >= 2 -- = false
1 < 2 -- = true
1 <= 2 -- = true
true and false -- = true
not true -- = false
and with bit/bitarrays do bit manipulation:
Bit operations (TBD):
let a = 0b0
let b = 0b_1b
a.and(b)
a.or(b)
a.xor(b)
a.not(b)
a.shift_left(b)
a.shift_right(b)
a.shift_right_zero(b)
TablaM has the concept of a "total order". It means all values can be compared in relation to the others. This is required for things like sorting to work. The total order is defined as:
Bit < Bool < Int < Float < Dec < Time < Date < DateTime <
Char < Str < Vec < Tree < Map < FFI Object < Any
You don't need to memorize this. Only to know that this feature exist.
Strings
All strings are encoded in valid UTF8.
"hello" -- Strings are enclosed with quotes
'hello' -- both '' or "".
"hello it's you" --so you can embed easily the opposite quote
"""
A long text
""" --- and triple quotes allow to enter longer text
"🍎" -- unicode can be used!
Dates (TBD)
Dates can be entered and validated at compile time if the string is prefixed & formatted as ISO date:
dt"2020-02-02T08:00:00" -- Full date time
d"2020-02-02" -- Just date time
t"08:00:00" -- Just time
Types
TablaM use a static type system, meaning all values are assigned a type and is not possible to use values where it doesn’t work (TBD, for now the types are checked at runtime).
Types are described in TitleCase like this:
Int
Dec
Float
Str
Date
DateTime
Time
Vec[it:Int]
Map[name:Str, age:Int]
Note how some values have a list of types inside []
. This is called the schema (or header) of the relation. ALL values have a schema:
Int = [it:Int]
Str = [it:Str]
Map[name:Str, age:Int] = [name:Str, age:Int]
This schema allows the relational operators to work and treat the values as they were tables in SQL.
Relations
The main feature of the language is the view of all values as relations. A relation is anything that can be described like "have a list of names
: Types
", "It has rows that match the header", and "it has columns of homogeneous values".
Scalars
A scalar is a relation of a unique value, a single column, a single row.
Examples of scalars are:
1
true
"hello"
Looking at them as relation, are identical to:
1 = [Int; 1]
true = [Bool; true]
"hello" = [Str; "hello"]
This means that the scalar is really a vector in disguise. This also mean that anything can be considered a scalar if is manipulated as a whole (this is the default in most languages).
Vectors
A vector is a collection of contiguous scalars that are of the same type. Can be considered similar to a "column" in a table.
Examples of vectors are:
[1; 2; 3] -- A vector of Ints
1 -- A vector of Ints
-- A vector of Ints, with a explicit schema with just the type. The name by default in vectos is "it"
[Int; 1; 2; 3]
-- A vector with a explicit schema with name "age" and type Int
[age: Int; 1; 2; 3]
-- A 2d vector. Normal people call it a table
[city:Str, country:Str;
"miami", "USA";
"bogota", "Colombia"]
Note how ,
is used to separate values (aka: cells) and ;
is to separate rows.
The vector is ordered exactly as was entered (or loaded form any source), and allow duplicated rows.
Tree (TBD)
A Tree is a collection of rows. The rows are stored internally in a B-Tree, meaning the rows are ordered according to a key that must be explicitly defined using the total order described before.
The name Tree can convey the idea of a hierarchy of values in most languages. But remember that this is a relational language and see all things as relations, but this still allow to express a hierarchy (is a Tree), just that the keys/values are like relations.
Examples of trees are:
Tree[|
pk id:Int, name:Str;
1, "hello"; -- row0
2, "world" -- row1
|]
Note the use of [||]
to enclose the data, that it must be preceded by the type Tree
, and the keyword pk
is used to define the key for comparison and fast search by that pk
. The presence of a key also mean that duplicated rows are replaced with the last row of that pk.
Enums (TBD)
To declare enums:
enum Status do
case Active
case Inactive
enum Option[T] do
case Some(T)
case None
enum Value do
case Str(String)
case Num(Int)
case Dec(Decimal)
enum Value2:Value do
case Bool(Bool)
enum ValueNums: Value
.Num, .Dec
To pattern match on enums
match status do
case Active do
"active"
case Inactive do
"Inactive"
end
match optional do
case Some(x) do
x
case None do
abort("No value")
end
Relational operators
The relational operators are the second most distinctive feature of the language. Them are intrinsically part of the relational model. Where exist a relation, you can be sure you can apply ALL the relational operators. Them are described in their own page.
All relational operators must start with the operator query ?
.
Examples of relational operators are:
-- Filter only values exactly = 2
[1; 2; 3] ?where #0 = 2
-- Open a file, sort it and return the first 3 rows
open("file.csv") ?sort ?limit 3
--You can use them everywhere
for i in [1; 2; 3] ?skip 2 do-- skip the first 2 rows
end
Functions
Most of the functionality of the language is provided with functions. A function is a piece of code that perform varied task to fulfils a need.
To declare a function:
fun sum(a:Int, b:Int) = Int do
a + b
end
fun total_invoice(inv:[|price:Money, qty: Money ...|]) = $inv[| total: Money |] do
inv?select inv.price * inv.qty as total
end
To use a function:
-- print the value to stdout (aka: Terminal)
print(1)
-- sum all the values
sum([1;2;3])
-- sum all the values and then print, in pipeline notation
[1;2;3] | sum | print
The available functions are described in their own page.
Control flow
In TablaM is more idiomatic to use the relational operators or use a more functional style, but is possible to use imperative control flow:
if
let value :=
if true do
1
else
2
end
Note how you can return the last value in each if branch.
However, this is not possible for looping constructs, these return the "pass" value. Is similar to the void in other languages.
while
while true do
println("this repeat forever")
end
-- return pass
for
for i in 1..10 do --this count from 1 to 9
println(i)
end
-- return pass
Types (tbd)
type Invoice do
price:Decimal
qty:Decimal
total:Decimal
end
impl Invoice
fun save(self, db:Db) do
end
end
type i32 as InvoiceId -- alias
type InvoiceId is i32 -- newtype
Traits (tbd)
trait Display do
fun display(self)=String
impl Display for Invoice
fun display(self)=String do
end
end
Modules (tbd)
mod Invoices
end
import Invoices