Hi everyone, I’ve been playing around a bit lately with the idea of ‘sum types’ (aka tagged unions, or enums in Rust circles). These are quite the hotness these days in functional programming and I kept having trouble fully getting my head around them, so I finally just sat down and implemented them in julia, which has clarified things for me a fair amount.
Check it out at SumTypes.jl.
I think at least in a dynamic language like Julia, there’s good reason to be skeptical of any claim that these are an alternative to Union
, however I think maybe there are some uses for these things, and at the very least if someone tells you
Julia is a terrible programming language, it doesn’t even have sum types, the greatest language feature ever!
you can just tell them
Big deal, Mason made a sum type package in 55 lines of julia code.
If you really don’t wanna click on the link, here’s an excerpt from the README where I explain a bit about what a sum type is and how they work:
SumTypes.jl
Sum types, sometimes called ‘tagged unions’ are the type system equivalent of the disjoint union operation (which is not a union in the traditional sense). From a category theory perspective, sum types are interesting because they are dual to Tuple
s (whatever that means).
At the end of the day, a sum type is really just a fancy word for a container that can store data of a few different, pre-declared types and is labelled by how it was instantiated.
Users of statically typed programming languages often prefer Sum types to unions because it makes type checking easier. In a dynamic language like julia, the benefit of these objects is less obvious, but perhaps someone can find a fun use case.
Let’s explore a very fundamental sum type (fundamental in the sense that all other sum types may be derived from it):
julia> using SumTypes
julia> @sum_type Either{A, B} begin
Left{A, B}(::A)
Right{A, B}(::B)
end
This says that we have a sum type Either{A, B}
, and it can hold a value that is either of type A
or of type B
. Either
has two ‘constructors’ which we have called Left{A,B}
and Right{A,B}
. These exist essentially as a way to have instances of Either
carry a record of how they were constructed by being wrapped in dummy structs named Left
or Right
. Here we construct some instances of Either
:
julia> Left{Int, Int}(1)
Either{Int64,Int64}(Left{Int64,Int64}(1))
julia> Right{Int, Float64}(1.0)
Either{Int64,Float64}(Right{Int64,Float64}(1.0))
Note that unlike Union{A, B}
, A <: Either{A,B}
is false, and
Either{A, A}
is distinct from A
.
Pattern matching on Sum types
Because of the structure of sum types, they lend themselves naturally to things like pattern matching. As such, SumTypes.jl
re-exports MLStyle.@match
from MLStyle.jl and automatically declares Sum types as MLStyle record types so they can be destructured:
julia> @match Left{Int, Int}(1) begin
Either(Left(x)) => x + 1
Either(Right(x)) => x - 1
end
2
julia> @match Right{Int, Int}(1) begin
Either(Left(x)) => x + 1
Either(Right(x)) => x - 1
end
0