---
title: Language description
url: https://www.tines.com/docs/formulas/language/
updated: 2024-04-15T09:14:57+00:00
---

*[tines.com](https://www.tines.com/llms.txt) › [Docs](https://www.tines.com/llms.txt) › [Formulas](https://www.tines.com/llm/docs/formulas.md)*

# Language description

*[View on tines.com](https://www.tines.com/docs/formulas/language/)*

## Types

The formula language has the following types, which correspond to the [types](https://en.wikipedia.org/wiki/JSON#Data_types) in standard JSON:

### Text

To enter text, you can wrap your values in either single or double quotes

```python
# Double quotes
"this is some text"

# Single quotes
'this is also some text'
```

There is no difference in behavior between the quote styles.

If you need to use a quote inside some text you can escape it with `\`

```python
# Escaping text
"As Einstein said \"Never memorize something that you can look up.\""
```

### TRUE/FALSE

Booleans can be accessed like so, note that case is significant:

```python
# true
TRUE

# false
FALSE
```

Formulas does not support type coercing, ie. `[]` will not be coerced to `FALSE`. All values are truthy by default except for `FALSE` & `NULL`.

### NULL

Null values are written with all caps and are falsy:

```python
NULL

# true
IS_BLANK(NULL)
```

### Numbers

Numbers are written in simple notation.

```python
# number
123

# decimal
123.45

# negative
-123
```

### Arrays

Usually you will interact with arrays that come from your event data, however you can also create an array either using the array function or using array literal syntax.

```python
# creating an array using a function
ARRAY(1, 2, 3)
# [1, 2, 3]

# creating an array using array literal
[1, 2, 3]
# [1, 2, 3]

```

To access an item in an array, you use square brackets and numbers, the first item is at position zero:

```python
# my_array =  ["first", "second", "third"]

my_array[0]

# "first"
```

### Objects

Usually you will interact with object that come from your event data, however you can also create an object using the object function or object literal syntax.

```python
# creating an object using function
OBJECT("key1", "value1", "key2", "value2")
# {key1: "value1", key2: "value2"}

{key1: "value1", key2: "value2"}
# {key1: "value1", key2: "value2"}
```

When using object literal syntax, quotes are not required around keys unless needed (e..g if you want a key with a space in it).

## Operators

Operators allow you to compare values or perform basic math on them:

```python
# check if equal
a = b

# check if greater than
body.count > 1

# multiply
body.count * 5
```

See the reference section for more details.

## Functions

Functions allow you to perform operations on event data.

You call a function by using its name followed by opening and closing parentheses.

All functions are written in uppercase.

```python
MY_FUNCTION()
```

Most functions will accept one or more arguments, which can be specified like this:

```python
MY_FUNCTION(argument1, argument2)
```

Function calls can be nested:

```python
MY_FUNCTION(FUNCTION1(), FUNCTION2())
```

### Function chaining

Since function nesting can result in difficult to read expressions, we also support the ability to chain function calls.

Calls are chained like so:

```python
REPLACE(my_event.url, "https", "http[s]") |> UPCASE(%)
```

The result of the function on the left is made available on the right as `%`, where it can be used in any place

### Lazy evaluation

Arguments to most functions will be eagerly evaluated, for example take the following nested functions:

```python
MY_FUNCTION(FUNCTION1(), FUNCTION2())
```

When this is evaluated, `FUNCTION1` will be called first, then `FUNCTION2`, finally `MY_FUNCTION` will be called with the results of the previous two calls passed as arguments.

Exceptions to this rule are `IF` `AND` and `OR`, in these functions arguments are lazily evaluated, this allows you to effectively use these to control execution:

```python
IF(FUNCTION1(), FUNCTION2())
```

Here `FUNCTION2` will only be called if the `FUNCTION1` returns true.

## Lambdas

Lambdas are a custom, reusable function that you create using the `LAMBDA` function. This function is a little different from other functions because you specify the placeholders as the arguments to the function.

```python
LAMBDA(a, b, a + b)
```

Here `a` and `b` are the arguments that will need to be passed when the lambda is called and the `a + b` is the expression that will be evaluated when the lambda is called.

There are three ways of using lambdas.

### Immediately invoked

First they can be immediately invoked i.e. `LAMBDA(a, b, a + b)(1, 2)`. This will create a function to add two numbers together and then immediately call it. This might not seem very useful, but it can help avoid repetition in cases where you need to re-use the same calculation in multiple places.

For example if you wanted to check if the current time is between 9 and 5 you might do something like this:

```python
IF(
  AND(
    DATE("now", "%H") >= 9,
    DATE("now", "%H") < 17,
  ),
  "office hours",
  "after hours"
)
```

Using a lambda you could avoid this repetition like so:

```python
LAMBDA(
  current_hour,
  IF(
    AND(
      current_hour >= 9,
      current_hour < 17,
    ),
    "office hours",
    "after hours"
  ),
)(DATE("now", "%H"))
```

### With an Array function

The next way you can use Lambdas is as an argument to the functions `MAP_LAMBDA`, `FILTER`, `FIND` and `REDUCE`. For example let's imagine we have the following array of data in a field called `fruit`:

```python
[
  {
    "name": "apple",
    "in_stock": 0
  },
  {
    "name": "banana",
    "in_stock": 5
  },
  {
    "name": "pear",
    "in_stock": 6
  }
]
```

If we wanted to find all the items that are in stock we could write the following:

```python
FILTER(fruit, LAMBDA(item, item.in_stock > 0))
```

Or if we wanted to find the item with the name `pear` we could do

```python
FIND(fruit, LAMBDA(item, item.name = "pear"))
```

Or if we wanted to extract all the names we could do this:

```python
MAP_LAMBDA(fruit, LAMBDA(item, item.name))
```

### As a LOCAL or RESOURCE

The final way you can use lambdas is to assign them to LOCAL or to a RESOURCE and then call them from somewhere else. This can be a great way of defining your own re-usable bits of functionality.

![](https://www.datocms-assets.com/55802/1655989698-formulas_lambda_in_resource-ccb1924cb0955f74abcbe3d70a221daf.png)

Here we have created a lambda for defanging URLs and stored it in a `RESOURCE`. The lambda looks like this:

```
LAMBDA(
  url,
  url
    |> REPLACE(%, ".", "[.]")
    |> REPLACE(%, "http", "hxxp")
)
```

Ensure the lambda in the Resource, is defined inside of single value mode:

![](https://www.datocms-assets.com/55802/1656586401-single_value_mode_lambda_in_resource-4c580d87695c0ba25ff525c6780a423c.png)

*Lambda in a resource*

With this in place we can use it from any story like so:

![](https://www.datocms-assets.com/55802/1655989711-formulas_call_lambda_in_resource-3ad244ca2079cbea224c9356e6d19f68.png)

```python
RESOURCE.my_lambdas.defang(url)
```

## Tags

Tags are only available in text mode. You can nest tags inside each other. There must always be a corresponding `end` tag for each tag.

### if/elseif/else/endif

These tags all work together to allow you to conditionally evaluate sections in text mode.

At its simplest you can have an `if` and `endif` pair:

![](https://www.datocms-assets.com/55802/1655989723-actions_formulas_if_tag_1-d6b518c39baed0b139ab1a1cbb59de2e.png)

If this is run with a user named Alice, it will output `Hi Alice`, but if the user has no name it will just output `Hi`.

You can also add an `else` block to act as a catch all:

![](https://www.datocms-assets.com/55802/1655989737-actions_formulas_if_tag_2-e3dd0cd6e35a057686a0f4bab231ac35.png)

Now if the user has no name it will output `Hi there`.

Finally you can add more conditions using `elseif`:

![](https://www.datocms-assets.com/55802/1655989779-actions_formulas_if_tag_3-1833ef88fb86c3fc19ac98926ce0c8c1.png)

You can add as many additional conditions with `elseif` as you like.

As mentioned above, Formulas does not coerce types, with all but `NULL` & `FALSE` being truthy. To check if a value is blank simply write the following:

![](https://www.datocms-assets.com/55802/1655989788-actions_formulas_if_tag_4-da94aa59015808ccd59d1b57749b9dec.png)

### for/endfor

The `for` tag allows you to repeat the same block of code multiple times for each item in an array.

![](https://www.datocms-assets.com/55802/1713172457-screenshot-2024-04-15-at-10-14-00.png)

This will output `User names: `followed by the names of all the users in the array `users`.

Within a for tag, there is a special `FORLOOP` variable available with the following properties:

`FORLOOP.index0`: The zero based index of the current loop iteration. That is the first time through the loop this will be 0, the second time 1 and so on.

`FORLOOP.index`: The one based index of the current loop iteration. That is the first time through the loop this will be 1, the second time 2 and so on.

`FORLOOP.first`: This will be true the first time through the loop.

`FORLOOP.last`: This will be true the last time through the loop.

For example, if we wanted to output a comma between every name we could do something like this:

![](https://www.datocms-assets.com/55802/1655989812-actions_formulas_for_tag_2-042ce7553e1950b879e98c4a7f8eff17.png)

### raw/endraw

The `raw` tag allows you to escape any content within it, preventing interpolation at runtime. This escaping allows for repeated `<` to be expressed within formulas without triggering interpolation.

![](https://www.datocms-assets.com/55802/1713172484-screenshot-2024-04-15-at-09-58-30.png)
