Formulas
This manual explains the Hexl language that you use in Herculus to express
formulas. A formula describes how the values of the cells in a column are
depending on other columns. For example, a formula for some column might be as
simple as $Quantity * $Price
, indicating that the cells are the products of
the cells from the "Quantity" and the "Price" columns.
Every formula consists of expressions that are combined into larger
expressions. The formula above consists of two sub-expressions, $Quantity
and
$Price
, which are combined by the *
operator. Every sub-expression
evaluates to a value, which is then used to evaluate the larger expression.
The Hexl language can also be used inside of report templates. Any expression that can appear in the formula of a column can also appear in the control blocks of a template, for example in an if-then-else block:
{% if $Quantity > 0 %} Something {% else %} Another thing {% endif %}
The Hexl Language
Types
In the same way that every column in Herculus has a specific type (e.g.,
String
or Number
), every expression in Hexl has a type. You can think of an
expression's type as the set of values that it can evaluate to.
The formula of a column is only valid if its type is the same as that of its
column. The type of an expression is automatically determined by Herculus. For
example, the expression 2 + 2
has type Integer
, since it's the sum of two
Integer
s.
Currently, the columns of tables in Herculus can have one of the following types:
Number
. The set of all decimal numbers, for example-4.0
or3.8
.Integer
. The set of all natural numbers, for example0
or-14
.String
. The set of all strings, for example"Hello"
or""
.Boolean
. The set of the two boolean valuesTrue
andFalse
.DateTime
. The set of all points in time.Row t
, wheret
is a table in the current project. The set of all rows from tablet
.Maybe a
, wherea
is again a type. The set of all values of typea
plus the valueNothing
. This type is used to express that a value may not be there.List a
, wherea
is another type. The set of all lists with elements of typea
. For exampleList Number
is the set of all lists with numbers in them.
Basic Arithmetic
You can use basic arithmetic operators to combine two expressions with a type
that implements the Num
interface.
+
adds two numbers-
subtracts the second number from the first*
multiplies two numers/
divides the first by the second number
For example, to subtract a
from b
you would simply write b - a
.
Conditionals
Hexl has an if-then-else expression to determine which one of two
sub-expressions is used given a value of type Boolean
:
if condition then trueBranch else falseBranch
In this expression, condition
is a sub-expression of type Boolean
. When it
evaluates to the value True
, the whole expression will have the value of
trueBranch
, otherwise it will have the value of falseBranch
. Therefore, if
trueBranch
and falseBranch
have the same type a
, the whole if-then-else
expression will also be of this type.
For example, the following is a valid if-then-else expression when x
is a
Number
:
if x > 0 then x - 1 else x
Since it's comparing two numbers, the condition x > 0
has type Boolean
. Both
sub-expression branches x - 1
and x
are of type Number
, so the whole
if-then-else expression has type Number
.
Using Functions
A function can be applied to a value by just writing it in front of it. For
example, if x
is a boolean value, we write not x
to negate it.
The not
function takes a Boolean
as input, and returns a
new Boolean
. We express this fact as follows:
not : Boolean -> Boolean
We call this the signature of the function not
.
If a function takes more than one argument, the arguments are just listed one after another:
formatNumber "%.2f" 2.0
Since the formatNumber
function takes a value of type String
, and then a
value of type Number
, and returns a value of type String
(the formatted
number), the sugnature is:
formatNumber : String -> Number -> Number
If you want an argument to be a more complicated sub-expression, just put parentheses around it:
formatNumber "%.2f" (1 / 7)
For a list of functions that are included, see the Function Reference.
Defining Your Own Variables and Functions
New variables can be introduced on new lines above the formula:
x = 1 / 7
y = x * x
if y > 0.5 then "Foo" else "Bar"
Own functions can also be defined in the same way, by introducing the arguments
between the function name and the =
:
square x = x * x
square (13 - 4)
Anonymous Functions
A function can also be defined without giving it a name, by using a lambda
expression. For example, the expression \x -> x > 0
is a function, which
takes one argument, a Number
, and returns the Boolean
value True
exactly
when it is greater than 0
.
This is often useful when using other functions that take a function as an
argument, such as filter
:
filter (\s -> s.Quantity >= 10) #Sales
Accessing Data From Other Cells / Tables / Columns
There are three ways to access data in other places in your project:
A Cell of Another Column of the Same Table
The prefix $
is used to reference the value of a cell in another column (which
has to be part of the same table as the column you are writing the formula
for). Let's say I'm writing the formula of column "Revenue" as $Quantity *
$Price
. Then every cell of the "Revenue" column will be the product of the
corresponding cells from the "Quantity" and the "Price" columns.
The type of expression $Foo
will always be the type of the column "Foo".
A Whole Table
The prefix #
is used to reference whole tables, represented as a List
of
rows. For example, #Books
returns a value of type List a
, where a
is a row
from the table Books
.
A Whole Column
You can also only reference one particular column of a table by joining the
table name with the desired column name via a dot: The expression
#Sales.Quantity
refers to the "Quantity" column from the "Sales" table. It
will evaluate to a value of type List a
, where a
is the type of the
"Quantity" column.
Records
A record consists of a set of named fields, each of which has a certain
type. For example, the record type { Name : String, Age : Number }
has two
fields, Name
and Age
, of typ String
and Number
, respectively.
Given a record value, you can access the values of its individual fields using
the .
operator. For example, to access the Name
field of one the above
record, one would write r.Name
, where r
is the record.
Rows of tables can also be treated as records. For example, if there is a table
with a column Quantity
of type Number
, and row
is one of its rows, we can
access the value in the "Quantity" column of that row by writing row.Quantity
.
Report Templates
Herculus features so-called "Report Columns" which can generate, for each of that column's cell, a report in various formats (PDF, HTML, Plaintext). The report is generated according to a template which is written in various markup languages (e.g., Markdown, HTML, LaTeX).
Independent of the markup language, the following constructs can be used to access and print values, as well as to control what to render.
Printing Values
To print a value use {{ expression }}
, where the type of expression
needs
to implement the Print
interface.
Customer: {{ $Name }}
Revenue: {{ $Revenue }}
For Loops
To render some part of the template for every element of a list, use:
{% for element in list %}
Body (element is in scope)
{{ element }}
{% endfor %}
Here, list
is an expression of type List a
, for some type a
. The variable
element
will be available in the body of that block, and whenever used, will
evaluate to a value of type a
.
For example, we can use this to loop over the items of a bill:
<ol>
{% for item in $Items %}
<li> <b>{{ item.Description }}</b>, quantity: {{ item.Quantity }}
{% endfor %}
</ol>
Conditionals
To render one or another part of the template, depending on the truth value of some expression, use:
{% if condition %}
Condition evaluated to true
{% else %}
Condition evaluated to false
{% endif %}
Here, condition
can be any expression which has a type Boolean
.