Syntax & Examples
Importing
Section titled “Importing”The algebraic data type can be defined using @data macro. It can be imported from the Moshi.Data module:
using Moshi.Data: @dataThe Moshi.Data module also defines a set of reflection functions to work with the algebraic data types. You can check Reflection for more information. All the name can be imported together using:
using Moshi.Data.PreludeQuick Example
Section titled “Quick Example”Here is a quick example of defining a simple algebraic data type:
@data Message begin Quit() struct Move x::Int y::Int end
Write(String) ChangeColor(Int, Int, Int)endThis 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:
Message.Quit()Message.Move(10, 20)Message.Write("Hello, World!")Message.ChangeColor(255, 0, 0)Formal Syntax
Section titled “Formal Syntax”The syntax is as follows:
<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
Section titled “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:
<ident> ( )for example, the Quit variant in the above example is a singleton variant.
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:
# ❌ WRONG — Score.ZeroZero is a type, not a Score.Type valueScore.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 valueScore.Type[Score.ZeroZero(), Score.Even(), Score.Plus(1)]The same applies when you dispatch on a value. Suppose you overload + on Score.Type:
Base.:+(s::Score.Type, n::Int) = ...Then the caller must pass a value, not the bare singleton name:
Score.ZeroZero + 1 # ❌ MethodError: no method matching +(::Type{Score.ZeroZero}, ::Int)Score.ZeroZero() + 1 # ✅ dispatches on the Score.Type valueThe rule is uniform: a singleton variant always needs () to become a value — including inside a
@match 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.
Anonymous Variant
Section titled “Anonymous Variant”The anonymous variant is useful when you want to define a variant with anonymous fields. It can be defined as:
<ident> ( <type>+ )for example, the Write and ChangeColor variants in the above example are anonymous variants.
Named Variant
Section titled “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:
struct <ident> <field>+endfor example, the Move variant in the above example is a named variant.
Exporting Variants
Section titled “Exporting Variants”By default the @data namespace only marks its variant constructors as
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
usinged, list them in an export statement inside the @data body:
@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 exportedOnly 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.
Generics/Type Parameters
Section titled “Generics/Type Parameters”The @data macro also supports defining algebraic data types with type parameters. For example:
@data Option{T} begin Some(T) None()endThis 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
@data <ident>{<type>+} [ <supertype> ] begin <variant>+endthe 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
Section titled “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:
@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"endthe 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:
@match message begin Message.Move(;y=10, x) => xend