Capture Semantics
As part of my efforts on ts-unify, I needed a way to represent ‘capturing’ and transforming values in a data structure from one form to another. This article elucidates those extraction semantics.
Continuation of prior article.
Basic capturing
The capture primitive $
represents a particlar value to capture from a
structure.
const matcher = match({
person: {
name: "Alice",
age: $("age"),
},
});
In the above example, the ‘capture bag’ extracted for a input value of
{ person: { name: "Alice", age: 200 } }
would be { age: 200 }
. This form
minimally specifies that the key ‘age’ should exist, but the value could be
fulfilled by null
or undefined
, as long as the key is present.
Extra keys present on the underlying value but not the specified shape are ignored.
Multiple captures
Multiple capture keys simply separately add to the capture bag:
const matcher = match({
person: {
name: "Alice",
age: $("age"),
},
location: $("location"),
});
So the capture bag becomes: { age, location }
in this case.
Multiple captures of equivalent capture term
If one capture term with a particular name is present in multiple places in the shape pattern, this represents a constraint that the two instances must be the same deeply equivalent value.
const matcher = match({
person: {
name: "Alice",
age: $("age"),
},
pet: {
age: $("age"),
},
});
So the above matcher only matches if pet.age
and person.age
are the same
value.
Implicit capture by key
If the capture term is named equivalently to its corresponding key, we may elide
the invocation of $
and leave it as a bare function. The matching engine then
will implicitly bind that key’s value to the corresponding bag entry.
const matcher = match({
person: {
name: "Alice",
age: $,
},
});
This form is equivalent to the one priorly displayed.
Array element capture position
Tuple and array elements may be captured by emplacing the capture term in the appropriate slot:
const matcher = match({
person: {
name: "Alice",
inventory: [$("item")],
},
});
The above specifier says, “only match if the inventory has only one element”,
and “extract that element into ‘item’” - so the resultant capture bag is
{ item }
.
Implicit array element capture
In the case of e.g. { inventory: [$] }
, the capture implicitly takes the name
inventory
.
Spread array elements
The capture primitive may be used in an array spread case to extract out subarrays.
const matcher = match({
person: {
name: "Alice",
inventory: [...$("items"), "pencil"],
},
});
The above matcher only matches if the last element in inventory is "pencil"
,
and extracts out the prior elements (may result in an empty array) into the
capture term keyed by "items"
.
Implicit spread array elements
Equivalently, { inventory: ["pencil", ...$] }
would match only if the first
entry is a "pencil"
value, binding to the inventory
key in the resultant
capture bag.
Object spread captures
The capture primitive $
may also be used in an object spread position. This
assigns all non-pattern-specified keys into the specified capture term.
const matcher = match({
person: {
name: "Alice",
...$("attrs"),
},
});
In this case, the capture term "attrs"
is granted all attributes present on
person
except for "name"
.
Implicit object spread capture
The $
primitive may also be used as a bare object spread.
const matcher = match({
person: {
name: "Alice",
...$,
},
});
In this case, the other attributes are emplaced on a key "person"
in the
resultant capture bag.
Full wildcard
If an explicitly named capture is used directly without being on a subobject, that entire object is supplied on the given capture key:
const matcher = match($("stuff"));
In line with the other examples, the capture bag becomes { stuff }
, where
stuff
has a value of whatever is passed in.
Implicit full wildcard
If a bare $
primitive is used, then all keys and values present on the
provided object becomes entries on the capture bag - in other words, the value
provided becomes the capture bag itself.
const matcher = match($);
So in this case, if { person: { name: "Alice", age: 200 }}
is provided, then
the underlying capture bag has key "person"
with that value.
Destination transforms
The syntax for matching and resultant transformation can in principle have a 1:1 correspondence. So a matching pattern can be alternatively interpreted to supply mentioned capture terms with their priorly matched value.