Pattern
- Literal Pattern
- Capturing pattern
- Type Pattern
- As-Pattern, And Pattern
- Guard
- Range Pattern
- Predicate
- Reference Pattern
- Custom Pattern, Dict, Tuple, Array
- Or Pattern
- ADT destructuring, GADTs
- Advanced Type Pattern
- Side Effect
- Active Pattern
- Expr Pattern
- Ast 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:
NumberAbstractStringSymbol
Capturing Pattern
@match 1 begin
x => x + 1
end
# => 2Note 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 UppercaseCapturingExtension UppercaseCapturing conflicts with Enum.
Any questions about Enum, check Active Patterns.
Type Pattern
@match 1 begin
::Float => nothing
b :: Int => b
_ => nothing
end
# => 1There 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
endGuard
@match x begin
x && if x > 5 end => 5 - x # only succeed when x > 5
_ => 1
endPredicate
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 # 2Reference 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"
endCustom 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 pattern(like
Elixir's dictionary matching or ML record matching)
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- Tuple pattern
@match (1, 2, (3, 4, (5, ))) begin
(a, b, (c, d, (5, ))) => (a, b, c, d)
end
# => (1, 2, 3, 4)- Array pattern(much more efficient than Python for taking advantage of array views)
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]
4Or 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("") # falseTips: 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])
...- Support destructuring Julia types defined regularly
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- About GADTs
@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 # 9They 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 # :oddNote 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 # :oddFinally, 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)) == :fAst 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 # trueHow 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)
endHere is several articles about Ast Patterns.