Transforms
Among arr.ai's powerful features, transform operators are perhaps the most heavily used. They come in four flavors, each operating on a different kind of input:
->transforms a single value.=>transforms the members of a set.>>transforms the entries of a sequence or dictionary.:>transforms the attributes of a tuple.
The -> transform operator has the following syntactic structure:
lhs -> exprbinds the name.tolhsand evaluatesexprin this context.lhs -> \pattern exprbindspatterntolhsand evaluatesexprin this context.
The other three operators follow the same syntax. However, they bind each of the
elements of the lhs, not the entire lhs.
If this isn't clear, don't worry, it'll become clear as we work through the examples below.
The basics#
Let's explore the basic usage of the above operators, one at a time.
->#
The arrow operator, ->, can seem almost trivial:
@> 42 -> .@> 42 -> . + 1@> "hello, " -> . ++ "world"@> (x: 2, y: 3, z: 5) -> .x * .y + .zThe last example above illustrates the utility of the . name binding, since
.x is shorthand for (.).x.
The alternative form is likewise fairly trivial:
@> 42 -> \x x + 1@> (x: 2, y: 3, z: 5) -> \t t.x * t.y + t.zIn this case, instead of the name . being bound, the name x is bound to 42
when evaluating the expression x + 1. This can come in handy when there are
transform operators nested inside each other and an expression needs to refer to
multiple levels:
@> (x: 3, y: 5, z: 5) -> ((x: 2, y: 2) -> \c ((c.x - .x)^2 + (c.y - .y)^2)^0.5) * .zThe -> operator shows its usefulness when lhs becomes more complex:
@> /set data = (customer: (custid: 123, name: (first: "Franz", last: "Kafka")))@> data.customer.name -> $`${//str.upper(.last)}, ${.first}`As you can see, -> offers a very lightweight mechanism to refer to a common
complex expression while allowing a reader to follow a natural left-to-right
order as they seek to understand the intent.
On the flip side, when a complex expression spans multiple lines of code, the
let operator is usually preferred:
@> let v = (x: 3, y: 5, z: 5); > ((x: 2, y: 2) -> \c ((c.x - v.x)^2 + (c.y - v.y)^2)^0.5) * v.zWhile let . = ... is also allowed, it is not considered idiomatic arr.ai.
=>
The "set-arrow" operator, =>, requires lhs to be a set. It applies the
transform to each member of lhs and evaluates to a set of the results:
@> {2, 4, 6, 8, 10} => . + 1@> {2, 4, 6, 8, 10} => . % 6Note that, if the transform produces the same output for some of the lhs
elements, the resulting set will have fewer members than lhs. Remember that
sets cannot express the notion of duplicate membership, so if two or more input
values yield the same output value, the resulting set will contain that value
without reference to how many occurrences contributed to it.
>>#
The "sequence-arrow" operator, >> requires lhs to be a binary relation with
@ as one of its attributes. This includes arrays, dictionaries, strings and
byte arrays. It transforms the value associated with each @.
As a refresher, remember that arrays are just sets whose tuples have two
attributes: @ denoting a position within the array and @item denoting the
value at that position. Similarly, strings, are just sets whose tuples have
attributes @ and @char. And likewise for byte arrays (@ and @byte) and
dictionaries (@ and @value).
@> {(@:0, @item:1), (@:1, @item:2), (@:2, @item:3), (@:3, @item:4)}@> {(@:0, @char:115), (@:1, @char:116), (@:2, @char:114), (@:3, @char:105), (@:4, @char:110), (@:5, @char:103)}@> {(@:0, @byte:98,), (@:1, @byte:121), (@:2, @byte:116), (@:3, @byte:101), (@:4, @byte:115)}@> {(@:[-37.8, 145], @value:'MEL'), (@:[-33.9, 151.2], @value:'SYD'), (@:[51.5, -0.1], @value:'LCY')}The >> operator can be thought of as transforming just the value instead of
the key:
@> {(@:0, @item:1), (@:1, @item:2), (@:2, @item:3), (@:3, @item:4)} >> 2 ^ .@> [1, 2, 3, 4] >> 2 ^ .@> ["s", "toe", "thumb"] >> . ++ "nail"@> {"A": 10, "B": 42, "C": 100} >> 100 - .@> {[-37.8, 145]: 'mel', [-33.9, 151.2]: 'syd', [51.5, -0.1]: 'lcy'} >> //str.upper(.)>>>#
The >>> operator is a variant of the >> operator. It performs the same basic
operation, but also makes the @ attribute available to the transformation
expression. This form is special in that it must be a two-argument function.
@> ['red', 'green', 'blue'] >>> \i \c $'${c} = ${i}'@> /set cities = {[-37.8, 145]: 'MEL', [-33.9, 151.2]: 'SYD', [51.5, -0.1]: 'LCY'}@> cities >>> \[lat, lng] \code $'${code} is at (${lat}, ${lng})'Note that >>> still transforms only the associated value. @ remains
unchanged. If you want to transform the entire tuple, use => instead.
:>#
The "tuple-arrow" operator, :>, requires lhs to be a tuple. It applies the
transform to each attribute value of lhs and evaluates to a tuple of the results:
@> (r: 0.5, g: 0.2, b: 0.7) :> 1 - .(b: 0.3, g: 0.8, r: 0.5)Unary forms#
The above operator can be used in unary form. For instance, => \x 2 + x. In
all cases, the lhs is implied to be .. This property allows the transform
operators to be chained together to transform deeper structures:
@> [{1, 2}, {2, 3, 4}, {1, 5}] >> => 10 + .@> {(r:0.7, g:0, b:0), (r:0.4, g:0.6, b:0), (r:0.5, g:0.5, b:1)} => :> . ^ 2Interaction with order and orderby#
(The following discussion applies equally to order and orderby, so we'll
only discuss orderby.)
The orderby operator transforms a set into an array:
@> {'red', 'green', 'blue'} orderby .@> {(r:0.7, g:0, b:0), (r:0.4, g:0.6, b:0), (r:0.5, g:0.5, b:1)} orderby .r@> {(r:0.7, g:0, b:0), (r:0.4, g:0.6, b:0), (r:0.5, g:0.5, b:1)} orderby .gThings can get a bit confusing when applying orderby to a sequence or
dictionary. It is important to remember that orderby always operates on the
underlying set, never on the abstraction it represents. For instance, consider
the following expression:
[9, 4, 2, 4] orderby .At first glance, it seems that this will reorder the numbers of the array. However, when you try it out, you'll notice that the output is in fact an array of the tuples underlying the original array, not an array of the numbers it represents.
In order to rearrange the values accordingly, you must explicitly deal with the
underlying representation of arrays. While this can be confusing at first, it is
a result of consistently applying the rule that orderby operates on sets. It
also gives the programmer full control over how ordering should behave. In the
above case, a question arises: do you want two instances of the number 4 in the
result? There isn't a single correct answer. If you want an array of all scores,
you can do it like this:
@> [9, 4, 2, 4] orderby [.@item, .] >> .@itemLet's unpack this one step at a time. orderby [.@item, .] evaluates to an
array ordered first by .@item, which is the number, and then by ., which is
the whole tuple. The purpose of the . is to act as a tie-breaker. Without it,
the results will behave strangely in future. You could also use .@ as the
tie-breaker in this case, since .@ will be unique for all input elements.
However, this assumes regular arrays in which all values of .@ are unique, and
this is not guaranteed in arr.ai. It's generally safest to use . as a
tie-breaker, since it is guaranteed to be unique, regardless of the input set's
structure.
As observed earlier, the result is an array of the tuples underlying the
original array, so we use >> .@item to extract just the numbers from that
intermediate result.
On the other hand, if you only want an ordered array of the scores that were achieved and don't want to see duplicate scores repeated, it's simply a matter of getting a set of the scores and ordering them:
@> [9, 4, 2, 4] => .@item orderby .