MLStyle.jl, a package to supply algebraic data types and all kinds of pattern matching

Hi, community,
I’m a fan of ML, however in addition to F#, it’s a bit difficult to integrate them into daily works. If you’re struggling for this like me, I’m to bring you something makes a big convenience to stop your anxieties such as
if efficiency and readability conflicts, which should I choose?”. (if you’re kind of urgent, just see https://github.com/thautwarm/MLStyle.jl.)

P.S

The documenter support is added and you can check out https://thautwarm.github.io/MLStyle.jl/latest where I’m trying to figure out some meaningful introductions of this package and corresponding concepts like ADT and pattern-matching, just as @evanfields suggested.

For @ontouchstart 's proposal, the ML here is not machine learning but Meta. ML language family has various features to help with general purpose programming.

Currently, as we are introducing many easy, intuitive and useful features into MLStyle.jl from other languages like Rust(range pattern, if let (in developing)), Elixir(partially dictionary matching), Python(dynamic array packing) and so on, MLStyle.jl is becoming more and more powerful.

The Original

As we all know, Julia is so convenient in practical usage, and recently I’ve found the design pattern of Julia is so compatible to ML family.

For example, the struct with no fields would create a singleton when instantiating, which solved the problem to distinguish a enum from variable to capture without case sensitive unification like

match data with
| E ->  // E is a enum here
| e ->  // e is a pattern to capture here

But in Julia we can do

@match data begin
 &E ->  # use & to announce that it's using the variable in current context.
            #  that's a pattern(`^`) from Elixir lang.
 E() -> # for E() construct a singleton, and we destruct it in the same way.
 e   ->  #  capture
end

Then I wrote a package to do such stuffs(all the pattern matching with ADT in the world to solve problems easilier.)

The syntax might seems similar to Match.jl in basic usage, but actually this syntax is originally from my head…(actually I think anyone writes Scala, Rust and ML will have the same inspiration).

The difference is that, I have implemented all kinds of commonly used pattern matching from various languages(ML family, Rust, Haskell, Elixir, Scala) in MLStyle.jl, anditionally you can even simply extend it without much efforts.

Here is the docs of patterns.

The whole documents are not finished yet, but you can have a preview at here.

The implementation might not be very efficient, but in the future bootstrap of this packages’ core will tell that it could do optimizations that human can hardly make, while these functional programming infrastructures does make coding easy and pleasant.

Here are some snippets from docs.

Some commonly used patterns

dict = Dict(1 => 2, "3" => 4, 5 => Dict(6 => 7))
@match dict begin
    Dict("3" => four::Int, 
          5  => Dict(6 => sev)){four < sev} => sev
end
# => 7

@match (1, 2, (3, 4, (5, )))

    (a, b, (c, d, (5, ))) => (a, b, c, d)

end
# => (1, 2, 3, 4)

lst = List.List!(1, 2, 3)

@match lst begin 
    1 ^ a ^ tail => a
end

# => (2, MLStyle.Data.List.Cons{Int64}(3, MLStyle.Data.List.Nil{Int64}()))

Vector matching is practical, too. As the result of using array view, we’re able to avoid copying data when packing, while Python cannot do such easily.

julia> it = @match [1, 2, 3, 4] begin                     
         [1, pack..., a] => (pack, a)                 
       end                                                
([2, 3], 4)                                               
                                                          
julia> first(it)                                          
2-element view(::Array{Int64,1}, 2:3) with eltype Int64:  
 2                                                        
 3                                                             
julia> it[2]                                              
4                         

Make extensions

uisng MLStyle

# define custom application-like pattern
PatternDef.App(*) do args, guard, tag, mod
         @match (args) begin
            (l::QuoteNode, r :: QuoteNode) => MLStyle.Err.SyntaxError("both sides of (*) are symbols!")
            (l::QuoteNode, r) =>
               quote
                   $(eval(l)) = $tag / ($r)
               end
           (l, r :: QuoteNode) =>
               quote
                   $(eval(r)) = $tag / ($l)
               end
           end
end

@match 10 begin
     5 * :a => a
end
# => 2.0

Many other features to help your daily coding in Julia.

Pattern function is available, too.

@def f begin
    # patterns here
    x                  => 1
    (x, (1, 2)){x > 3} => 5
    (x, y)             => 2
    ::String           => "is string"
    _                  => "is any"
end
f(1) # => 1
f(4, (1, 2)) # => 5
f(1, (1, 2)) # => 2
f("") # => "is string"
13 Likes

:sweat_smile:

This looks really useful. A few years back I had a spurt of trying to learn Haskell, and while it didn’t stick, I do think of the pattern matching often.

I suspect you haven’t gotten much response because–at least to non-advanced users like me–it’s clear your project does a lot but not really clear what it does. It would be quite helpful if this post and/or the github readme started with a very simple “here’s a case where you would want to use pattern matching, here’s what it looks like in Julia without MLStyle, and here’s how things are better/easier/clearer with MLStyle.”

For example, I checked out the github repo and wanted to see pattern matching in action. No example, so I go docs -> pattern matching. The first thing I see is the ADT Destructing example. It’s got complicated data types which I didn’t know how to read because I didn’t first read the algebraic data types section, the ADT example doesn’t have comments or context explaining what the code is trying to do or why this package is needed, etc.

All of which is to say this looks great but maybe some more cleanup on documentation, examples, and motivation would make it clearer!

2 Likes

I agree ! pattern matching is one of the many things that i really liked about both Haskell and ML.

the other thing are data types, i.e.

data Fruit = Apple | Pear | Peach

I find many occasions where I would like to use them, and I keep thinking it would not be too painful of an extension to julia.

1 Like

Thank you @evanfields!

I think I’m really touched by your words. Your suggestions would be very helpful especially the concrete one,

here’s a case where you would want to use pattern matching, here’s what it looks like in Julia without MLStyle, and here’s how things are better/easier/clearer with MLStyle.

I’m always missing this sort of topic(introduce or explain something), and in the following I will do as what you’ve said.

Thanks again, sincerely.

It is a great start.

If I understand it correctly, this is a domain specific package for people who are familiar with ML programming language in which M stands for Meta. In other contexts, ML could stand for Machine Learning in which M stands for Machine. So perhaps you can make it clear in the README to avoid confusion.

Anyway, you got me interested in ML and I will use your package as an excuse to explore it.

2 Likes

that link is broken. the “.)” are extraneous characters.

Oh my bad. Thank you!