The otter mascot of Lutra

Lutra

General-purpose query language

⚠️ Work in progress

This website (and Lutra project as a whole) is a work in progress. It describes what we want Lutra to become, not what it is yet. The content might be outdated, incomplete or might contain typos.

See project status.

Getting started

Easiest way to try out the language is to run .lt files with the CLI. To do that, first install the CLI:

$ cargo install --git https://codeberg.org/aljazerzen/lutra lutra-cli

Now create a file named example.lt with the following content:

func main() -> "Hello world!"

This file contains a single function called main, which just returns text Hello world!. Run the function:

$ lutra run example.lt
"Hello world!"

This guide will show different examples, each of which can be placed into a .lt file and executed with lutra run.

Tuples

There are 3 container types: tuples, arrays and enums. Let's start with tuples, the product type.

Tuples are used to combine multiple values together, so they can be returned from functions as a single value.

func main() -> {"Hello world!", true, 42}

In the body of the main function, we now use curly braces {}, with three tuple fields.

In the braces, we can place any number of fields of any type - even another nested tuple. Fields can also be named, for example:

func main() -> {id = 5, title = "Prestige", 2006}

Instead of constructing this tuple in the main function, let's extract it into a constant, named movie:

let movie = {id = 5, title = "Prestige", 2006}

func main() -> movie

That did not change the result, but it allows us to show how to pick just title and release year out of the movie:

let movie = {id = 5, title = "Prestige", 2006}

func main() -> {movie.title, movie.2}

We can obviously refer to named fields by name, but to pick release year, we must use its position. In this case, the position is 2, because we start counting at 0.

A major constraint of tuples is that they can only have a fixed number of fields. For example, if we wanted to return all actors of a movie and decided to store them in a tuple, each movie would have to have the same number of actors.

We don't want that, so we will use an array instead.

Arrays

Arrays are containers that can contain many items. They use square brackets [] and items are delimited by commas, with optional trailing comma (this applies to tuples too).

let actors = [
  "Hugh Jackman",
  "Christian Bale",
  "Michael Caine",
]

func main() -> actors

Note that all items of the array must be of the same type. We couldn't place a 5, true, or {name = "Piper Parabo"} into the array above.

Incomplete

This guide is incomplete. We should also include following examples:

let actors = ...

func main() -> std::slice(actors, 0, 2)
let actors = ...

func main() -> (
    actors | std::slice(0, 2)
)
let movie1 = {
  id = 5,
  title = "Prestige",
  2006,
  actors = [
    "Hugh Jackman",
    "Christian Bale",
    "Michael Caine",
  ],
}

let movie2 = {
  id = 6,
  title = "Her",
  2013,
  actors = [
    "Joaquin Phoenix",
    "Scarlett Johansson",
  ],
}

let movies = [movie1, movie2]

func main() -> movies
...

type Movie = {
  id = int,
  title = text,
  int,
  actors = [text],
}

func main(): [Movie] -> [movie1, movie2]
...

func get_title(m: Movie) -> m.title

func main() -> get_title(movie1)
...

func main() -> std::map(movies, get_title)
...

func main() -> (movies | std::map(get_title))