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 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 supports ADT throw @compactify
but there is no pattern matching support for the definition, resulting in not-so-idiomatic dispatches such as this example
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
@adt Food begin
Apple
Orange
Banana
end
@adt Message begin
Info(::String)
Warning(::String)
Error(::String)
end
@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
@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> @match Move(1, 2) begin
Move(x, y) => x + y
_ => false
end
3
keyword constructor pattern
julia> @match Move(1, 2) begin
Move(;x) => x
_ => false
end
1
or you can match the singletons
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
typeErr
is used: https://github.com/JuliaLang/julia/blob/master/base/toml_parser.jl#L282 - there’s even a package 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
@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: 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> 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
@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
and a strict inline printer
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 that uses vitepress 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 and reactjs, there has been very nice progress on static site generator (SSG) in these two community such as vitepress and 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 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.