Skip to main content

Aggregate Data

Quarrel provides three tools for working with compound or aggregate data:

  • Tables
  • Sequences
  • Records

These three tools provide the three fundamental aspects of programming with data: storage (tables), procedure (sequences), and structure (records). Everything in Quarrel is made within this trichotomy of syntax and semantics.

Tables#

A series of items of any kind that is available by lookup, can be numerical or textual keys. Tables allow you to work with data as you would a list or dictionary in other languages, navigating through the values contained within either by name or by position.

l .= [1, 2, 3]
l[1] .= 3 #= [1, 3, 3]
# you can even use a shorthand \ to access list elements
l\0 #= 1

Method Operators#

Many common ways to interact with lists are implemented by syntax within the postcircumfix square brackets. You can get the index of a value by using ? as your index to get a check routine It returns either the index number or an empty sequence.

fruit .= ["apples", "oranges", "pears"]
|-| <- fruit[1] #= oranges
|-| <- fruit[?]("oranges") #= 1
|-| <- fruit[?] "bananas" #= ()

Add a new list items to the end by using the [+] postcirmfux method operator.

nums .= [10, 20, 30, 40]
nums[+] .= 50 #= [10, 20, 30, 40, 50]
timesTen .= => \1 * 10
nums[+] .= timesTen 6 #= [10, 20, 30, 40, 50, 60]
seventy .= 7
nums[+] .= seventy #= [10, 20, 30, 40, 50, 60, 7]
seventy .= seventy * 10
|-| <- nums #= [10, 20, 30, 40, 50, 60, 70]

Remove list items with !, takes one argument: the index to remove.

nums[!](0)
|-| <- nums #= [20, 30, 40, 50, 60, 70]
nums[!] 5
|-| <- nums #= [20, 30, 40, 50, 60]
nums[!] nums[@]
|-| <- nums #= [20, 30, 40, 50]

First and last elements:

first .= l[<:]
last .= l[:>]

To take the first or list while removing from the original list, use the <! or !> operators.

first .= l[<!] # shift (take first element)
last .= l[!>] # pop (take last element)

Specify how many to shift or pop by placing a number next to the colon

first_two .= l[<:2]
last_two .= l[2:>]

To get a length of a list, use a @ inside brackets:

l .= [10,20,30]
len .= l[@] #= 3

To delete elements of a list, use [!] and provide the position to delete as a routine argument:

ls .= [1,2,"s",3]
ls[!](2) #= [1,2,3]

To set a value to empty, assign it an empty sequence:

l[1] .= ()

Ranges#

Ranges are basic list builders that can be utilized in a pinch to craft a series of changing data. You can use a range to specify a slice of a list that you want to take. You can also use ranges as a starting point for a series of transformations.

l[0::1] #= [1, 2]
# the inclusive-inclusive range operator
l[2::] # open-ended ranges are allowed
# the exclusive-inclusive range operator
l[0!:1] #= [2]
# the inclusive-exclusive range oeprator
l[0:!1] #= [1]
0::9 #= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ranges also work with shorthand lookup \ operator

l\0::1 # but they can't be open-ended

Sequences#

Sequences are an interesting concept in Quarrel. They work as arithmetic order operators, structural sequence delimiters, procedural blocks (including in named routines).

When you use a sequence in the context of a binary operation, some operations will invoke the sequence to obtain the last expression's result. Others may apply directly to the sequence expression in some fashion. If you ever want to invoke forcefully, use the ! postfix operator.

num = (1+2)+3 #= num == 6
s .= (1, 2, 3) #= s! == 3

Here the + operator forces the invocation of the sequence to do the addition. This is the most common semantic when math operations are invoked.

Sequences can be provided arguments (values that are provided at invocation and available to the internal scope of the sequence) with the => routine operator. Therefore, a sequence with a parameter pattern supplied is called a routine.

Auto-generated param patterns are possible using a hash lookup in the current scope

inc .= (\1 + 1)

Method Operators#

Here we use the [+] post-circumfix sequence method operator to add additional dispatchable routines to the map container.

map .= Routine? f, [] => []
map[+] .= Routine? f, [x, xs...] => [f(x), ...map(f, xs)]
map[+] .= Routine? f => [x, xs...] => [f(x), ...map(f, xs)]
map (n => n+1), [1, 2, 3]
# returns (2, 3, 4)

To get all of the patterns of each routine that maps to the container name:

map[_:] #= [{f, []}, {f, [x, xs...]}, {f}]

To see if a routine included in this container has a matching pattern

map[?] {f, []} #= true

Change a routine in a container instead of adding a new one:

map[-] {f, []} => [1]

To get the count of the number of routines in the container

map[@] #= 3

Patterns#

Quarrel is not a typed language but it does provide a place to guard against malformed data inside of a contract or named pattern. Patterns are a kind of data that describes other data. They are used throughout Quarrel and have many unique syntactic elements that we will discuss at length further down (the rabbit hole...). Patterns can be thought of as a separate language with some similar syntax/semantics. That being said, they have been designed to feel intuitive and to fit seamlessly into the full Quarrel language.

