Pattern

Pattern

Patterns provide convenient ways to manipulate data.

Literal Pattern



@match 10 {
    1  => "wrong!"
    2  => "wrong!"
    10 => "right!"
}

# => "right"

There are 3 distinct types whose literal data could be used as literal patterns:

Capturing Pattern


@match 1 begin
    x => x + 1
end
# => 2

Note that, by default only symbols given in lower case could be used as capturing.

If you prefer to capture via upper case symbols, you can enable this feature via

@use UppercaseCapturing

Extension UppercaseCapturing conflicts with Enum.

Any questions about Enum, check Active Patterns.

Type Pattern


@match 1 begin
    ::Float  => nothing
    b :: Int => b
    _        => nothing
end
# => 1

There is an advanced version of Type-Patterns, which you can destruct types with fewer limitations. Check Advanced Type Pattern.

As-Pattern

As-Pattern can be expressed with And-Pattern.

@match (1, 2) begin
    (a, b) && c => c[1] == a && c[2] == b
end

Guard


@match x begin
    x && if x > 5 end => 5 - x # only succeed when x > 5
    _        => 1
end

Predicate

The following has the same semantics as the above snippet.


function pred(x)
    x > 5
end

@match x begin
    x && function pred end => 5 - x # only succeed when x > 5
    _        => 1
end

@match x begin
    x && function (x) x > 5 end => 5 - x # only succeed when x > 5
    _        => 1
end

Range Pattern

@match 1 begin
    0:2:10 => 1
    1:10 => 2
end # 2

Reference Pattern

This feature is from Elixir which could slightly extends ML pattern matching.

c = ...
@match (x, y) begin
    (&c, _)  => "x equals to c!"
    (_,  &c) => "y equals to c!"
    _        => "none of x and y equal to c"
end

Custom Pattern

Not recommend to do this for it's implementation specific. If you want to make your own extensions, check Pervasives.jl.

Defining your own patterns using the low level APIs is quite easy, but exposing the implementations would cause compatibilities in future development.

Dict, Tuple, Array

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

@match (1, 2, (3, 4, (5, ))) begin
    (a, b, (c, d, (5, ))) => (a, b, c, d)

end
# => (1, 2, 3, 4)
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

Or Pattern

test(num) =
    @match num begin
       ::Float64 ||
        0        ||
        1        ||
        2        => true

        _        => false
    end

test(0)   # true
test(1)   # true
test(2)   # true
test(1.0) # true
test(3)   # false
test("")  # false

Tips: Or Patterns could nested.

ADT Destructuring

You can match ADT in following 3 means:


C(a, b, c) => ... # ordered arguments
C(b = b) => ...   # record syntax
C(_) => ...       # wildcard for destructuring

Here is an example:


@data Example begin
    Natural(dimension :: Float32, climate :: String, altitude :: Int32)
    Cutural(region :: String,  kind :: String, country :: String, nature :: Natural)
end

神农架 = Cutural("湖北", "林区", "中国", Natural(31.744, "北亚热带季风气候", 3106))
Yellostone = Cutural("Yellowstone National Park", "Natural", "United States", Natural(44.36, "subarctic", 2357))

function my_data_query(data_lst :: Vector{Cutural})
    filter(data_lst) do data
        @match data begin
            Cutural(_, "林区", "中国", Natural(dim=dim, altitude)) &&
            if dim > 30.0 && altitude > 1000 end => true

            Cutural(_, _, "United States", Natural(altitude=altitude)) &&
            if altitude > 2000 end  => true
            _ => false

        end
    end
end
my_data_query([神农架, Yellostone])
...
struct A
    a
    b
    c
end

# allow `A` to be destructured as datatypes in current module.
@as_record internal A

@match A(1, 2, 3) begin
    A(1, 2, 3) => ...
end
@use GADT

@data internal Example{T} begin
    A{T} :: (Int, T) => Example{Tuple{Int, T}}
end

@match A(1, 2) begin
    A{T}(a :: Int, b :: T) where T <: Number => (a == 1 && T == Int)
end

Advanced Type Pattern

Instead of TypeLevel feature used in v0.1, an ideal type-stable way to destruct types now is introduced here.

@match 1 begin
    ::String => String
    ::Int => Int
end
# => Int64

@match 1 begin
    ::T where T <: AbstractArray => 0
    ::T where T <: Number => 1
end

# => 0

struct S{A, B}
    a :: A
    b :: B
end

@match S(1, "2") begin
    ::S{A} where A => A
end
# => Int64

@match S(1, "2") begin
    ::S{A, B} where {A, B <: AbstractString} => (A, B)
end
# => (Int64, String)

Side-Effect

