File: pattern_matching.out.rb

package info (click to toggle)
ruby-algebrick 0.7.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 276 kB
  • sloc: ruby: 1,614; makefile: 3
file content (150 lines) | stat: -rw-r--r-- 6,409 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# Let's define a tree and binary tree to demonstrate the pattern matching abilities.
Tree = Algebrick.type do |tree|
  variants Empty = type,
           Leaf  = type { fields Integer },
           Node  = type { fields tree, tree }
end                                                # => Tree(Empty | Leaf | Node)

BinaryTree = BTree = Algebrick.type do |btree|
  fields! value: Comparable, left: btree, right: btree
  variants Empty, btree
end
    # => BTree(Empty | BTree(value: Comparable, left: BTree, right: BTree))

extend Algebrick::Matching                         # => main

# Product matchers are constructed with #.() syntax.
Leaf.(any) === Leaf[1]                             # => true
Leaf.(1) === Leaf[1]                               # => true
Leaf.(2) === Leaf[1]                               # => false
# There are also some shortcuts to use when product has more fields.
BTree.()                                           # => BTree.(any, any, any)
BTree.(value: any, left: Empty)                    # => BTree.(any, Empty, any)
BTree.(value: any, left: Empty) === BTree[1, Empty, Empty]
    # => true

# Any object responding to #=== can be converted to matcher.
(1..2).to_m                                        # => Wrapper.(1..2)
(1..2).to_m === 2                                  # => true
Empty.to_m                                         # => Empty.to_m
# As matchers are using standard #=== method it does not have to be always converted.
Empty === Empty                                    # => true
Leaf === Leaf[1]                                   # => true

# Tree matches all its values.
[Empty, Leaf[1], Node[Empty, Empty]].all? { |v| Tree === v }
    # => true

# There is also a #match method in Matching module to make pattern matching easier.
match Leaf[1], # supply the value for matching
      # if Leaf.(0) matches :zero is returned
      (on Leaf.(0), :zero),
      # when computation of the result needs to be avoided use block
      # if Leaf.(1) matches block is called and its result is returned
      (on Leaf.(1) do
        (1..10000).inject(:*) # expensive computation
        :one # which is :one in this case
      end)                                         # => :one

# Alternatively case can be used.
case Leaf[1]
when Leaf.(0)
  :zero
when Leaf.(1)
  (1..10000).inject(:*) # expensive computation
  :one
end                                                # => :one

# But that won't work nicely with value deconstruction.
# Each matcher can be marked with #~ method to store value against which is being matched,
# each matched value is passed to the block, ...
match Leaf[0],
      (on ~Leaf.(~any) do |leaf, value|
        [leaf, value]
      end)                                         # => [Leaf[0], 0]

btree = BTree[1,
              BTree[0, Empty, Empty],
              Empty]
    # => BTree[value: 1, left: BTree[value: 0, left: Empty, right: Empty], right: Empty]
match btree,
      (on BTree.(any, ~any, ~any) do |left, right|
        [left, right]
      end)
    # => [BTree[value: 0, left: Empty, right: Empty], Empty]

# or alternatively you can use Ruby's multi-assignment feature.
match btree,
      (on ~BTree do |(_, left, right)|
        [left, right]
      end)
    # => [BTree[value: 0, left: Empty, right: Empty], Empty]


# Matchers also support logical operations #& for and, #| for or, and #! for negation.
Color = Algebrick.type do
  variants Black = atom,
           White = atom,
           Pink  = atom,
           Grey  = type { fields scale: Float }
end                                                # => Color(Black | White | Pink | Grey)

def color?(color)
  match color,
        on(Black | Grey.(-> v { v < 0.2 }), 'black-ish'),
        on(White | Grey.(-> v { v > 0.8 }), 'white-ish'),
        on(Grey.(-> v { v >= 0.2 }) & Grey.(-> v { v <= 0.8 }), 'grey-ish'),
        on(Pink, "that's not a color ;)")
end                                                # => :color?

color? Black                                       # => "black-ish"
color? Grey[0.1]                                   # => "black-ish"
color? Grey[0.3]                                   # => "grey-ish"
color? Grey[0.9]                                   # => "white-ish"
color? White                                       # => "white-ish"
color? Pink                                        # => "that's not a color ;)"

# A more complicated example of extracting node's value and values of its left and right side
# using also logical operators to allow Empty sides.
match BTree[0, Empty, BTree[1, Empty, Empty]],
      (on BTree.({ value: ~any,
                   left:  Empty | BTree.(value: ~any),
                   right: Empty | BTree.(value: ~any) }) do |value, left, right|
        { left: left, value: value, right: right }
      end)                                         # => {:left=>nil, :value=>0, :right=>1}

# It also supports matching against Ruby Arrays
Array.() === []                                    # => true
Array.() === [1]                                   # => false
Array.(*any) === []                                # => true
Array.(*any) === [1]                               # => true
Array.(*any) === [1, 2]                            # => true
Array.(1, *any) === []                             # => false
Array.(1, *any) === [1]                            # => true
Array.(1, *any) === [1, 2]                         # => true

match [],
      on(~Array.to_m) { |v| v }                    # => []
match [],
      on(~Array.()) { |v| v }                      # => []
match [1, 2],
      on(~Array.(*any)) { |v| v }                  # => [1, 2]
match [1, 2],
      on(~Array.(*any)) { |(v, _)| v }             # => 1
match [1, 2, 3],
      on(Array.(any, *~any)) { |v| v }             # => [2, 3]
match [:first, 1, 2, 3],
      on(Array.(:first, ~any, *any)) { |v| v }     # => 1
match [:+, 1, 2, :foo, :bar],
      (on Array.(:+, ~Integer.to_m, ~Integer.to_m, *~any) do |int1, int2, rest|
        { sum: int1 + int2, rest: rest }
      end)                                         # => {:sum=>3, :rest=>[:foo, :bar]}


# There is also a more funky syntax for matching
# using #>, #>> and Ruby 1.9 syntax for lambdas `-> {}`.
match Leaf[1],
      Leaf.(0) >> :zero,
      Leaf.(~any) >-> value do
        (1..value).inject(:*) # an expensive computation
      end                                          # => 1