# \[ANN\] Expronicon v0.8: type-stable rust-like algebra data type & pretty printing Julia expr & more

**URL:** <https://discourse.julialang.org/t/ann-expronicon-v0-8-type-stable-rust-like-algebra-data-type-pretty-printing-julia-expr-more/92647>\
**Category:** Package Announcements\
**Tags:** announcement\
**Created:** [January 7, 2023, 8:52pm UTC](https://discourse.julialang.org/t/ann-expronicon-v0-8-type-stable-rust-like-algebra-data-type-pretty-printing-julia-expr-more/92647 "2023-01-07T20:52:09Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)\
**Post date:** [January 7, 2023, 8:52pm UTC](https://discourse.julialang.org/t/ann-expronicon-v0-8-type-stable-rust-like-algebra-data-type-pretty-printing-julia-expr-more/92647/1 "2023-01-07T20:52:10Z")

</div>

I’m excited to announce a few new features to the Expronicon package’s recent releases (v0.8), as it starts  
getting stable I’m going to release a 1.0 version for the next stage if there are no significant changes in the future.

## Type-stable algebra data types (ADT)

There have been many attempts to support ADT from a package, such as MLStyle’s `@datatype`, the Unityper package, and ErrorTypes that try to mimic rust-like error types. It is crucial to many compiler-like programs, such as symbolic engines (Symbolics.jl), parsers, error handling, etc.

One issue with the previous implementation was **type stability** , e.g in [MLStyle](https://github.com/thautwarm/MLStyle.jl) the `@datatype` is actually implemented as lowering the ADT-like syntax into a set of Julia struct types, and a corresponding abstract type then supporting it with pattern match as control flow. This as a result will not be type stable when the constructed object needs to re-dispatch to different variant types.

The other issue is the **pattern match** , e.g [Unityper](https://github.com/YingboMa/Unityper.jl) supports ADT throw `@compactify` but there is no pattern matching support for the definition, resulting in not-so-idiomatic dispatches such as this example

```julia
foo!(xs) = for i in eachindex(xs)
    @inbounds x = xs[i]
    @inbounds xs[i] = @compactified x::AT begin
        A => D()
        B => A()
        C => B()
        D => A()
    end
end

```

The new ADT support in Expronicon get you both: **type stability** with **pattern match** with a set of new interfaces for the variants (“subtypes” of the ADT), you can construct the ADT in rust-enum-like syntax

```julia
@adt Food begin
    Apple
    Orange
    Banana
end

```

```julia
@adt Message begin
    Info(::String)
    Warning(::String)
    Error(::String)
end

```

```julia
@adt Animal begin
    struct Cat
        name::String = "Tom"
        age::Int = 3
    end
    struct Dog
        name::String = "Jack"
        age::Int = 5
    end
end

```

### Pattern Match

and use MLStyle’s pattern match to dispatch methods for them, the following is an example from rust’s documentation rewrite with Expronicon in Julia

```julia
@adt Message begin
    Quit

    struct Move
        x::Int
        y::Int = 1
    end

    Write(::String)

    ChangeColor(::Int, ::Int, ::Int)
end

```

and you can match the variants using the exact same syntax as how you construct them

**positional constructor pattern**

```julia
julia> @match Move(1, 2) begin
           Move(x, y) => x + y
           _ => false
       end
3

```

**keyword constructor pattern**

```julia
julia> @match Move(1, 2) begin
           Move(;x) => x
           _ => false
       end
1

```

or you can match the singletons

```julia
julia> @match Quit begin
        Quit => true
        _ => false
    end
true

```

### Type-stable error handling

In many Julia programmers’ programs, we use error/result types to handle errors, e.g

- in the TOML parser, the `Union` type `Err` is used: [julia/base/toml\_parser.jl at master · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/blob/master/base/toml_parser.jl#L282)
- there’s even a package [ErrorTypes.jl](https://github.com/jakobnissen/ErrorTypes.jl)

however, they all have the issue that causes type-instability in your function, now with Expronicon’s ADT,  
you can easily do it in a **type-stable** way

```julia
@adt Result begin
    OK
    Error(::String)
end

```

### Reflections

Expronicon’s ADT supports a rich set of reflections, you can query the variant type via the `variant_type` function, and a full list of auto-generated reflections is available here: [Expronicon.jl/src/adt/traits.jl at main · Roger-luo/Expronicon.jl · GitHub](https://github.com/Roger-luo/Expronicon.jl/blob/main/src/adt/traits.jl)

### As fast as Unityper

benchmarking with the example in Unityper’s README.

```julia
julia> using BenchmarkTools

julia> @btime UnityperBench.foo!($xs)
  57.834 μs (0 allocations: 0 bytes)

julia> @btime ExproniconBench.foo!($ys)
  57.625 μs (0 allocations: 0 bytes)

julia> @btime NaiveBench.foo!($gs)
  93.375 μs (10000 allocations: 312.50 KiB)

```

### Limitations

In order to enforce type-stability, we are not able to support generic ADT anymore (e.g the `Option` type in rust), this is because implementing generic ADT in an efficient way requires Julia compiler to understand the type and be able to infer the type accordingly. Otherwise, we will have many different copies of the same singleton type if implemented with macros, e.g

```julia
@adt Option{T} begin
    None
    Result(::T)
end

```

because `None` is not parametric, we will require the Julia compiler to recognize the `None` object as the same type/object no matter what `T` is. This will break the type-stability guarantee, which I currently don’t have a good solution to.

## (reworked) pretty printing for Julia expression

A new pretty printer has been implemented for Julia expression with syntax highlighting in the terminal, e.g

 ![image](https://global.discourse-cdn.com/julialang/original/3X/0/0/00243a79f635b0293be35f871eca7e40ac826cf2.jpeg)

and a strict inline printer

 ![image](https://global.discourse-cdn.com/julialang/original/3X/1/1/11b21aa71729f9fc5620d546201be5898e607745.jpeg)

this is useful when you trying to debug your generated expression while working on Julia’s meta-programming.

## Documentation

As some have noticed (on slack), we recently put up a new [documentation website](https://expronicon.rogerluo.dev/) that uses [vitepress](https://vitepress.vuejs.org/) as an experiment to replace `Documenter` based websites. Although Documenter has been a very convenient tool, I believe we need to catch up on the progress of the frontend community and adopt new tools such as [vuejs](https://vuejs.org/) and [reactjs](https://reactjs.org/), there has been very nice progress on static site generator (SSG) in these two community such as vitepress and [https://docusaurus.io/](https://docusaurus.io/).

what vitepress/docusaurus provide for documentation websites?

- super fast - a static single page solution to the documentation
- reactive - you can notice all the widgets are super reactive because they are generated through AOT compilation and optimized
- fast search - they both have good search engines integrated, so you don’t have to wait for a few seconds to get what you want

### Limitations

currently, if one wants to generate cross-reference or API reference from docstring, one will need to write [a Julia script](https://github.com/Roger-luo/Expronicon.jl/blob/main/docs/generate.jl) using Documenter to generate such. This is a bit annoying while developing since this will cause the vitepress/docusaurus local server not to be able to track what has changed. Not to say expanding `@example` etc. in the documentation.

These limitations do not seem not solvable, but gonna require more effort and likely require writing vuejs/reactjs components to support it, which I don’t have more time to work on. But I hope this early work can inspire people who are interested in writing Documenter alternatives.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [January 9, 2023, 1:37am UTC](https://discourse.julialang.org/t/ann-expronicon-v0-8-type-stable-rust-like-algebra-data-type-pretty-printing-julia-expr-more/92647/2 "2023-01-09T01:37:06Z")

</div>

What does

```julia
@adt Food begin
    Apple
    Orange
    Banana
end

```

expand to?

* * *

What is the difference between

```julia
@adt Animal begin
struct Cat
x::Int
end
end

```

and

```julia
@adt Animal begin
Cat(x::Int)
end

```

---

<div class="post-metadata">

**Author:** ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)\
**Post date:** [January 9, 2023, 1:59am UTC](https://discourse.julialang.org/t/ann-expronicon-v0-8-type-stable-rust-like-algebra-data-type-pretty-printing-julia-expr-more/92647/3 "2023-01-09T01:59:29Z")

</div>

> [@jar1](#):
>
> What does
> 
> ```julia
> @adt Food begin
> Apple
> Orange
> Banana
> end
> 
> ```
> 
> expand to?

It’s a complicated expansion, I’m not sure what exactly you want to know about the code it generates. But as a user, you shouldn’t be worrying about what it generates but only need to know what interface it provides. If you want to know what the macro does, you can explore the compilation passes here [https://github.com/Roger-luo/Expronicon.jl/blob/main/src/adt/emit.jl#L239](https://github.com/Roger-luo/Expronicon.jl/blob/main/src/adt/emit.jl#L239)

> [@jar1](#):
>
> What is the difference between
> 
> ```julia
> @adt Animal begin
> struct Cat
> x::Int
> end
> end
> 
> ```
> 
> and
> 
> ```julia
> @adt Animal begin
> Cat(x::Int)
> end
> 
> ```

is not a valid syntax. I don’t think any example like this is provided above. If you are asking what a constructor call means, it means anonymous fields. It saves you a few seconds for thinking a good field name when the structure is super simple (like the message example above)

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 22, 2023, 5:40pm UTC](https://discourse.julialang.org/t/ann-expronicon-v0-8-type-stable-rust-like-algebra-data-type-pretty-printing-julia-expr-more/92647/4 "2023-03-22T17:40:54Z")

</div>

> [@Roger-luo](#):
>
> ### Limitations
> 
> In order to enforce type-stability, we are not able to support generic ADT anymore (e.g the `Option` type in rust), this is because implementing generic ADT in an efficient way requires Julia compiler to understand the type and be able to infer the type accordingly. Otherwise, we will have many different copies of the same singleton type if implemented with macros, e.g
> 
> ```julia
> @adt Option{T} begin
> None
> Result(::T)
> end
> 
> ```
> 
> because `None` is not parametric, we will require the Julia compiler to recognize the `None` object as the same type/object no matter what `T` is. This will break the type-stability guarantee, which I currently don’t have a good solution to.

Have you seen the way that ErrorTypes.jl (and SumTypes.jl) handle this? We use implicit `convert` to do it.

```julia
julia> @sum_type Option{T} begin
           None()
           Result{T}(::T)
       end;

julia> None()
Option{Uninit}(None())

julia> let x::Option{Int} = None()
           x
       end           
Option{Int64}(None())

```
