Pattern Matching
At the very heart of Atomy is pattern-matching, and its behavior with dispatch. Patterns are used to both check for and deconstruct a value in one fell swoop. Depending on the scenario, a pattern-match may error if it doesn't match (=
, =!
, and sometimes methods), or just silently fail (match
).
When defining a method, a few things occur. First, the target of the definition is determined based on the message receiver pattern. See A Pattern's Target for a more detailed account, but you don't have to memorize any of this.
Once the target is determined, Atomy will check to see if the method is already defined on the target, perhaps with different patterns. If it doesn't exist, the method is simply inserted. If it does, the method is inserted based on its "precision" in relation to the others.
This sorting is the magic dust. Pattern A is said to be "more precise" than pattern B if it matches fewer cases, or if pattern B will always match what pattern A will match, but not the other way around.
For example, the pattern 1
is much more precise than the pattern x
- the former only matches the integer 1
, while the latter will match any value! In addition, with two Constant
patterns, one specifying a subclass of the other, the subclass is more precise.
Care is taken to maintain proper precision-based ordering so that an object may be extended after the fact with method definitions that handle more specific cases. For example, let's say in your original code you define this:
1 foo := -1
2 foo := -2
_ foo := .umm
Without precision sorting, not only would the order of definitions matter greatly, but no one would be able to handle any other cases! If someone came along and tried defining 3 foo := -3
, it would be unreachable, because the wildcard _
matches everything. They'd always get back .umm
for everything but 1
and 2
. Likewise, if you defined that catch-all case first, it would override the others.
With precision sorting, things "just work." You can define your methods in whatever order you want - maybe writing the catch-all first and then drilling into the details afterward. It doesn't matter.
Types of Patterns
There are a ton of different pattern-matchers that come with Atomy, owing to the fact that they are so easy to define and can provide for immense expressive power.
_
,foo
,@foo
,@@foo
,$foo
, ...Possibly-named wildcard matches. Global, instance, and class variables behave the same, but bind as their respective types rather than as locals.
Example:
> _ = 1 1 > a = 2 2 > a 2
1
,4.0
,true
,false
,"foo"
,.foo
, ...Primitive literal values that will match only themselves.
Example:
> 1 = 1 1 > 1 = 2 Atomy::PatternMismatch: pattern of type `Match' did not match value `2' > 4.0 = 4.0 4.0 > 4.0 = 4.000000000001 Atomy::PatternMismatch: pattern of type `Literal' did not match value `4.000000000001' > "foo" = "foo" "foo"
foo: pattern
,foo { pattern }
A named pattern-match. Matches
pattern
, binding the value it matches to a local variable,foo
.Example:
> (foo: 2) = 2 2 > foo 2 > (foo: 2) = 1 Atomy::PatternMismatch: pattern of type `Named' did not match value `1'
head . tail
Matches a non-empty list, pattern-matching its first value on
head
and the rest of it ontail
.Example:
> (x . xs) = [1, 2, 3] [1, 2, 3] > x 1 > xs [2, 3]
[]
,[pattern, pattern-2]
, ...Matches a list of fixed length, recursively matching each
pattern
on each of its values.Example:
> [] = [] [] > [a, b] = [1, 2] [1, 2] > [c, 4] = [3, 4] [3, 4] > [a, b, c] [1, 2, 3]
.foo(x)
,.foo(x, _)
, ...Matches a particle value, recursively pattern-matching the particle's values. A wildcard in a particle pattern matches placeholders, but no other pattern will.
Example:
> .foo(x) = .foo(2) .foo(2) > x 2 > .foo(_) = .foo(_) .foo(_) > .foo(_) = .foo(2) .foo(2) > .foo(2) = .foo(_) Atomy::PatternMismatch: pattern of type `Particle' did not match value `.foo(_)'
'a
,`a
,`(1 + ~b)
, ...Matches expression values recursively. Unquotes serve as nested patterns, with the same recursive semantics as quasiquotation. Splice unquotes are similar, but act as "splats", matching the rest of its container.
Example:
> `a = `a a > `(1 + ~b) = '(1 + 2) 1 + 2 > `(1 + ~'2) = '(1 + 2) 1 + 2 > ``(1 + ~~c) = ``(1 + ~3) `(1 + ~3) > [b, c] [2, 3] > `[1, 2, ~*['3, '4]] = '[1, 2, 3, 4] [1, 2, 3, 4] > `[1, 2, ~*xs] = '[1, 2, 1 + 2, 1 + 3] [1, 2, 1 + 2, 1 + 3] > xs [1 + 2, 1 + 3]
{ expression }
Used to target a value's singleton class;
expression
is evaluated and its singleton class is the target for a definition. This pattern acts as a wildcard otherwise.Example:
> a = "foo" "foo" > { a } fizz := 42 #<Rubinius::CompiledMethod fizz file=wrapper> > a fizz 42 > "foo" fizz NoMethodError: undefined method `fizz' on an instance of String.
&pattern
Ruby-style proc-arg pattern, used to capture a block passed to a method as a
Proc
.Example:
> x my-block(&y) := y [x] #<Rubinius::CompiledMethod my_block file=wrapper> > 1 my-block: 42 Atomy::MethodFail: method 'my_block' does not understand the given arguments: [] > 1 my-block [x]: x + 2 Atomy::MethodFail: method 'my_block' does not understand the given arguments: []
*pattern
Ruby-style "splat" pattern, typically used to match the rest of the arguments in a method definition.
Example:
> x my-splat(y, *zs) := [x, y, zs] #<Rubinius::CompiledMethod my_splat file=wrapper> > 1 my-splat(2, 3, 4, 5) [1, 2, [3, 4, 5]] > x my-splat(y, *[3, zs]) := [x, y, zs] #<Rubinius::CompiledMethod my_splat file=wrapper> > 1 my-splat(2, 3, 4) [1, 2, 4]
pattern = default
Ruby-style default argument pattern. Will match
pattern
on the argument, or ondefault
's value if the argument is not given.Example:
> x my-default(y = x + 1) := [x, y] #<Rubinius::CompiledMethod my_default file=wrapper> > 1 my-default [1, 2] > 1 my-default(42) [1, 42]
pattern ? predicate
Matches
pattern
, and then evaluatespredicate
with the value asself
, only succeeding if both the match and the predicate succeed.If
pattern
is not given,_
is assumed.Example:
> (Integer ? odd?) = 41 41 > (Integer ? odd?) = 42 Atomy::PatternMismatch: pattern of type `Predicate' did not match value `42' > (Integer ? odd?) = "foo" Atomy::PatternMismatch: pattern of type `Predicate' did not match value `"foo"' > (? odd?) = 41 41 > (? odd?) = 42 Atomy::PatternMismatch: pattern of type `Predicate' did not match value `42' > (? odd?) = "foo" NoMethodError: undefined method `odd?' on an instance of String.
pattern-1 & pattern-2
Succeeds only if both patterns match the value. This is similar to
foo: pattern-2
, but you can specify a pattern rather than a named wildcard in place offoo
.Example:
> (Integer & 41) = 41 41 > (Integer & 42) = 41 Atomy::PatternMismatch: pattern of type `And' did not match value `41' > (a & b) = x NoMethodError: undefined method `x' on #<Atomy::Module:0x3f4b8> (Atomy::Module) > [a, b] NoMethodError: undefined method `a' on #<Atomy::Module:0x3f4b8> (Atomy::Module)
pattern-1 | pattern-2
Succeeds if either patterns match the value. This pattern is short-circuiting; if one matches, the other won't be attempted. Therefore, don't rely on bindings in the second pattern being set.
Example:
> (one | two) = 41 41 > [one, two] NoMethodError: undefined method `two' on #<Atomy::Module:0x3fc80> (Atomy::Module) > (1 | 2) = 2 2 > (1 | x) = 2 2 > x 2
pattern with(expression, sub-pattern)
Matches
pattern
on the value. If the match succeeds, matchessub-pattern
on the result of evaluatingexpression
with the value asself
. Useful for matching things like instance variables. An omittedpattern
implies_
.Example:
> Integer with(odd?, true) = 1 1 > data(MyPoint(@x, @y)) nil > with(@x, 1) = MyPoint new(1, 2) #<Rubinius::BlockEnvironment:0x411ac>
A Pattern's Target
When defining a method, the receiver in the message-pattern determines where the method is inserted.
_
,foo
,@foo
,@@foo
,$foo
, ...Object
{ foo bar }
foo bar
's singleton classFoo
,Foo::Bar
,::Bar
, ...the class named by the constant
1
,2
, ...Integer
true
TrueClass
false
FalseClass
1.0
,2.0
, ...Float
""
,"foo"
, ...String
head . tail
,[]
,[pattern, pattern-2]
, ...List
'x
,`x
,`(1 + ~y)
, ...Expression
.foo
Symbol
.foo(1)
Particle
foo: pattern
,foo { pattern }
target of pattern
pattern = default
target of pattern
pattern ? predicate
target of pattern
? predicate
Object
pattern-1 & pattern-2
target of pattern-1
pattern-1 | pattern-2
target of pattern-1
pattern-1 with(expression, pattern-2)
target of pattern-1
&foo
,*foo
Object
Thus, 1 foo := x
is a definition placed on Integer
, while { self } foo
is inserted on self
's singleton class, similar to def self.foo; ...; end
in Ruby.