MPark - Patterns - Pattern Matching in C++ - Michael Park - CppCon 2017
MPark - Patterns - Pattern Matching in C++ - Michael Park - CppCon 2017
Patterns
Pattern Matching in C++
https://github.com/mpark/patterns
Michael Park
@mpark
@mcypark
1
History
OCaml Rust
SNOBOL ML C++ Haskell Scala Swift
2
Why doesn't C++
have feature X?
3
No one proposed it
4
Standards Proposal
5
Purpose
6
Overview
• MPark.Patterns
7
Algebraic Data Types
8
Algebraic Data Types
9
Pattern matching is the best tool for decomposing
Algebraic Data Types
10
What is Pattern Matching?
11
“In pattern matching, we attempt to match
values against patterns and, if so desired,
bind variables to successful matches.”
https://en.wikibooks.org/wiki/Haskell/Pattern_matching
12
“In pattern matching, we attempt to match
values against patterns and, if so desired,
bind variables to successful matches.”
let p = Point { x: 7, y: 0 };
match p {
Point { x: 0, y } => println!("Y axis: {}", y),
Point { x , y: 0 } => println!("X axis: {}", x),
Point { x , y } => println!("{}, {}", x, y)
}
https://en.wikibooks.org/wiki/Haskell/Pattern_matching
12
Pattern matching is a declarative approach in lieu of
manually testing for a value with a sequence of
conditionals and extracting the desired components.
let p = Point { 7,
auto x: 0
7,};
y: 0 };
if (p.x
match p {
== 0) printf("Y axis: %d\n", p.y);
else
Point
if (p.y
{ x: ==
0, y
0) printf("X
} => println!("Y
axis: %d\n",
axis:p.x);
{}", y),
else
Point { x , y: 0
printf("%d,
} => println!("X
%d\n", p.x,
axis:p.y);
{}", x),
Point { x , y } => println!("{}, {}", x, y)
}
12
Evaluating Expressions
enum Expr {
Int(i32), values
Neg(Box<Expr>), patterns
Add(Box<Expr>, Box<Expr>), variables
Mul(Box<Expr>, Box<Expr>),
}
13
Evaluating Expressions
enum Expr {
Int(i32), values
Neg(Box<Expr>), patterns
Add(Box<Expr>, Box<Expr>), variables
Mul(Box<Expr>, Box<Expr>),
}
13
Evaluating Expressions
enum Expr {
Int(i32), values
Neg(Box<Expr>), patterns
Add(Box<Expr>, Box<Expr>), variables
Mul(Box<Expr>, Box<Expr>),
}
13
Evaluating Expressions
enum Expr {
Int(i32), values
Neg(Box<Expr>), patterns
Add(Box<Expr>, Box<Expr>), variables
Mul(Box<Expr>, Box<Expr>),
}
13
Evaluating Expressions
struct Expr { virtual ~Expr() = default; };
struct Int : Expr { int value; };
struct Neg : Expr { shared_ptr<Expr> expr; };
struct Add : Expr { shared_ptr<Expr> lhs, rhs; };
struct Mul : Expr { shared_ptr<Expr> lhs, rhs; };
14
Evaluating Expressions
14
LLVM
15
Visitor
struct Int; struct Neg; struct Add; struct Mul; int eval(const Expr &expr) {
struct Eval : Expr::Vis {
struct Expr { void operator()(const Int &that) const {
struct Vis { result = that.value;
virtual void operator()(const Int &) const = 0; }
virtual void operator()(const Neg &) const = 0; void operator()(const Neg &that) const {
virtual void operator()(const Add &) const = 0; result = -eval(*that.expr);
virtual void operator()(const Mul &) const = 0; },
}; void operator()(const Add &that) const {
result = eval(*that.lhs) + eval(*that.rhs);
virtual ~Expr() = default; }
virtual void accept(const Vis&) const = 0; void operator()(const Mul &that) {
}; result = eval(*that.lhs) * eval(*that.rhs);
}
struct Int : Expr {
void accept(const Vis& vis) const { vis(*this); } int &result;
int value; };
};
int result;
struct Neg : Expr { expr.accept(Eval{result});
void accept(const Vis& vis) const { vis(*this); } return result;
shared_ptr<Expr> expr; }
};
!
};
16
Insight from Swift
http://apple-swift.readthedocs.io/en/latest/Pattern%20Matching.html
17
Various Forms of
Pattern Matching in C++
18
Various Forms of
Pattern Matching in C++
19
Matching Simple Types
int x = 1;
switch (x) {
switch case 1: case 2: printf("one or two\n"); break;
case 3: printf("three\n"); break;
default: printf("anything\n");
}
string s = "c";
20
Matching Product Types
pair<X, Y> p;
values
apply(
[](pair<X, Y> p, Z z) {
apply([](X x, Y y) {
apply([z](X x, X y) {
apply // ...
// ...
}, p);
}, p);
}, t);
auto [p, z] = t;
Structured auto [x, y] = p;
auto [x, y] = p;
Binding
auto [[x, y], z] = t;
21
Matching Sum Types
struct Expr;
struct Neg { shared_ptr<Expr> expr; }; values
22
MPark.Patterns
23
Main Goals
• Declarative
• Structured
• Cohesive
• Composable
24
Basic Structure
values
patterns
variables
#include <mpark/patterns.hpp>
25
Back to the Point
struct Point { int x; int y; };
values
26
Re: Evaluating Expressions
struct Expr;
values
namespace std {
template <>
struct variant_size<Expr> // Opt into `VariantLike`
: integral_constant<size_t, 4> {};
} // namespace std
27
Re: Evaluating Expressions
int eval(const Expr &expr) {
return match(expr)( values
28
Optional Flag
values
29
Patterns So Far
Pattern Matches Example
Expression Any 0
30
Simplifying Expressions
Let's simplify the expression tree we've been evaluating.
Simplification Rules:
• -(-v) == v
• v + 0 == v
• v × 1 == v
• v × 0 == 0
31
Simplifying Expressions
struct Expr;
namespace std {
template <>
struct variant_size<Expr> // Opt into `VariantLike`
: integral_constant<size_t, 4> {};
} // namespace std
32
Simplifying int
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
pattern(as<int>(_)) = [&] { return expr; },
// ...
);
}
result 17 expr
33
Simplifying -(-v)
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Neg>(ds(some(as<Neg>(ds(arg)))))) = [](auto &e) {
return simplify(e);
},
// ...
);
} - expr
-
e
result 17
34
Simplifying v + 0
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Add>(ds(some(as<int>(0)), arg))) = [](auto &r) {
return simplify(r);
},
pattern(as<Add>(ds(arg, some(as<int>(0))))) = [](auto &l) {
return simplify(l);
},
// ...
); + expr
}
l r
result 17 0
35
Simplifying v × 1
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Mul>(ds(some(as<int>(1)), arg))) = [](auto &r) {
return simplify(r);
},
pattern(as<Mul>(ds(arg, some(as<int>(1))))) = [](auto &l) {
return simplify(l);
},
// ...
); × expr
}
l r
result 17 1
36
Simplifying v × 0
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Mul>(ds(arg(some(as<int>(0))), _))) = [](auto &l) {
return l;
},
pattern(as<Mul>(ds(_, arg(some(as<int>(0)))))) = [](auto &r) {
return r;
},
// ...
); × expr
}
l r
17 0 result
37
Simplifying -
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Neg>(ds(arg))) = [&](auto &e) {
auto simple_e = simplify(e);
return simple_e == e
? expr
: simplify(make_shared<Expr>(Neg{simple_e}));
},
// ...
); result ➖ ➖ expr
} e
result ➖ expr
e ➕
simple_e 17
simple_e 17 0
38
Simplifying +
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Add>(ds(arg, arg))) = [&](auto &l, auto &r) {
auto simple_l = simplify(l), simple_r = simplify(r);
return simple_l == l && simple_r == r
? expr
: simplify(make_shared<Expr>(Add{simple_l,
simple_r}));
},
// ... result ➕ ➕ expr
); l
r
} result ➕ expr
l r
➕
simple_l 17 23 simple_l 17
simple_r simple_r 23 0
39
Simplifying ×
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
// ...
pattern(as<Mul>(ds(arg, arg))) = [&](auto &l, auto &r) {
auto simple_l = simplify(l), simple_r = simplify(r);
return simple_l == l && simple_r == r
? expr
: simplify(make_shared<Expr>(Mul{simple_l,
simple_r}));
},
// ... result × × expr
); l
r
} result × expr
l r
➕
simple_l 17 23 simple_l 17
simple_r simple_r 23 0
40
Putting it all together
shared_ptr<Expr> simplify(const shared_ptr<Expr> &expr) {
return match(*expr)(
pattern(as<int>(_)) = [&] { return expr; }, // v
pattern(
anyof(as<Neg>(ds(some(as<Neg>(ds(arg))))), // -(-v)
as<Add>(ds(some(as<int>(0)), arg)), // v + 0
as<Add>(ds(arg, some(as<int>(0)))),
as<Mul>(ds(some(as<int>(1)), arg)), // v * 1
as<Mul>(ds(arg, some(as<int>(1))))) = [](auto &e) {
return simplify(e);
},
pattern(
anyof(as<Mul>(ds(arg(some(as<int>(0))), _)), // v * 0
as<Mul>(ds(_, arg(some(as<int>(0)))))) = [](auto &zero) {
return zero;
},
pattern(as<Neg>(ds(arg))) = [&](auto &e) { /* ... */ },
pattern(as<Add>(ds(arg, arg))) = [&](auto &l, auto &r) { /* ... */ },
pattern(as<Mul>(ds(arg, arg))) = [&](auto &l, auto &r) { /* ... */ }
);
}
41
The real power of pattern matching is that the
patterns are built the same way as the values
42
A Few More Things...
43
Identifiers
44
Pattern Guard
int fib(int n) {
return match(n)(
pattern(arg) = [](int x) {
WHEN(x <= 0) { return 0; };
},
pattern(1) = [] { return 1; },
pattern(arg) = [](int x) {
return fib_v1(x - 1) + fib_v1(x - 2);
});
}
45
Identifier Pattern
struct Point { int x; int y; };
auto p = Point { 7, 0 };
IDENTIFIERS(x, y);
match(p)(
pattern(ds(0 , y )) = [](int y) {
printf("Y axis: %d\n", y);
},
pattern(ds(x , 0 )) = [](int x) {
printf("X axis: %d\n", x);
},
pattern(ds(x , y )) = [](int x, int y) {
printf("%d, %d\n", x, y);
}
);
46
Identifier Pattern
Repeated identifiers mean the values have to be equal!
IDENTIFIERS(x, y);
match(t)(
pattern(ds(x, x, x)) = [] { printf("all the same!"); },
pattern(ds(x, y, x)) = [] { printf("bordered!"); },
pattern(_) = [] { printf("No recognized pattern"); });
// prints: "bordered!"
47
Back to the Pattern Guard
int fib(int n) {
IDENTIFIERS(x);
return match(n)(
pattern(x).when(x <= 0) = [](int) { return 0; },
pattern(1) = [] { return 1; },
pattern(x) = [](int x) {
return fib_v1(x - 1) + fib_v1(x - 2);
});
}
48
Variadic Pattern
Syntax: variadic(<pattern>)
match(t)(
pattern(ds(arg, arg, arg)) = [](auto, auto, auto) {});
match(t)(
pattern(ds(variadic(arg))) = [](auto, auto, auto) {});
49
Inside of a template
template <typename Tuple>
void print(Tuple &&tuple) {
match(forward<Tuple>(tuple))(
pattern(ds(variadic(arg))) = [](auto &&... xs) {
int dummy[] = { (cout << xs << ' ', 0)... };
(void)dummy;
}
);
}
50
This is C++17 apply!
template <typename F, typename Tuple>
decltype(auto) apply(F &&f, Tuple &&t) {
return match(forward<T>(t))(
pattern(ds(variadic(arg))) = forward<F>(f));
}
51
almost tuple_cat
tuple<int, int, int> t = { 101, 202, 101 };
match(t)(
pattern(ds(variadic(arg))) = [](auto, auto, auto) {});
52
almost tuple_cat
template <typename... Tuples>
auto almost_tuple_cat(Tuples &&... tuples) {
return match(forward<Tuples>(tuples)...)(
pattern(variadic(ds(variadic(arg)))) = [](auto &&... xs) {
return make_tuple(forward<decltype(xs)>(xs)...);
});
}
53
Performance
54
MPark.Patterns
void fizzbuzz() {
using namespace mpark::patterns;
for (int i = 1; i <= 100; ++i) {
match(i % 3, i % 5)(
pattern(0, 0) = [] { printf("fizzbuzz\n"); },
pattern(0, _) = [] { printf("fizz\n"); },
pattern(_, 0) = [] { printf("buzz\n"); },
pattern(_, _) = [i] { printf("%d\n", i); });
}
}
55
switch
void fizzbuzz() {
for (int i = 1; i <= 100; ++i) {
switch (i % 3) {
case 0:
switch (i % 5) {
case 0: printf("fizzbuzz\n"); break;
default: printf("fizz\n"); break;
}
break;
default:
switch (i % 5) {
case 0: printf("buzz\n"); break;
default: printf("%d\n", i); break;
}
break;
}
}
}
56
fizzbuzz(): # call printf imul rax, rcx, jmp .LBB1_8
@fizzbuzz() jmp .LBB0_9 1431655766 .LBB1_6: # in Loop:
push rbx .LBB0_4: # in Loop: mov rdx, rax Header=BB1_1 Depth=1
mov ebx, 1 Header=BB0_1 Depth=1 shr rdx, 63 mov edi, .Lstr
.LBB0_1: # =>This Inner test eax, eax shr rax, 32 jmp .LBB1_8
Loop Header: Depth=1 je .LBB0_7 add eax, edx .LBB1_7: # in Loop:
movsxd rcx, ebx mov edi, .Lstr.4 lea edx, [rax + Header=BB1_1 Depth=1
imul rax, rcx, jmp .LBB0_8 2*rax] mov edi, .Lstr.5
1431655766 .LBB0_6: # in Loop: imul rax, rcx, .LBB1_8: # in Loop:
mov rdx, rax Header=BB0_1 Depth=1 1717986919 Header=BB1_1 Depth=1
shr rdx, 63 mov edi, .Lstr mov rsi, rax call puts
shr rax, 32 jmp .LBB0_8 shr rsi, 63 .LBB1_9: # in Loop:
generated code!
1717986919 Header=BB0_1 Depth=1 mov eax, ecx xor eax, eax
mov rsi, rax call puts sub eax, esi pop rbx
shr rsi, 63 .LBB0_9: # in Loop: cmp ecx, edx ret
sar rax, 33 Header=BB0_1 Depth=1 je .LBB1_4 .L.str.3:
add eax, esi inc ebx test eax, eax .asciz "%d\n"
lea esi, [rax + cmp ebx, 101 je .LBB1_6
4*rax] jne .LBB0_1 mov edi, .L.str.3 .Lstr:
mov eax, ecx pop rbx xor eax, eax .asciz "buzz"
sub eax, esi ret mov esi, ebx
cmp ecx, edx main: # @main call printf .Lstr.4:
je .LBB0_4 push rbx jmp .LBB1_9 .asciz "fizz"
test eax, eax mov ebx, 1 .LBB1_4: # in Loop:
je .LBB0_6 .LBB1_1: # =>This Inner Header=BB1_1 Depth=1 .Lstr.5:
mov edi, .L.str.3 Loop Header: Depth=1 test eax, eax .asciz "fizzbuzz"
xor eax, eax movsxd rcx, ebx je .LBB1_7
mov esi, ebx mov edi, .Lstr.4
57
Future Work
58
Future Work
• Exhaustiveness checking
59
MPark.Patterns
Pattern Matching in C++
https://github.com/mpark/patterns
Michael Park
@mpark
@mcypark
60
Implementation Peek
61
Structure
using namespace mpark::patterns;
IDENTIFIERS(<identifier>...); // optional
match(<expr>...)(
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
// ...
);
62
Structure
using namespace mpark::patterns;
IDENTIFIERS(<identifier>...); // optional
match(<expr>...)(
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
// ...
);
template <typename... Patterns>
struct Pattern {
template <typename F>
Case<Pattern, F> operator=(F &&f) && noexcept;
62
Structure
using namespace mpark::patterns;
IDENTIFIERS(<identifier>...); // optional
match(<expr>...)(
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
// ...
);
template <typename
<typename...
Pattern,
Patterns>
typename F>
struct Case
Pattern
{ {
Pattern
template
pattern;
<typename F>
FCase<Pattern,
f; F> operator=(F &&f) && noexcept;
};
Ds<Patterns &&...> patterns;
};
62
Structure
using namespace mpark::patterns;
IDENTIFIERS(<identifier>...); // optional
match(<expr>...)(
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
pattern(<pattern>...) = [](<binding>...) { /* ... */ },
// ...
);
template
template <typename...
<typename...
<typename Values>
Pattern,
Patterns>
typename F>
struct
struct Match {
Pattern
Case { {
template
Pattern <typename
template
pattern; Pattern,
<typename F> typename F, typename... Cases>
decltype(auto) operator()(Case<Pattern, F> &&case_,
FCase<Pattern,
f; F> operator=(F &&f) && noexcept;
Cases&&... cases) && {
}; // ...
} Ds<Patterns &&...> patterns;
};
tuple<Values &&...> values;
}
62
Match::operator()
if constexpr (sizeof...(Cases) == 0) {
throw match_error{};
} else {
return move(*this)(forward<Cases>(cases)...);
}
}
63
Match::operator()
if constexpr (sizeof...(Cases) == 0) {
throw match_error{};
} else {
return move(*this)(forward<Cases>(cases)...);
}
}
63
match_result<T>
match_result(no_match_t) noexcept {}
match_result(nullopt_t) = delete;
64
match_result-aware invoke
65
Expression Pattern
template <typename ExprPattern, typename Value, typename F>
auto try_match(const ExprPattern &expr_pattern,
Value &&value,
F &&f) {
return expr_pattern == forward<Value>(value)
? match_invoke(forward<F>(f))
: no_match;
}
66
Arg Pattern
template <typename Pattern, typename Value, typename F>
auto try_match(const Arg<Pattern> &arg,
Value &&value,
F &&f) {
if constexpr (is_void_v<Pattern>) {
return match_invoke(forward<F>(f),
forward<Value>(value));
} else {
return try_match(arg.pattern,
forward<Value>(value),
forward<F>(f));
}
}
67