UNPKG

sai-language

Version:

An object-oriented language designed to afford code comprehension and maintenance. Transpiles in-place to Javascript.

1,418 lines (855 loc) 43.6 kB
# This file is automatically generated. _Do not edit._ Unknown Heading ### | ALL - _query a collection for all values_ - pipers - ^all Checks a collection to see if it has _all_ of multiple values. If so, returns true. ... | all [expr] The expression to the left is the _haystack_, it is where we are looking. The expression on the right is the (set of) _needles_ that we are looking for. The __all__ piper examines the haystack to see if it has _all_ of the needles. If so, it returns __true__. Otherwise, it returns __undefined__. Example: set menu list tortilla, bean, lettuce, rice, meat, sauce debug menu | any list tofu, avocado // undefined debug menu | any list fish, meat // undefined debug menu | any list rice, bean // true You can also test against a single value: debug menu | any 'bean' // 'bean' debug menu | any 'gelato' // undefined #### Quirks You can use iterators on either side. Iterators on the needle side are collected, so don't try to needle with an infinite generator. When the haystack is an object, its keys are examined, not values. Using an object (or any scalar value) as the needle creates a test for _that object/value_. as if you had placed the object/value in an array by itself. No optimizations are made that require assumptions about the sortability of either haystack or needles, so this function is slower at the expense of being robust. If none of the needles are found in the haystack, every needle has been compared with every item in the haystack. It might be faster in some cases to sort and search, but this function does not do that. ### | AND - _confirms a collection_ - pipers - ^and Examines every item in a collection, returning the collection itself if all of them are _truthy_. ... | and For example: set t: 'horse', 9, true set f: 0, 'goat', 99 debug t | and debug f | and > [ 'horse', 9, true ] > 0 Rules for source data: - __undefined__: returns `undefined`. - __value__: returns the value. - __array__: returns _the array_ itself if _every_ element in the array is _truthy_, otherwise returns the first _falsy_ element. - __traits__: returns _the object_ itself if _every_ value in the object is _truthy_, otherwise returns the first _falsy_ value. - __iterable__: yields `true` if _every_ value returned by the iterator is _truthy_, otherwise yields the first _falsy_ value. This _vill_ lock up on infinite _truthy_ generators as it will try forever to find a _falsy_ value. If given an iterator, __and__ returns an iterator that will yield one value (or lock up). ### | ANY - _query a collection for any of one-or-more values_ - pipers - ^any Checks a collection to see if it has _any_ of multiple values. If so, returns the value it found first. ... | any [expr] The expression to the left is the _haystack_, it is where we are looking. The expression on the right is the (set of) _needles_ that we are looking for. The __any__ piper examines the haystack to see if it has _any_ of the needles. If so, it returns the first one it finds. Otherwise, it returns __undefined__. Example: set menu list tortilla, bean, lettuce, rice, meat, sauce debug menu | any list tofu, avocado // undefined debug menu | any list fish, meat // 'meat' debug menu | any list rice, bean // 'rice' You can also test against a single value: debug menu | any 'bean' // 'bean' debug menu | any 'gelato' // undefined #### Quirks You can use iterators on either side. Iterators on the haystack side are collected, so don't try to query with an infinite generator. When the haystack is an object, its keys are examined, not values. Using an object (or any scalar value) as the needle creates a test for _that object_ as if you had placed the object/value in an array by itself. No optimizations are made that require assumptions about the sortability of either haystack or needles, so this function is slower at the expense of being robust. If none of the needles are found in the haystack, every needle has been compared with every item in the haystack. It might be faster in some cases to sort and search, but this function does not do that. ### | AUDIT - _observe the elements of a collection_ - pipers - ^audit Audit is used to pass all values in a collection into an expression, code block or method. Audit doesn’t alter the collection, it just observes all values and keys/indices going by. ... | audit [expr] ... | audit (AS var1, var2...) [indent code block] ... | audit USING [function reference] For example: set b to chain fruit |observe debug('Pre-sort') |audit debug('Fruit #${key} is ${it}') |by desc |observe debug('Post-sort') |audit debug('Fruit #${key} is ${it}') debug b > Pre-sort > Fruit #0 is Apple > Fruit #1 is Banana > Fruit #2 is Citron > Post-sort > Fruit #0 is Citron > Fruit #1 is Banana > Fruit #2 is Apple > [ 'Citron', 'Banana', 'Apple’ ] This example shows how one could add instrumentation to a process in a light-weight fashion. Neither **observe** nor **audit** alter the chained data. _A side effect of this is that **observe** can’t be used with non-reproducible iterators as it’s impossible to statically observe an iterator without draining it._ ### | BY - _collection sorting_ - pipers - ^by To create a sorted array, use **by**. The newly resulting array will be sorted by the specified inline expression or code block. ... | by [expr] ... | by (AS var1, var2...) [indent code block] ... | by USING [function reference] ... | by (optional) asc (optional) desc **By** does not sort in-place; it always returns a new array. If **by** is used on an iterable, it will **collect** all values before sorting. If **by** is used on a true object, it will be converted to an array. #### by inline When you use the inline expression form, the provided expression is used to extract a value from a single record, which is then compared against other records to determine the sort order. The **it** pronoun and its **dot** scoping prefix are available within the expression. Print a list of friends sorted alphabetically by name: set result to friends | by .name debug ( result | thru .name ).join(', ') > Ann, Doug, Ellie, Harry, Jenna, John, Marshal, Sara You may sort by more than one value by adding another **by** clause. Multiple sort clauses are handled the way a database would: if a clause provides no guidance (the values are identical), subsequent clauses are each checked in turn. Print sorted by length of name, then age: set result to friends | by .name.length by .age debug (result | thru '${.name}: ${.age}').join('\n') > Ann: 23 > Doug: 18 > John: 19 > Sara: 23 > Ellie: 22 > Jenna: 28 > Harry: 31 > Marshal: 21 #### Sorting of objects by the property/key In _inline_ expression sorting only, if your source collection is an object, you can use the __key__ pronoun as sort criteria, in addition to the __it__ pronoun and related accessors. The key is the property name. In the example below, `rejections` is one of the keys in the folder object, while `34` is its value. set folder: rejections 34 acceptances 23 pending 4 debug folder | by key > [ 4, 23, 34 ] As you can see, the key is discarded in the final array output. If you need different behaviour, you'll need to partition the task into simpler components, e.g. by using __enlist__, __by__ and __thru__ in turn to preserve the data in the form you need. Using __key__ as sort criteria on something that isn't an object will throw a runtime exception. #### by block When using **by** with a block of code, the pronouns change. You are given both records under consideration just as you would using the Javascript `.sort` method. And perhaps unsurprisingly to those who have ever seen a programming textbook, the pronouns are the letters **a** and **b**. Print a sorted list of ages by province: set result to friends | by return a.province <=> b.province or a.age <=> b.age debug (result | thru '${.name}: ${.province}, ${.age}').join(' ') > Doug: ON, 18 > John: ON, 19 > Marshal: ON, 21 > Sara: ON, 23 > Jenna: ON, 28 > Ellie: QC, 22 > Ann: QC, 23 > Harry: QC, 31 Note the __key__ pronoun is not available with block expression sorting. #### by using __By using__ provides the ability to use a named function for your sorting facilitator. set ProvinceAge to task given a, b return a.province <=> b.province or a.age <=> b.age set result to friends | by using ProvinceAge // same result as previous example #### desc (descending) _modifier_ .. [expr] | by [expr] (desc) Used with the **by** comprehension just like **asc** but the sort order will be highest to lowest. debug list Bob, Carol, Ted, Alice | by it desc > [ 'Ted', 'Carol', 'Bob', 'Alice' ] ### | COLLECT - _collect values from an iterator into a static list_ - pipers - ^collect If the expression on the left is an iterator, converts it to an array/list by draining the iterator; otherwise do nothing. If the iterator never ends, your system will lock up until you run out of memory. (You could use a **limit** comprehension to keep that from happening.) ... | collect Example: set Odds to process local i to 1 while i yield i set i + 2 debug Odds() | collect // !!!! this locks up the computer debug Odds() | limit 10 | collect > [ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 ] The opposite of **collect** is **iterate**. The difference between **enlist** and **collect** is that collect will only transform an iterator, while **enlist** will transform all values. ### | CONCAT - _assemble two collections into a single list_ - pipers - ^concat A list operator, not a string operator, **concat** returns a list that consists of a copy of set 1 followed by a copy of set 2. Set 1 is not modified, a new array or iterable is returned. ... | concat [expr] Undefined values are ignored, and scalar values are promoted to single element arrays. As follows: [1, 2] concat [3, 4] -> [1, 2, 3, 4] [1, 2] concat [[3, 4], [5, 6]] -> [1, 2, [3, 4], [5, 6]] [1, 2] concat 3 -> [1, 2, 3] [1, 2] concat {c:3, d:4} -> [1, 2, {c:3, d:4}] [1, 2] concat undef -> [1, 2] 1 concat [3, 4] -> [1, 3, 4] 1 concat 3 -> [1, 3] 1 concat {c:3, d:4} -> [1, {c:3, d:4}] 1 concat undef -> [1] {a:1, b:2} concat [3, 4] -> [{a:1, b:2}, 3, 4] {a:1, b:2} concat 3 -> [{a:1, b:2}, 3] {a:1, b:2} concat {c:3, d:4} -> [{a:1, b:2}, {c:3, d:4}] {a:1, b:2} concat undef -> [{a:1, b:2}] undef concat undef -> undef undef concat 3 -> [3] undef concat {c:3, d:4} -> [{c:3, d:4}] undef concat [3, 4] -> [3, 4] If the first set is an iterable, **concat** will return an iterable, otherwise it returns a list (draining the second list if it is an iterable). ### | COUNT - _collection item count_ - pipers - ^count Returns the number of elements in the collection. ... | count If the collection is an iterator, it will be drained. debug undefined count debug 1 count debug fruit count > 0 > 1 > 3 ### | DELETE - _multi-attribute removal_ - pipers - ^delete Returns a copy of the collection with the specified attributes removed. Elements in an array are removed in order, so sequence does matter. ... | delete [expr] Examples set obj to: a 1, b 2, c 3, d 4, e 5, f 6 set obj delete 'a' debug obj > { b: 2, c: 3, d: 4, e: 5, f: 6 } set obj delete: 'c', 'f' debug obj > { b: 2, d: 4, e: 5 } set obj delete: b 'B is for Bonobo.', d 'D is for Dixie cups.' debug obj > { e: 5 } When **delete** operates on an array, it returns a copy of the array with the elements specified removed. debug (list 1,2,3,4,5) delete 1 > 1, 3, 4, 5 debug (list 1,2,3,4,5) delete (list 1,2) > 1, 3, 5 Elements are removed in first->last order, so sequence does matter. debug (list 1,2,3,4,5) delete (list 2,1) > 1, 4, 5 __delete__ is also a first-class keyword used for undefining variables entirely. Please review the related entry in the keywords documentation. ### | ENKEY - _convert a list/iterator into a set of traits_ - pipers - ^enkey __Enkey__ transforms a list into an object (set of traits) by turning each value in the list into a trait with the value of `true`. ... | enkey ... | enkey [expr] This is similar to __entrait__ except that instead of assuming the source list has embedded key/value pairs, the source list is used for the keys only, and the value is either `true` or some other expression that is calculated. Examples: set a list a, b, c set a | enkey debug a > { 'a': true, 'b': true, 'c': true } set a list a, b, c set a | enkey '${it.toUpperCase()}${key}' > { 'a': 'A0', 'b': 'B1', 'c': 'C2' } Dry, but very useful. ### | ENLIST - _convert a collection to an array_ - pipers - ^enlist Converts various collection types into a list/array such that it should be possible to reverse the transformation later without losing data. ... | enlist Here are the basic rules: undefined -> undefined value -> [ value ] list -> self object -> [ [ key, value ], [ key, value ], ... ] iterable -> [ value, value, value, ... ] Note specifically that **enlist** will turn a set of key, value pairs into an array of [key,value] arrays thus no data is lost in the conversion. set data to: cats 7 dogs 12 debug data debug data | enlist > { cats: 7, dogs: 12 } > [ [ 'cats', 7 ], [ 'dogs', 12 ] ] Such a result can be turned back into traits with **entrait**. The difference between **enlist** and **collect** is that **enlist** will always transform its source data into an array/list, while **collect** will only transform an iterator. ### | ENTRAIT - _convert collections to object/traits_ - pipers - ^entrait Converts various collection types into traits/fields using rules that allow the transformation to be reversed later without losing data. ... | entrait Here are the rules: undefined -> undefined value -> { value: true } list -> { [0][0]: [0][1], [1][0]: [1][1], ... } object -> self iterable -> { first[0]: first[1], next[0]: next[1]... } **Entrait** is designed primarily to losslessly restore the results of enlist and iterators that produce arrays of key/value pairs. debug 'Coyote' | entrait > { Coyote: true } set data to: list cats, 7 list dogs, 12 debug data debug data | entrait > [ [ 'cats', 7 ], [ 'dogs', 12 ] ] > { cats: 7, dogs: 12 } **Entrait** ignores input data that does not conform to expectations; if given a list that does not contain two-element key/value lists, it will not create traits. You could, however, use **enkey** to transform this data: debug fruit | enkey > { Apple: true, Banana: true, Citron: true, Durian: true } ### | EXPECTS - _rule enforcer for traits_ - pipers - ^expects Expects compares an object againt a set of rules and returns a list of rules that are **violated**. Thus, a successful test of expectations is an empty array. An array with one or more elements is returned that contains a list of the rules that were broken. ... | expects [expr] In first-class usage as a keyword, __expects__ is used to check whether a function’s named parameters (or any arbitrary object) has certain traits, and optionally if those traits are of a particular type. For more on this, review the keywords documentation. For use as a piper, __expects__ compares an object againt a set of rules and returns a list of rules that are **violated**. Thus, a successful test of expectations is an empty array. An array with one or more elements contains a list of the rules that were broken. #### expects rules Rules for **expects** take the form of a set of traits. Their names correspond to names of traits that must be found in the expression under test. The value, if not _true_, is interpreted as an object type; the trait must be of that type (or a child of that type). Any violations to the rules are returned in a list; that list being made up of plain objects with the following fields: .trait - name expected .expects - type expected .found - type found Here are some examples, first as a keyword AddStudent task expects $name string, $age number @students.push copy $ .. @AddStudent name 'Sally', age '12' > Error: SAI: parameter exception in AddStudent > age should be number, but it's string Performing this same test in an ad-hoc fashion using the **expects** operator would look like this: set newStudent to: name 'Sally', age '12' set studentRules to: name 'string', age 'number' debug newStudent expects studentRules > [ { trait: 'age', expects: 'number', found: 'string' } ] N.B. the **expects** clause rule format is identical to that of a single-line **traits** definition, with the additional requirement of a **$** before every trait name. ### | FIRST - _first value_ - pipers - ^first Returns the first value in a list or iterator. With an expression, block or function, returns the first value where the expression is true. ... | first ... | first [expr] ... | first (AS var1, var2...) [indent code block] ... | first USING [function reference] Example: debug fruit | first > Apple __First__ doesn't just crop the array, it unwraps whatever value is there, as if you'd coded `array.[0]`. __First__ unwraps iterators, returning a single value (or undefined). If you need the iteration chain preserved, use __limit__ (in combination with __has__ if necessary). #### First with a comparison If you provide a comparison expression, block, or function, __first__ will evaluate the comparison against each element in the collection, and return the first value that the comparison returns true. If you use __first__ on an object, the order of comparisons is undefined. Example: set fruit list Apple, Banana, Cherry, Durian debug fruit | first .length is 6 // Banana ### | HAS - _collection filtering_ - pipers - ^has Use __has__ to test for exclusion each object in a collection. ... | has [expr] ... | has (AS var1, var2...) [indent code block] ... | has USING [function reference] The pipe operator **has** indicates an expression based filter using the **it** _pronoun_ to represent the item currently under examination. Since **it** is in play, the associated **dot** scoping prefix is also active within the expression for easy access to item fields. #### has inline debug friends | has (.province = 'QC') and (.cat or .dog) > [ { name: 'Ann', age: 23, cat: true, province: 'QC' } ] Any valid expression can be used in a **has** piper, and one can refer to values outside the expression as well. set rentAge to: ON 25, QC 21 debug friends | has .age >= rentAge[.province] > [ { name: 'Ellie', age: 22, province: 'QC' }, > { name: 'Ann', age: 23, cat: true, province: 'QC' }, > { name: 'Harry', age: 31, province: 'QC' }, > { name: 'Jenna', age: 28, dog: true, province: 'ON' } ] #### has block **Has** can reference a block of code directly, which makes the **it** pronoun available within that block, or be given the name of a task (or a task definition). You must **return** a _truthy_ (keep) or _falsy_ (discard) value from the block so the filter can take the according action. If you don’t return a value, all rows will be discarded. friends | has return .age >= rentAge[.province] #### has using You can integrate function calls by adding the **using** keyword immediately following the __has__ keyword itself. set CanRent to task given row return row.age >= rentAge[row.province] debug friends | has using CanRent ### | HIGHEST - _search collection for highest value_ - pipers - ^highest Returns the collection element with the highest value of the expression. ... | highest [expr] ... | highest USING [function reference] ... | highest ... | highest (AS var1, var2...) [indent code block] Prepares the pronouns **it** and **key** for use by the expression. debug friends highest .age > { name: 'Harry', age: 31, province: 'QC' } __Highest__ is like a combination of __by__ and __first__, except that instead of sorting, it just runs through the collection and finds the correct answer. (This is faster.) ### | INTO - _collection integration_ - pipers - ^into Javascript features the array method `Array.prototype.reduce` which performs the reduction function, and SAI extends its applicability with the **into** compherension keyword. ... | into [value] [expr] ... | into [value] (AS var1, var2...) [indent code block] ... | into [value] USING [function reference] In addition to the item (**it**) and the item key (**key**) available in other comprehensions, an sum variable (**sum**) is used when evaluating **into** expressions. The value of the expression becomes the value of **sum** the next time the expression is evaluated. #### into inline The **sum** variable is initialized by the value following **into** — in the example below `0` — and then each row in the collection is visited and we add `.age` to it. This adds up the ages of every friend. debug friends | into 0 sum + .age > 185 #### into block The **into** comprehension, like other comprehensions, has an inline version with an expression, and a long-form which takes a block of code. A more complex example. See if you can suss out how it works. debug friends | into blank set sum[.province] to (self default 0) + 1 > { ON: 5, QC: 3 } Here’s a hint: **self** is a _pronoun_ used only in **set** statements. It is initialized to the previous value of the variable being changed by **set**. Another hint: **default** is an operator that evaluates to its right hand value if and only if the left hand value is _undefined_. Last hint: **blank** initializes an object with no traits; it is the SAI equivalent of Javascript’s `{}`. The comparable word for arrays/lists with no elements is **empty**; in Javascript you’d write `[]`. #### into using The addition of **using** lets you call an external function. The function must always `return` the value you wish to be used as the sum so the value can be preserved across function calls. (The block version of **into** takes care of this for you.) set ageTotal to task given accumulator, row return accumulator + row.age debug friends | into 0 using ageTotal > 185 ### | ITERATE - _transforms a static collection into an iterated form_ - pipers - ^iterate Ensures the collection is an iterable. If it already is an iterable, return it unchanged. If it has a **Iterate** method, call it and return that iterable. If it's an Array, produce an iterator over its values. If it's an object, produce an iterator that returns for each trait a 2-element array with trait name, trait value. If it's just a plain value, produce an iterator that returns that value. ... | iterate Using **iterate** in this way allows you to guarantee that processing is being done using an iteration rather than a static collection. To go the other way, use **collect**, which turns an iterable into an Array. The following example follows the one in the keyword **Iterator**: ply inventory | by it.1 debug it > [ apple: 2, banana: 1 ] // undesired result ply inventory | iterate | by it.1 debug it > [ 'banana', 1 ] // correct result > [ 'apple', 2 ] The example wants to print out a sorted list of inventory items. The undesired result comes about because **by** needs access to the entire collection in order to sort it, so must process imperatively. Even if you give **by** an object with a **Iterate** method, which all objects and arrays have whether you put it there yourself or not, that method will not naturally be called. So, **by** converts the `inventory` object into an array so it can be sorted. The array conversion produces an array of all enumerable traits; in the case of `Tally` objects, the only enumerable trait is `bag`, and so an array that looks like `[ bag ]` is sorted (to no effect) and then printed one element at a time. When you force a call to **Iterator** with the **iterate** comprehension, you get the correct result you're expecting, as you thereby force **by** to use your custom iterator rather than a naive array conversion. The upshot is that right now SAI favours imperative collections rather than lazy iterators except when specifically using the **iterate** statement or comprehension. ### | KEYS - _return a list of trait names_ - pipers - ^keys Returns the keys (or indices) of a collection’s elements. ... | keys __Keys__ returns a list comprising the keys/indices of the collection it is given. If you give it an object/traits, you get the trait names. debug friends | first | keys > [ 'name', 'age', 'cat', 'province'] If you give it an array, it returns an array of indexes. debug fruit | keys > [ 0, 1, 2 ] Given an iterator, it returns an iterator that counts from 0 to the number of results in the source. Of course, you lose the values, but you didn't want them, did you. ### | LAST - _last value_ - pipers - ^last Returns the last value in a list or iterator. With an expression, block or function, returns the first value where the expression is true. ... | last ... | last [expr] ... | last (AS var1, var2...) [indent code block] ... | last USING [function reference] If the collection is an iterator, it will be exhausted. If it's an infinite iterator, the machine will stop responding. debug fruit | last > Citron __Last__ doesn't just crop the array, it unwraps whatever value is there, as if you'd coded `array.[array.length-1]`. __Last__ unwraps iterators, returning a single value (or undefined). If you need the iteration chain preserved, use __limit__ (in combination with __has__ if necessary). #### Last with a comparison If you provide a comparison expression, block, or function, __last__ will evaluate the comparison against each element in the collection, and return the falst value that the comparison returns true. If you use __last__ on an object, the order of comparisons is undefined. Example: set fruit list Apple, Banana, Cherry, Durian debug fruit | last .length is 6 // Durian #### Quirks Arrays: __last__ starts looking at the tail of the array. Objects: since the keys of an object are presented with no defined order, __last__ and __first__ examine elements in an object the same way. Iterators: __last__ starts at the beginning, compares every item, and returns the last one found when the iterator ends. If the iterator doesn't end, __last__ will hang. If no match is found, __last__ does not yield a value, it simply terminates. If your comparison is expensive, you may wish to __collect__ the iterator (turning it into an array) before applying __last__; this exchanges memory use for processing speed. ### | LIMIT - _select certain array/iterator elements_ - pipers - ^limit Returns a subset of elements from an array, string, or iterator. ... | limit [expr1 (, expr2, ...)] If one parameter is provided, returns the first **qty** elements in the collection if **qty** is positive. If negative, returns the last (absolute) **qty** number of elements. If an **index** is supplied, return **qty** elements starting at **index**. Negative index values do interesting things, there's a chart below. .. [collection] | limit [qty] .. [collection] | limit [index], [qty] .. [string] | limit [character count] .. [string] | limit [start character], [character count] You always get a list back, even if just one element will be returned. limit -y last y rows limit 0 empty limit undefined everything limit +y first y rows limit -x, -y everything except last y rows starting x from end of list limit -x, 0 empty limit -x, undef last x rows limit -x, +y y rows starting x from end of list limit 0, -y everything except last y rows limit 0, 0 empty limit 0, undef everything limit 0, +y first y rows limit +x, -y everything except last y rows starting at x limit +x, 0 empty limit +x, undef everything starting at x limit +x, +y y rows starting at x If the collection is an iterable, it will only be fetched as many times as needed. (If you ask for elements offset from the end, the iterator will be exhausted because an iterator’s length can only be ascertained by exhausting it.) If the iterator is infinite and you ask for an offset from the end, you'll run out of memory as it will cache an infinite number of intermediate results order to comply with your impossible request. (Infinite iterators have no end.) #### limit with strings You can use __limit__ to extract substrings. For this purpose characters are treated as elements in the array. debug 'abcdef' | limit 2,2 // cd debug 'abcdef' | limit -3 // def debug 'abcdef' | limit -3,2 // de debug 'abcdef' | limit 1,-1 // bcde And so on. One "gotcha" is operator precedence; pipers have the lowest priority, so you will need to wrap them in parenthesis if using __limit__ in any logical expression. Do this: if '#!' is (line | limit 0,2) // correct code Not this: if '#!' is line | limit 0,2 // bad code if ('#!' is line) | limit 0,2 // also bad Another "gotcha" is the way __limit__ intentionally changes behaviour when its arguments change from negative to positive. If you pass calculated values that could become negative, __limit__ may return things you don't expect. ### | LOWEST - _search collection for lowest value_ - pipers - ^lowest Returns the collection element with the lowest value of the expression. ... | lowest [expr] ... | lowest USING [function reference] ... | lowest ... | lowest (AS var1, var2...) [indent code block] Prepares the pronouns **it** and **key** for use by the expression. debug friends lowest .age > { name: 'Doug', age: 18, province: 'ON' } __Lowest__ is like a combination of __by__ and __first__, except that instead of sorting, it just runs through the collection and finds the correct answer. (This is faster.) ### | OBSERVE - _inspect a value without changing it_ - pipers - ^observe Evaluates the right expression using the left expression value as the **it** pronoun; however, **observe** always returns the original left hand expression, no matter the result of the right expression. ... | observe [expr] ... | observe (AS var1, var2...) [indent code block] ... | observe USING [function reference] For example: set b to 'Fred' set c to b observe debug '${it.length} letters.' debug c > 4 letters. > Fred See the example for __audit__ for a more interesting reason why __observe__ is nice. A warning: while you can **observe** an iterable expression, all you will see is a function. Take care not to invoke it, lest you drain it inadvertently. Heisenberg's uncertainty principle is very much at play with iterables: you can either have it or know what's in it. N.B. **observe** very useful in **chain** expressions. ### | OR - _explores a collection_ - pipers - ^or Examines every item in a collection, returning the first one found _truthy_. ... | or For example: set t: undefined, 9, true set f: 0, null, false debug t | or debug f | or > 9 > undefined Rules for source data: - __undefined__: returns `undefined`. - __value__, returns the value. - __array__, returns the first _truthy_ element found in the array, otherwise `undefined`. The array is searched starting at index 0. - __traits__: returns that value if _any_ value in the collection is _truthy_, otherwise `undefined`. The collection is searched in no defined order. - __iterable__: yields the _first_ truthy value returned by the iterator. Yields `undefined` if the iterator ends before one is found. Will lock up if an infinite iterator never yields a truthy value. If given an iterator, __or__ returns an iterator that will yield one value (or lock up). ### | REDUCE - _collection reduction_ - pipers - ^reduce __Reduce__ transforms a collection of items into a single item by giving a function two items and asking it to produce a single item in response. This is repeated over the entire collection. ... | reduce [expr] ... | reduce USING [function reference] ... | reduce (AS var1, var2...) [indent code block] Let's say you have an array with four numbers: set b to: 4, 7, 12, 34 set result to b | reduce as a, b debug "a:${a} b:${b}" return a+b debug "result: ${result}" The __reduce__ operator is given a block of code that accepts two items, a and b. It prints them both out, then returns their sum. This prints: > a:4 b:7 > a:11 b:12 > a:23 b:34 > result: 57 Since there were four items in the list, the code block was called three times. The first time with the first two elements in the array. Subsequently, it was called with the previous result and the next element in the array. If the collection given to __reduce__ only has one element, that element is returned without calling your code; since there was only one item, there is no way to combine it with another. If you give __reduce__ an empty collection, the result is __undefined__. __Reduce__ is a classic operator for transforming sets of data, and there are far better explanations of how it works and what it is good for to be found elsewhere. The rest of this documentation will just show examples. *Note that if you are looking for a reduce-like operator where you provide a starting value for the accumulator and are given each item in the collection in turn, then look at __into__ which does exactly what you want. __Reduce__ is kind of a different animal.* #### reduce inline The inline form of reduce gets two pronouns, __sum__ and __it__. set result to b | reduce sum+it #### reduce block A __reduce__ code block has the two available pronounts __sum__ and __it__, which you can rename with an __as__ clause, as shown in the above example. #### reduce using The __using__ clause is available with __reduce__. You're passed two parameters, the sum and the next element to consider. set Adder to task given a, b return a+b set result to b | reduce using Adder ### | REPLICATE - _create a list by copying an element_ - pipers - ^replicate Replicate sucks, it should be removed. ... | replicate [expr] What it does: set b 'a' | replicate 3 debug b > [ 'a', 'a', 'a' ] I dunno, this doesn't feel meaty. ### | SELECT - _multi-attribute query_ - pipers - ^select Returns a new collection that is the subset of the original collection identified by the provided list of keys/indices. ... | select [expr] Example: debug fruits | select: 2, 0; > [ 'Citron', 'Apple' ] debug friends | limit 3 | thru <- it | select list name, age > [ { name: 'Sara', age: 32 }, { name: 'John', age: 19 }, { name: 'Ellie', age: 23 } ] ### | SET - _replacement operator_ - pipers - ^set A chainable comprehension operator that allows direct reference and replacement of the incoming dataset within an expression or code block, using the **it** pronoun. ... | set [expr] ... | set (AS var1, var2...) [indent code block] ... | set USING [function reference] **Set** can be used with an expression: debug 4 | set 5*it+2 | set it/7 > 3.142857142857143 **Set** can use an indented code block: debug friends by .age | set set .length to 3 > [ { name: 'Doug', age: 18, province: 'ON' }, > { name: 'John', age: 19, cat: true, dog: true, province: 'ON' }, > { name: 'Marshal', age: 21, dog: true, province: 'ON' } ] _If you don’t specifically **return** a value or object from within an **set** code block, the original value will be used (as in the example above). In other words, there is an implicit `return it` at the end of every **set** block._ **Set** supports the **using** clause, in which case the function specified receives the original value as its first parameter, and the return value is passed forward. The two debug statements below are equivalent set ExtractFirst to task return $[0] debug friends #cat | set Extract(it) debug friends #cat | set using Extract > { name: 'Sara', age: 23, cat: true, province: 'ON' } > { name: 'Sara', age: 23, cat: true, province: 'ON' } You must specifically return a value in the function called by **set using**. ### | THRU - _transform a collection with a map-like operation_ - pipers - ^thru Pass each element of a collection “thru’ an expression, code block, or previously defined function. The result of the expression becomes the new value in a copy of the collection. ... | thru [expr] ... | thru (AS var1, var2...) [indent code block] ... | thru USING [function reference] __Thru__ returns the same type of collection it is given. An array yields an array, an object an object, and an iterator another iterator. (There are other pipers for transforming between types of collection.) Conversion to uppercase: debug fruit | thru .toUpperCase() > [ 'APPLE', 'BANANA', 'CITRON' ] A more complex formatting that could be an expression but I needed a block example: debug friends | has .cat | thru return '${key+1}) ${.name}, age ${.age}, lives in ${.province}' > [ '1) Sara, age 23, lives in ON', '2) Jon, age 19, lives in QC', '3) Ann, age 23, lives in QC' ] Passing values **thru** a function: set rot13 to task set out to '' count $length set char to $charCodeAt(key) switch char >> 5 case 2,3 with char andb 31 if it > 26 nop elsif it > 13 set char - 13 elsif it > 0 set char + 13 set out + ~String.fromCharCode(char) return out debug fruit | thru using rot13 > [ 'Nccyr', 'Onanan', 'Pvgeba' ] The function called by the **using** variant receives three parameters for every iteration: - item value - item key - reference to the entire collection ### | TOTAL - _reduction with addition/concatenation_ - pipers - ^total __Total__ is very like __into__ however the emphasis is on addition. ... | total [expr] ... | total USING [function reference] ... | total __Total__ is a reduction operator that is specialized for adding numbers, or concatenating strings. debug list a,b,c; | total > abc Here, total has iterated through the three elements of the `list a, b, c` and added them together. Because they are strings, the result is a concatenated version of the array of strings. debug array 5, 10, 15, 20 | total > 50 Similarly, total has added up the numbers. You can pass an expression (or function) to __total__ just like with __into__, in which case the addition is whatever value the expression returns on each iteration. debug fruit | total .length > 16 The length of the three strings in the fruit array. #### total using The addition of **using** lets you call an external function. The function must always `return` the value you wish to be added. You get two parameters, the value, and the key. set ageTotal to task given row, id return row.age debug friends | total using ageTotal > 185 ### | UPDATE - _multi-attribute update_ - pipers - ^update Creates a new collection that is a composite of a copy of the left collection with all of the values in the right collection overlaid. If discrete keys are available in the left or right collection, they will be used intelligently. Undefined values on the right will not be assigned. ... | update [expr] Examples Sample data set list1 to list Apple, Banana, Citron set list2 to list Kiwi, undefined, Mango set traits1 to: a 1, b 2, c 3 set traits2 to: '1' 4, d 5 debug list1 | update list2 > [ 'Kiwi', 'Banana', 'Mango' ] debug list1 | update traits1 > [ 'Apple', 4, 'Citron', d: 5 ] debug traits1 | update traits2 > { '1': 4, a: 1, b: 2, c: 3, d: 5 } debug traits1 | update list2 { '0': 'Kiwi', '2': 'Mango', a: 1, b: 2, c: 3 } __Update__ also works with iterators and treats them like arrays. If the left side collection is **undefined**, it will be initialized **blank**. __Update__ is handy when used in __set__ statements. set [variable] | update [collection] Update a collection variable with a set of keys/values. When used like this in a __set__ statement, the original data is updated in-place (maybe). Works on lists and traits, and accepts lists, traits and iterators for update data. Updating a list: debug fruit > [ 'Apple', 'Banana', 'Citron' ] set fruit update: '1' 'Pear', '3' 'Guava' debug fruit > [ 'Apple', 'Pear', 'Citron', 'Guava' ] set fruit update list Grape, undefined, Melon debug fruit > [ 'Grape', 'Pear', 'Melon', 'Guava' ] Updating traits: set friend to friends.1 debug friend > { name: 'John', age: 19, cat: true, dog: true, province: 'ON' } set friend update: name 'Jon', province 'QC' debug friend > { name: 'Jon', age: 19, cat: true, dog: true, province: 'QC' } set friends update: undefined, friend debug friends [ { name: 'Sara', age: 23, cat: true, province: 'ON' }, { name: 'Jon', age: 19, cat: true, dog: true, province: 'QC' }, { name: 'Ellie', age: 22, province: 'QC' }, { name: 'Marshal', age: 21, dog: true, province: 'ON' }, { name: 'Doug', age: 18, province: 'ON' }, { name: 'Ann', age: 23, cat: true, province: 'QC' }, { name: 'Harry', age: 31, province: 'QC' }, { name: 'Jenna', age: 28, dog: true, province: 'ON' } ] If the left-side collection is undefined, **update** initializes it to **blank** before merging. ### | VALUES - _return a list of collection values_ - pipers - ^values Returns the values of a collection's elements. ... | values __Values__ returns a list of all of the values in a collection. If you give it an object/traits, you get the the values. debug friends | first | values > [ 'Sara', 23, true, 'ON' ] Given an array, you get a copy of the array. debug fruit | values > [ 'Apple', 'Banana', 'Citron' ] With an iterator, it returns that iterator; that is, it does nothing.