Latitude provides several collection data types, as well as a centralized interface for dealing with them.
First, cons cells deserve a brief mention. Latitude has a Cons
object which behaves like a cons cell in Lisp-like languages. Cons
cells have a car
and a cdr
slot. Assignments to these two slots is
done with =
rather than :=
.1 Cons cells can be
explicitly made with Cons clone
but are usually constructed with
cons
.
first := Cons clone.
putln: first car ++ " " ++ first cdr.
;; Prints Nil Nil
second := cons ("A", "B").
putln: second car ++ " " ++ second cdr.
;; Prints A B
Containers, as well as cons cells, can be used to store methods in a sanitary way. Generally, when storing a method in a slot, future access to that slot will evaluate the method unless explicitly blocked. However, storing methods in a cons cell will allow later retrievals to get the method back automatically.
;; An ordinary object
first := Object clone.
first foo := { "Test". }.
println: first foo. ; Prints "Test"
;; A cons cell
second := cons (Nil, Nil).
second car = { "Test". }.
println: second car. ; Prints "Method"
Latitude arrays are cloned off the Array
object. You can clone from
the Array
object directly, but it is more common to enclose elements
in square brackets, which will implicitly construct an array.
;; A new, empty array
Array clone.
;; Another new, empty array
[].
;; An array containing some elements
[1, 2, 3, 4, 5, 6].
Arrays can be read from and written to in constant time, via nth
and
nth=
. Negative indices will count from the back of the array.
arr := ["A", "B", "C", "D"].
println: arr nth (2). ;; Prints "C"
arr nth (2) = 99.
println: arr nth (2). ;; Prints 99
println: arr nth (-2). ;; Prints 99
println: arr. ;; Prints ["A", "B", 99, "D"]
Additionally, elements can be added or removed from the front or back of the array in constant time.
arr := [1, 2, 3].
arr popBack.
arr pushBack: 4.
arr pushBack: 5.
arr popFront.
arr pushFront: 0.
println: arr. ; [0, 2, 4, 5]
Insertion and removal at arbitrary positions in the array is
O(n)
.2
arr := [1, 2, 3, 4, 5].
arr insert: 1, 999.
arr removeOnce! { $1 == 3. }.
println: arr. ; [1, 999, 2, 4, 5]
Dictionaries in Latitude consist of key-value pairs, where the keys
are symbols and the values are Latitude objects. Dictionaries are
cloned from the Dict
object. As with arrays, you can clone the
Dict
object directly, but it is mor ecommon to see the dictionary
syntax used. Dictionary syntax is like array syntax except that the
key/value pairs are separated by an arrow =>
. An empty dictionary
can be constructed with [=>]
, since []
is an empty array.
;; A new, empty dictionary
Dict clone.
;; Another new, empty dictionary
[=>].
;; A dictionary containing some elements
[ 'one => 1, 'two => 2, 'three => 3 ].
Dictionaries are unordered, so the key-value pairs may not display in
the same order that you insert them. Accessing dictionary elements is
done via get
and get=
, and checking whether a key exists is done
via has?
.
foo := [ 'one => 1 ].
println: foo get 'one. ; 1
println: foo has? 'two. ; False
foo get 'one = -1.
foo get 'two = 2.
println: foo has? 'two. ; True
println: foo. ; [ 'two => 2, 'one => -1 ] (order may vary)
Latitude provides quite a few metaprogramming facilities, and when
using these facilities, it is often helpful to provide a list of
names, such as variable names. We’ve already been doing this, with
takes
.
foo := {
takes '[a, b, c].
;; ...
}.
If a single quote precedes an array or dictionary expression, you can think of the quote on the outside as “distributing” onto any names on the inside. Any literals which are not names (or which are already quoted) do not receive the quote.
'[a, b, c, d] == ['a, 'b, 'c, 'd].
'[1, 2, foo, 'bar] == [1, 2, 'foo, 'bar].
'[a => 1, b => 2] == ['b => 2, 'a => 1].
'[[[[latitude]]]] == [[[['latitude]]]]
Collection types, such as arrays and dictionaries, implement an
iterator
method which returns an iterator over the collection.
Iterators respond to element
, returning the current element; next
,
moving to the next element; and end?
, checking whether the iterator
is at its end yet. Additionally, if the iterator is over a mutable
collection, element=
will set the current element.
Here is a simple loop which prints out each element of the array.
arr := [1, 2, 3, 4, 5].
iter := arr iterator.
while { iter end? not. } do {
println: iter element.
iter next.
}.
However, we almost never operate directly with iterators, as
collections implement many higher-level iteration constructs built on
top of iterators. The simplest of these is visit
, which calls its
block for each element in the collection. This code snippet behaves
equivalently to the above snippet.
arr := [1, 2, 3, 4, 5].
arr visit {
println: $1.
}.
Iterating over a dictionary will yield key-value pairs as cons cells. The key is stored immutably in the car cell, and the value is stored mutably in the cdr cell, so that changes to the cdr will be reflected in the dictionary.
dict := [ 'foo => 1, 'bar => 2 ].
dict visit {
$1 cdr = $1 cdr + 1.
}.
println: dict. ; [ 'foo => 2, 'bar => 3 ] (order may vary)
The usual functional stream operations, such as folding and mapping, are available and work as expected.
arr := [1, 2, 3, 4, 5].
println: arr foldl (0, { $1 + $2. }). ; 15
println: arr map { $1 * $1. }. ; [1, 4, 9, 16, 25]
filter
returns an iterator, which can be converted back into an
array with to
.
arr := [1, 2, 3, 4, 5].
filtered := arr filter { $1 mod 2 == 0. }.
println: filtered. ; FilterIterator
println: filtered to (Array). ; [2, 4]
A full list of the collection methods can be found at Collection.
In addition to arrays and dictionaries, several other Latitude objects are iterable. For one thing, strings are iterable by character. Latitude strings are always encoded in UTF-8 and will iterate character-by-character.
"Hello!" visit {
putln: $1.
}.
"Unicode works too! αβγ" visit {
putln: $1.
}.
Nil
is an iterable object and always acts as an empty collection.
$*
is a method which returns an argument list object. An argument
list object is a collection which contains all of the arguments
currently in scope.
test := {
$* visit {
println: $1.
}.
}.
test: 1, 2, 3, 4, 5. ; Prints each argument on a separate line
Remember that arguments in Latitude are inherited from parent scopes,
so when iterating over $*
, you may find that there are more
arguments than you expect. Generally, if you write a method which
expects N arguments, you should generally assume that $*
might
contain additional arguments, and so you should only utilize the
first N.
Finally, if your intention is to iterate from some n
to some m
,
then Latitude provides a Range
type for doing so. We have already
indirectly seen this Range
type, used in looping statements, but it
is also a full-fledged collection and as such can have any collection
method applied to it.
10 upto 15. ; Collection consisting of 10, 11, 12, 13, 14
15 downto 10. ; Collection consisting of 15, 14, 13, 12, 11
5 times. ; Collection consisting of 0, 1, 2, 3, 4
This is not all of the collections in Latitude, but arrays and dictionaries are among the most commonly used. In the next chapter, we will discuss a very powerful control construct which can be used to implement many others.
[up]
[prev - Simple Flow Control]
[next - Advanced Flow Control]
1 Using =
calls the
accessor methods car=
or cdr=
. By doing so, we update the actual
internal slot where the car
and cdr
values are stored, rather than
the car
/ cdr
methods themselves. This behavior allows us to
safely store methods in a cons cell.
2 The !
in
removeOnce!
distinguishes it from the method removeOnce
, which has
the same behavior but returns a new array, whereas removeOnce!
mutates the array in-place.