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 Integers.

Currently, the columns of tables in Herculus can have one of the following types:

Basic Arithmetic

You can use basic arithmetic operators to combine two expressions with a type that implements the Num interface.

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.