---
title: Syntax & Examples
description: Syntax for defining algebraic data types in Moshi.jl
---

import { Aside } from '@astrojs/starlight/components';

## Importing

The algebraic data type can be defined using `@data` macro. It can be imported from the `Moshi.Data` module:

```julia
using Moshi.Data: @data
```

The `Moshi.Data` module also defines a set of reflection functions to work with the algebraic data types. You can check [Reflection](/data/reflection) for more information. All the name can be imported together using:

```julia
using Moshi.Data.Prelude
```

## Quick Example

Here is a quick example of defining a simple algebraic data type:

```julia
@data Message begin
    Quit()
    struct Move
        x::Int
        y::Int
    end

    Write(String)
    ChangeColor(Int, Int, Int)
end
```

This defines a new algebraic data type `Message` with 4 variants: `Quit`, `Move`, `Write`, and `ChangeColor`. The `Move` variant has two fields `x` and `y` of type `Int`. The `Write` variant has a single field of type `String`. The `ChangeColor` variant has three fields of type `Int`.

You can create an instance of the `Message` type as follows:

```julia
Message.Quit()
Message.Move(10, 20)
Message.Write("Hello, World!")
Message.ChangeColor(255, 0, 0)
```

<Aside type="tip">

The `@data` macro will always create a namespace as the same name of the
algebraic data type. And instead, one use `<ident>.Type` (`Message.Type` in this case)
as the type of the algebraic data type.

</Aside>

## Formal Syntax

The syntax is as follows:

```julia
<data> := @data <ident> [ <supertype> ] begin
    [ <export> ]
    <variant>+
end
<export> := export <ident> ( , <ident> )*
<variant> := <singleton> | <anonymous> | <named>
<singleton> := <ident> ( )
<anonymous> := <ident> ( <type>+ )
<named> := struct <ident>
    <field>+
end
<field> := <ident>::<type> [= <expr>]
```

`<data>` is the top-level syntax for defining an algebraic data type. It starts with the `@data` macro followed by the name of the data type. Optionally, it can have a supertype. The supertype can be any type that the data type extends. The `begin` keyword is used to start the body of the data type. The body consists of one or more variants.

A `<variant>` can be one of three types: `<singleton>`, `<anonymous>`, or `<named>`. A `<singleton>` variant is a variant with no fields. An `<anonymous>` variant is a variant with anonymous fields. A `<named>` variant is a variant with named fields.

### Singleton Variant

The singleton variant is a variant with no fields, similar to a `Base.@enum` member. It is defined
with an empty argument list:

```julia
<ident> ( )
```

for example, the `Quit` variant in the above example is a singleton variant.

<Aside type="caution" title="A singleton name is a type, not a value">

Unlike `Base.@enum` and Rust's `enum`, the singleton name **is not an instance**. It refers to the
variant *type*. To get an actual value, you must call the empty constructor with `()`:

```julia
@data Score begin
    ZeroZero()
    Even()
    Plus(Int)
end

Score.ZeroZero    # the variant *type* (a `DataType`) — NOT a value
Score.ZeroZero()  # the value — an instance of `Score.Type`
```

You can confirm this at the REPL:

```julia
julia> Score.ZeroZero isa Score.Type
false

julia> Score.ZeroZero() isa Score.Type
true
```

</Aside>

This is why the definition also uses `()`: writing `ZeroZero()` in the `@data` block mirrors how you
construct and pattern-match the variant everywhere else, so there is no illusion that the bare name
is a value.

Remember to add `()` **everywhere you want a value** — when storing variants in a collection, passing
them to a function, comparing them, or dispatching on them.

For example, building a `Vector{Score.Type}` requires calling every singleton constructor. Forgetting
the `()` stores a *type* where a value is expected, which fails to convert:

```julia
# ❌ WRONG — Score.ZeroZero is a type, not a Score.Type value
Score.Type[Score.ZeroZero, Score.Even, Score.Plus(1)]
# ERROR: MethodError: Cannot `convert` an object of type
#        Type{Score.ZeroZero} to an object of type Score.Type

# ✅ RIGHT — call the constructor to get the value
Score.Type[Score.ZeroZero(), Score.Even(), Score.Plus(1)]
```

The same applies when you dispatch on a value. Suppose you overload `+` on `Score.Type`:

```julia
Base.:+(s::Score.Type, n::Int) = ...
```

Then the caller must pass a *value*, not the bare singleton name:

