Patterns

Patterns are the magic gravy that makes Fate what it is. In fact, they're why Fate exists. Let me explain: Over the past years, I've been developing systems that are highly decoupled and heavily reliant on message queues. Almost exclusively, the data being passed around is JSON.

Don't get me wrong, JSON is great, but because it's a format designed to transport arbitrary data, it lacks the ability to implicitly convey information about higher level language constructs or validity. This might suck, unless you have a way to infer something about the data you're seeing, and that's where Patterns enter the picture.

Simple Patterns

A simple Pattern looks like this:

~42  # this will match the number 42

A Pattern could also look like this:

# this will match all numbers greater 
# than 30 and less than or equal to 100
~(it > 30 and it <= 100)

Array Patterns

One can also match against an Array of values.

let ZeroOnLeft = ~[0, _]

print("yep") if [0, 2] like ZeroOnLeft

The ZeroOnLeft pattern matches an Array where the first element is 0 and the second element matches the wildcard (_) symbol, meaning that it can be any value, but it must be present. As a result, the Array must be at least two elements in length.

Object Patterns

Try this Pattern on for size:

let Primate = ~{
  kingdom: 'Animalia',
  class: 'Mammalia',
  order: 'Primate'
}

let MonkeyWithFourAsses = Primate && ~{ asses: 4 }

It's leveraging a functional combination operator called && to combine two Patterns into a single one. There's also an or (||) combination operator.

Object Pattern Expressions

Object Patterns can also contain expressions using the it keyword or relative property references. A relative property reference is an identifier that begins with a single ..

let MonkeyWithMoreThanFourAsses = ~{
  kingdom: 'Animalia',
  class: 'Mammalia',
  order: 'Primate',
  .name='Bonzo' and .asses > 4
}

In this example, the expression .name='Bonzo' and .asses > 4 will inspect the properties of the Object being matched. Such expressions give a level of power that can't be achieved with simple key/value pairs.

Nested Patterns

When a Pattern is a set of nested Objects and\/or Arrays, it will perform nested processing.

let DogWhoseFatherLikesRed = ~{
  animal: 'dog',
  father: {
    favoriteColors: 'red' in it
  }
}

let otto = {
  animal: 'dog',
  name: 'otto',
  favoriteColors: ['blue'],
  father: {
    animal: 'dog',
    name: 'daddy',
    favoriteColors: ['green', 'blue', 'red']
  }
}

if otto like DogWhoseFatherLikesRed
  print("yep")
end

As you can see, it will always refer to the current Array or Object nesting, rather than the original object being matched.

Matching Multiple Patterns

The like operator lets one quickly evaluate an expression against a Pattern. But what if you need to evaluate an expression against several potential matching Patterns? This is where a match expression becomes useful.

let myNumber = 42

let result = match myNumber
  41: "forty-one was matched"
  it > 41: "some number greater than forty-one was matched"
  else: "nothing was matched"
end

If no else clause is included in the match, and the expression can't be matched to one of the Patterns, then a runtime error will be raised.

Matcher Function

A match expression that doesn't include an expression to process will return a function that can be used repeatedly to perform the matching transformation.

let myMatcher = match
  41: "forty-one was matched"
  else: "something other than 41"
end

41 | myMatcher | print
42 | myMatcher | print

Combining Patterns (&&, ||)

Patterns in Fate can be combined using Function Combination operators (&& and ||). What this means is that the Pattern operands will be combined into a single Pattern whose argument is passed through to the operand Patterns. For example:

import io

let Floats = ~{ buoyant: true },
    MadeOfWood = ~{ material: 'wood' },
    DuckWeight = ~{ weight: '1kg' },
    Witch = Floats && MadeOfWood && DuckWeight

let connie = { name: 'Connie Booth', buoyant: true, weight: '1kg', material: 'wood' },
    verySmallRocks = { bouyant: true, weight: '1kg', material: 'stone' }

connie like Witch | io.print
verySmallRocks like Witch | io.print

Here, Witch is a combined Pattern. Connie Booth matches this pattern, while Very Small Rocks do not.

The Patterns Module

If this isn't enough, Fate also provides a module that includes several pre-defined patterns and functions building compound patterns. For example, what if you want to create a Pattern for an array that can store only non-negative integers and that array must have between 1 and 10 elements?

import io
from pattern import ArrayOf, MinLength, MaxLength, NonNegativeInteger

let MyArray = ArrayOf(NonNegativeInteger) && MinLength(1) && MaxLength(10)

[0, 1, 2, 3, 4, 5] like MyArray                  | io.print  # false - zero
["hello", "there", 9] like MyArray               | io.print  # false - strings
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] like MyArray | io.print  # false - too big
[1, 2, 3, 4, 5] like MyArray                     | io.print  # true

results matching ""

    No results matching ""