Advanced Ludwig Syntax

This page focuses on advanced topics in Ludwig.

String Templating

Ludwig allows you to create string templates so that you can build strings using values in Ludwig data structures. Ludwig implements a subset of the Mustache specification.

What is Mustache?

Mustache is a templating standard with an emphasis on simplicity. With Mustache, you place {{tags}} in a string (or a file loaded as a string), and then a hash of tag values, like {"tags": "foo-and-bar"}. A renderer – in this case, built into the Ludwig compiler – processes the template string and hash, and produces a string result. Mustache templates are “logic-less,” a term which is best explained in the Mustache manual:

We call it “logic-less” because there are no if statements, else clauses, or for loops. Instead there are only tags. Some tags are replaced with a value, some nothing, and others a series of values.

The Ludwig.Template module provides a subset of Mustache template rendering features.

How does Ludwig implement Mustache?

The render function in the Ludwig.Template module renders a string given a Mustache template and data.

In Ludwig, Mustache templates are given as strings bound to the template argument. Strings can be literal, composed from functions, and read from files.

Hashes of values are given as a record bound to the data argument. Record field values must be one of the following types:

  • Int
  • Float
  • String

Attempting to render data of any other of any other type is not supported and results in an error. By extension, this means that other data types – like Bool or custom types – must be converted to a supported value, such as a String.

More complex Mustache features, such as sections and lists, are supported. Information about these features can be found in the reference for the Ludwig.Template module. Lambdas are supported in that a Ludwig function can be used as a value for any field in a record. Partials are not supported.


This example shows a properly formed, non-missing example of Mustache templates in Ludwig.

import Ludwig.Template

ex1: Template.render {
  template: "Hello {{name}}!",
  data: {name: "Bob"},
  missBehavior: Template.MissAsEmptyString,
} # => "Hello Bob!"

This example shows a rendering “miss,” in that the template string specifies a field which is not defined in data (namely, {{name}}).

ex2: Template.render {
  template: "Hello {{name}}!",
  data: {age: 42},
  missBehavior: Template.MissAsEmptyString,
} # => "Hello !"

This example shows a similar miss, but the behavior for a miss has been set to MissAsError. As a result, compilation of a composition using this function would fail.

ex3: Template.render {
  template: "{{name}}!",
  data: {name1: "Bob"},
  missBehavior: Template.MissAsError,
} # => Substitution error: Variable not found: "name"

Parametric polymorphism


This is an advanced topic! Very few Fugue users will need to know about it.

Ludwig’s type system supports parametric polymorphism. This means that types and functions can be parameterized over one or more types.

The builtin List, Dictionary and Optional types are good examples:

# A list of ints.
List<Int> my-int-list: [1, 2, 3]

# A list of strings
List<String> my-string-list: ["Hello", "World"]

However, it is also possible to write your own parametric types:

type Pair<a, b>:
    fst: a
    snd: b

Pair<Int, String> my-pair:
    fst: 78
    snd: "Hello World!"

fun swap(p: Pair<a, b>) -> Pair<b, a>:
    fst: p.snd
    snd: p.fst

As you can see, type parameters are written in lowercase.


Did you know that Fugue offers editor plug-ins so you can read and write Ludwig in your editor/IDE of choice? All plug-ins include Ludwig syntax highlighting, and some have additional features. See ludwig-mode for Emacs, ludwig-vim for vim, vscode-language-ludwig for VSCode, and language-ludwig for Atom.