Frequently Asked Questions

Getting Started

How do I install eucalypt?

On macOS, use Homebrew:

brew install curvelogic/homebrew-tap/eucalypt

On Linux or macOS without Homebrew, use the install script:

curl -sSf https://raw.githubusercontent.com/curvelogic/eucalypt/master/install.sh | sh

You can also download a binary from the GitHub releases page, or build from source with cargo install --path ..

Verify installation with:

eu version

How do I convert between data formats?

Pass a file in one format and specify the output format with -x or -j:

# YAML to JSON
eu data.yaml -j

# JSON to YAML (default output)
eu data.json

# YAML to TOML
eu data.yaml -x toml

What data formats does eucalypt support?

Input formats: YAML, JSON, JSON Lines (jsonl), TOML, EDN, XML, CSV, plain text, and eucalypt's own .eu syntax.

Output formats: YAML (default), JSON, TOML, EDN, and plain text.

Streaming input formats (for large files): jsonl-stream, csv-stream, text-stream.

How do I use eucalypt in a pipeline?

eu reads from stdin by default when used in a pipe and writes to stdout:

# Filter JSON from an API
curl -s https://api.example.com/data | eu -e 'items filter(.active)'

# Transform and re-export
cat data.yaml | eu transform.eu -j > output.json

Use -e to specify an expression to evaluate against the input data.

How do I pass arguments to a eucalypt program?

Use -- to separate eu flags from program arguments:

eu program.eu -- arg1 arg2 arg3

Inside your program, access them via io.args:

name: io.args head-or("World")
greeting: "Hello, {name}!"

Language

How do functions work in eucalypt?

Define functions with a parameter list after the name:

double(x): x * 2
result: double(21) //=> 42

Functions are curried -- applying fewer arguments than expected returns a partially applied function:

add(x, y): x + y
increment: add(1)
result: increment(9) //=> 10

What is catenation?

Catenation is eucalypt's pipeline syntax. Writing x f applies f to x as a single argument:

add-one(x): x + 1
result: 5 add-one //=> 6

Chain multiple transforms by writing them in sequence:

double(x): x * 2
add-one(x): x + 1
result: 5 double add-one //=> 11

This reads left to right: start with 5, double it (10), add one (11).

What are anaphora and when should I use them?

Anaphora are implicit parameters that let you define simple functions without naming them. There are three kinds:

Expression anaphora (_, _0, _1): turn an expression into a function.

squares: [1, 2, 3] map(_0 * _0) //=> [1, 4, 9]

String anaphora ({}, {0}, {1}): turn a string template into a function.

labels: [1, 2, 3] map("item-{}") //=> ["item-1", "item-2", "item-3"]

Block anaphora (, •0, •1): turn a block into a function.

Use anaphora for simple, readable cases. For anything more complex, prefer a named function. See Anaphora for details.

Why is there no lambda syntax?

Eucalypt deliberately omits lambda expressions. Instead, use:

  1. Named functions for anything non-trivial
  2. Anaphora (_, {}) for simple one-liners
  3. Sections ((+ 1), (* 2)) for operator-based functions
  4. Partial application (add(1)) for curried functions
# All equivalent ways to add one:
add-one(x): x + 1
result1: [1, 2, 3] map(add-one) //=> [2, 3, 4]
result2: [1, 2, 3] map(_ + 1) //=> [2, 3, 4]
result3: [1, 2, 3] map(+ 1) //=> [2, 3, 4]

How does block merging work?

When you write one block after another (catenation), they merge:

base: { a: 1 b: 2 }
overlay: { b: 3 c: 4 }
merged: base overlay //=> { a: 1 b: 3 c: 4 }

The second block's values override the first. This is a shallow merge. For recursive deep merge, use the << operator:

base: { x: { a: 1 b: 2 } }
extra: { x: { c: 3 } }
result: base << extra

How do I handle the lookup precedence gotcha?

The . (lookup) operator has higher precedence than catenation, so xs head.id parses as xs (head.id), not (xs head).id.

Use explicit parentheses:

data: [{ id: 1 }, { id: 2 }]
first-id: (data head).id //=> 1

See Syntax Gotchas for more.

Data Processing

How do I filter and transform lists?

Use map to transform and filter to select:

numbers: [1, 2, 3, 4, 5, 6]
small: numbers filter(< 4) //=> [1, 2, 3]
doubled: numbers map(* 2) //=> [2, 4, 6, 8, 10, 12]

Combine them in a pipeline:

result: [1, 2, 3, 4, 5, 6] filter(> 3) map(* 10) //=> [40, 50, 60]

How do I look up values in nested blocks?

Use chained . lookups for known paths:

config: { db: { host: "localhost" port: 5432 } }
host: config.db.host //=> "localhost"

For dynamic key lookup, use lookup with a symbol:

data: { name: "Alice" age: 30 }
field: data lookup(:name) //=> "Alice"

Use lookup-or to provide a default:

data: { name: "Alice" }
age: data lookup-or(:age, 0) //=> 0

How do I search deeply nested data?

Use deep-find for recursive key search:

# Finds all values for key "id" at any depth
ids: data deep-find("id")

Use lookup-path for a known sequence of keys:

data: { a: { b: { c: 42 } } }
result: data lookup-path([:a, :b, :c]) //=> 42

How do I sort data?

Sort lists with sort-nums or sort-strs:

names: ["Charlie", "Alice", "Bob"]
sorted: names sort-strs //=> ["Alice", "Bob", "Charlie"]
nums: [5, 1, 3, 2, 4]
sorted: nums sort-nums //=> [1, 2, 3, 4, 5]

For sorting by a key, use sort-by-str or sort-by-num:

people: [{ name: "Zoe" age: 25 }, { name: "Amy" age: 30 }]
by-name: people sort-by-str(.name)
youngest: (by-name head).name //=> "Amy"

How do I work with dates?

Use t"..." literals for date-time values:

meeting: t"2024-03-15T14:30:00Z"
date-only: t"2024-03-15"
before: t"2024-01-01" < t"2024-12-31" //=> true

See Date, Time, and Random Numbers for parsing, formatting, and arithmetic.

Advanced

How do I attach metadata to declarations?

Use the backtick (`) prefix:

` "Compute the square of a number"
square(x): x * x

result: square(5) //=> 25

Metadata can be a string (documentation) or a block with structured data:

` { doc: "Custom operator" associates: :left precedence: 75 }
(l <+> r): l + r

How do imports work?

Imports are specified in declaration metadata using the import key:

{ import: "helpers.eu" }

result: helper-function(42)

For named imports (scoped access):

{ import: "cfg=config.eu" }

host: cfg.host

See Import Formats for the full syntax including git imports.

How do I write tests?

Use the //=> assertion operator to check values inline:

double(x): x * 2
result: double(21) //=> 42

If the assertion fails, eucalypt panics with a non-zero exit code. Other assertion operators:

x: 5
check1: (x > 3) //!
check2: (x = 0) //!!
check3: x //=? pos?

How do I generate random values?

Use io.random for a stream of random floats, or pass --seed for reproducible output:

die: random.int(6, io.random).value + 1
eu --seed 42 game.eu

See Random Numbers for the full API.

What are sets and how do I use them?

The set namespace provides set operations. Convert lists to sets with set.from-list:

sa: set.from-list([1, 2, 3, 4])
sb: set.from-list([3, 4, 5, 6])
common: sa set.intersect(sb) set.to-list //=> [3, 4]
combined: sa set.union(sb) set.to-list sort-nums //=> [1, 2, 3, 4, 5, 6]
diff: sa set.diff(sb) set.to-list sort-nums //=> [1, 2]