To introduce side-effects into pattern matching, we provide a built-in pattern called Do pattern to achieve this. Also, a pattern called Many can work with Do pattern in a perfect way.

Do-Pattern and Many-Pattern


@match [1, 2, 3] begin
    Many(::Int) => true
    _ => false
end # true

@match [1, 2, 3,  "a", "b", "c", :a, :b, :c] begin
    Do(count = 0) &&
    Many(
        a::Int && Do(count = count + a) ||
        ::String                        ||
        ::Symbol && Do(count = count + 1)
    ) => count
end # 9

They may be not used very often but quite convenient for some specific domain.

Active Pattern

This implementation is a subset of F# Active Patterns.

There're 2 distinct active patterns, first of which is the normal form:

@active LessThan0(x) begin
    if x > 0
        nothing
    else
        x
    end
end

@match 15 begin
    LessThan0(_) => :a
    _ => :b
end # :b

@match -15 begin
    LessThan0(a) => a
    _ => 0
end # -15

The second is the parametric version.

@active Re{r :: Regex}(x) begin
    res = match(r, x)
    if res !== nothing
        # use explicit `if-else` to emphasize the return should be Union{T, Nothing}.
        res
    else
        nothing
    end
end

@match "123" begin
    Re{r"\d+"}(x) => x
    _ => @error ""
end # RegexMatch("123")


@active IsEven(x) begin
    if x % 2 === 0
        # use explicit `if-else` to emphasize the return should be true/false.
        true
    else
        false
    end
end

@match 4 begin
    IsEven() => :even
    _ => :odd
end # :even

@match 3 begin
    IsEven() => :even
    _ => :odd
end # :odd

Note that the pattern A{a, b, c} is equivalent to A{a, b, c}().

When enabling the extension Enum with @use Enum, the pattern A is equivalent to A():

@use Enum
@match 4 begin
    IsEven => :even
    _ => :odd
end # :even

@match 3 begin
    IsEven => :even
    _ => :odd
end # :odd

Finally, you can customize the visibility of your own active patterns by giving it a qualifier.

Expr Pattern

This is mainly for AST manipulations. In fact, another pattern called Ast Pattern, would be translated into Expr Pattern.

function extract_name(e)
        @match e begin
            ::Symbol                           => e
            Expr(:<:, a, _)                    => extract_name(a)
            Expr(:struct, _, name, _)          => extract_name(name)
            Expr(:call, f, _...)               => extract_name(f)
            Expr(:., subject, attr, _...)      => extract_name(subject)
            Expr(:function, sig, _...)         => extract_name(sig)
            Expr(:const, assn, _...)           => extract_name(assn)
            Expr(:(=), fn, body, _...)         => extract_name(fn)
            Expr(expr_type,  _...)             => error("Can't extract name from ",
                                                        expr_type, " expression:\n",
                                                        "    $e\n")
        end
end
@assert extract_name(:(quote
    function f()
        1 + 1
    end
end)) == :f

Ast Pattern

This might be the most important update since v0.2.

rmlines = @λ begin
    e :: Expr           -> Expr(e.head, filter(x -> x !== nothing, map(rmlines, e.args))...)
      :: LineNumberNode -> nothing
    a                   -> a
end
expr = quote
    struct S{T}
        a :: Int
        b :: T
    end
end |> rmlines

@match expr begin
    quote
        struct $name{$tvar}
            $f1 :: $t1
            $f2 :: $t2
        end
    end =>
    quote
        struct $name{$tvar}
            $f1 :: $t1
            $f2 :: $t2
        end
    end |> rmlines == expr
end # true

How you create an AST, then how you match them.

How you use AST interpolations($ operation), then how you use capturing patterns on them.

The pattern quote .. end is equivalent to :(begin ... end).

Additonally, you can use any other patterns simultaneously when matching asts. In fact, there're regular patterns inside a $ expression of your ast pattern.

A more complex example presented here might help with your comprehension about this:

ast = quote
    function f(a, b, c, d)
      let d = a + b + c, e = x -> 2x + d
          e(d)
      end
    end
end

@match ast begin
    quote
        $(::LineNumberNode)

        function $funcname(
            $firstarg,
            $(args...),
            $(a && if islowercase(string(a)[1]) end))

            $(::LineNumberNode)
            let $bind_name = a + b + $last_operand, $(other_bindings...)
                $(::LineNumberNode)
                $app_fn($app_arg)
                $(block1...)
            end

            $(block2...)
        end
    end && if (isempty(block1) && isempty(block2)) end =>

         Dict(:funcname => funcname,
              :firstarg => firstarg,
              :args     => args,
              :last_operand => last_operand,
              :other_bindings => other_bindings,
              :app_fn         => app_fn,
              :app_arg        => app_arg)
end

Here is several articles about Ast Patterns.