```julia
Score.ZeroZero + 1    # ❌ MethodError: no method matching +(::Type{Score.ZeroZero}, ::Int)
Score.ZeroZero() + 1  # ✅ dispatches on the Score.Type value
```

The rule is uniform: a singleton variant always needs `()` to become a value — including inside a
[`@match` pattern](#default-pattern), where `Score.ZeroZero()` matches the singleton value. The only
time you write the bare name `Score.ZeroZero` is when you genuinely want the variant *type* itself,
for example in a reflection function like [`isa_variant`](/data/reflection).

<Aside type="tip">

The bare identifier form (`ZeroZero` with no `()`) is **deprecated**. It still works but emits a
warning; write `ZeroZero()` instead. See [issue #22](https://github.com/Roger-luo/Moshi.jl/issues/22)
for the motivation.

</Aside>

### Anonymous Variant

The anonymous variant is useful when you want to define a variant with anonymous fields. It can be defined as:

```julia
<ident> ( <type>+ )
```

for example, the `Write` and `ChangeColor` variants in the above example are anonymous variants.

### Named Variant

The named variant is just like normal Julia struct definition, except that it is defined inside the data type. It can be defined as:

```julia
struct <ident>
    <field>+
end
```

for example, the `Move` variant in the above example is a named variant.

<Aside type="tip">
In the case of named fields, it supports `Base.@kwdef` syntax for default values.
</Aside>

<Aside type="tip" title="The ADT name refers to `.Type` inside the body">

Everywhere inside the `@data` body — field types *and* default values — the bare
ADT name refers to `<ident>.Type`. This makes self-referential defaults read
naturally:

```julia
@data SExpr begin
    struct Add
        arguments::Vector{SExpr} = SExpr[]   # `SExpr` means `SExpr.Type`
    end
end

SExpr.Add()  # arguments === SExpr.Type[]
```

Writing `SExpr.Type[]` explicitly is equivalent. See
[issue #33](https://github.com/Roger-luo/Moshi.jl/issues/33).

</Aside>

## Exporting Variants

By default the `@data` namespace only marks its variant constructors as
[`public`](https://docs.julialang.org/en/v1/base/base/#public) (on Julia 1.11+),
so you always reach them through the namespace, e.g. `Message.Quit()`. If you
want some variants to be brought into scope automatically when the namespace is
`using`ed, list them in an `export` statement inside the `@data` body:

```julia
@data Shape begin
    export Circle, Square

    Circle(Float64)
    Square(Float64)
    Triangle(Float64, Float64)
end

using .Shape          # brings `Circle` and `Square` into scope

Circle(1.0)           # no need to write `Shape.Circle(1.0)`
Square(2.0)
Shape.Triangle(3.0, 4.0)  # `Triangle` was not exported
```

Only the listed variants are exported; the rest stay `public`-only. Every name
in the `export` list must be a variant of the data type, otherwise the macro
raises an error.

<Aside type="tip">

This is opt-in on purpose — scoping variants behind the namespace keeps a
codebase readable. Reach for `export` when a handful of variants are used often
enough that the namespace prefix becomes noise. See
[issue #45](https://github.com/Roger-luo/Moshi.jl/issues/45) for the discussion.

</Aside>

## Generics/Type Parameters

The `@data` macro also supports defining algebraic data types with type parameters. For example:

```julia
@data Option{T} begin
    Some(T)
    None()
end
```

This defines a new algebraic data type `Option` with two variants: `Some` and `None`. The `Some` variant has a single field of type `T`. The `None` variant has no fields.

The type parameters should be declared in the type definition

```julia
@data <ident>{<type>+} [ <supertype> ] begin
    <variant>+
end
```

the syntax is the same as a normal `struct` type parameter declaration. Inside the `begin ... end` body,
the type parameters can be used as normal types.

## Default Pattern

The ADT defined with Moshi supports a default pattern when doing pattern matching.
This is always the inverse operation of the generated constructor. Taking the `Message` example above:

```julia
@match message begin
    Message.Quit() => "Quit"
    Message.Move(x, y) => "Move to $(x), $(y)"
    Message.Write(msg) => "Write: $msg"
    Message.ChangeColor(r, g, b) => "Change color to ($r, $g, $b)"
    _ => "Unknown"
end
```

the call pattern here does not need to match the exactly same number of arguments as the constructor.
Instead if the pattern is `Move(x)` it is equivalent to `Move(x, _)`.

For named variants, because it generates a `@kwdef` like constructor, the following keyword argument
pattern is also supported:

```julia
@match message begin
    Message.Move(;y=10, x) => x
end
```