rp .= { x, xs... } #

Here we have a pattern describing several arguments (at least 2) with 1 being captured as x and the rest captured in a list named xs.

point .= { x & y }

This is describing what's called a Record, Named Tuple or a struct in other languages. This type of structured data doesn't have it's own literal syntax and is only accessible with the interface provided by the programmer. You can either add multi-dispatch routines to common operators, build your own routine-based interface, or even include routines directly inside of the structure that can be accessed by entering their scope using the transform operator (and it's shorthand version: \).

complicated .= { pair[2], tripleOrMore[3+], mapWithTheseKeys<options, callback{}> }

With patterns, you can describe any kind of data. Here we have 4 arguments. The first one is a list of two elements. The second argument is a list of at least 3 elements. The third, a map containing at least two keys, an options key and a callback key that contains a pattern or routine.

mindBlown .= { ResultContract? callback{Number? argA, {Number | Symbol}? /\ argB[]} }

This pattern matches one argument that is a routine which has a parameter pattern of two arguments, one number followed by a list of values that can be Numbers or Symbols (Using the \/ operator would have meant that only one value needs to match this pattern). The result of applying a matching set of arguments to this pattern (aka: invoking the routine), is then tested against the ResultContract pattern which is defined elsewhere.

Contracts#

To name a pattern is to make it a contract that can be used in special guard statements within patterns (like above where we have the Number? contract being applied to argA or the impromptu contract being applied to a binary /\ operation to the argB list). There are two kinds of contracts: transformative and investigative. A transformative contract utilizes the ^ symbol as a suffix and attempts to "transform" the provided data into the data expected. An investigative contract utilizes the ? symbol. These contracts check the provided routine result and expect a boolean result. The routine can be just a parameter pattern and if the data would match then it is a success.

To define a contract, use the ^= or ?= assignment operators.

greaterThanTen ?= {n} => (n >> 10)
tenPerGroupAtLeastOne ?= {greaterThanTen? n} => (n / 10)

There are several primitive contracts that will give you the baseline set of checks needs to do simple scripting. But you can also use these primitive contracts in your own user-defined contracts to build out more complex data patterns.

Number? 10
Integer? 100
Decimal? 10.5
Text? "Wow"
Sequence? 1, 2
List? [3, 4]

An investigative contracts returns true or false depending on whether they meet the requirements specified by the routine (even just a pattern will work). Transformative contracts--on the other hand--have a parameter pattern to dispatch on the type of data provided and then the sequence provided "transforms" the provided argument(s) into something else.

numSymbol .= "3"
ten .= Integer^ numSymbol + 7 #= 10

Enumerated Data#

Patterns can indicate that a certain datum could be several different things. This allows you to do two things: model behavior that can intake several different kinds of data (sometimes called a union) and provides a facility for enumerations, the technique that is like a code-based combo box: a list of possible flags. You can use these with switch statements or mulit-dispatch routines to create functional interfaces.

To create one of these "enumerated" patterns, you need the | operator and a special postcircumfix {} operator that creates a named contract that is automatically assigned to a container. Since these "eponymous" contracts are always going to be a literal value, there's no need for the usual : or . to indicate assignment type.

Color ?= { Red{} | Green{} | Blue{} }
# To access subcontracts, use either `\` or `->` operators.
say .= Color? col => (
Color\Red? col @@ "You are red!"
Color -> (
Green? col @@ "You are green!"
Blue? col @@ "You are blue!"
)
)

Maps#

Also know as dictionaries, tables or hashes, the literal syntax uses the same operators as lists [ and ] brackets (sometimes called "square" brackets). The only difference between a list and a map is that maps contain only named ("containered") data elements where the name is delineated with either a . or :. Quarrel has unified lists and maps such that you can essentially think of a list as a form of map where the key is a positional index.

en .= [
one. [ name. "one", value. 1 ],
two. [ name. "two", value. 2 ],
three. [ name. "three", value. 3 ],
]

The . and the : mean the same as assignment (reference, copy). To get a value from within the map, use a post-circumfix [key] with the exact key. The \ shorthand allows you to avoid the need for quotation marks.

sp .= [numbers. en]
en["one"]["name"] .= "uno"
sp\ref\one\name == "uno" # true
fr .= [numbers: en]
fr\numbers\one\name .= "une"
en\one\name <> "une" # true

To use an expression to generate the key, use the square brackets postcircumfix version.

r["o"+"n"+"e"]\value

To get the number of keys (count of entries in map):

r[@]

To return a list of all keys:

r[_:]

To return a list of all values:

r[:_]

To delete a key:

r[!] "two"

When building a map from prior data, you can use a shorthand prefix . or : to grab the value in that container and set a key with the same name to point to that value (either a copy or referenced)

first .= "John"; last .= "Doe"
name .= [.first #[reference]#, :last #[copy]#]