Not-so-simple types

Collections are types which are used to aggregate and organize a set of data. Ryan supports two collection types: lists and dictionaries (or maps).

Lists

Lists are a collection of values, sequentially ordered. Here are some lists for you:

[1, 2, 3]       // Lists are a comma-separated sequence of values between brackets.
[1, "a", null]  // You can mix and match the types however you want.
[]              // An empty list is also a list
[
    1,
    2,
    3,  // Use forgiving commas for long lists
]

The two main operations on lists are concatenation (just like with strings) and index accessing. Concatenation is pretty straightforward:

[1, 2, 3] + ["a", "b", "c"]     // -> [1, 2, 3, "a", "b", "c"]

Alternatively, like in some programming languages, Ryan allows you to easily compose lists using flatten expressions (the ... syntax below):

let x = [4, 5, 6];
[1, 2, 3, ...x]     // -> [1, 2, 3, 4, 5, 6]

This yields a similar effect to adding lists.

Index accessing is also easy: get the n-th element in the list. However, Ryan shares a pet-peeve with many other programming languages: the first position is indexed by the number zero.

[1, 2, 3][0]        // -> 1
[1, 2, 3][1]        // -> 2, not 1!
[1, 2, 3][3]        // error! Tried to access index 3 of list of length 3 
                    // (3 is the 4th element!!)

Dictionaries (or maps)

dictionaries are a collection of values indexed by strings. This name, dictionary, is quite apt in describing what it does. Just like the regular old book, it uniquely associates a word to a value. Here is an example of a Ryan dictionary:

{
    "name": "Sir Lancelot of Camelot",
    "quest": "to seek the Holly Grail",
    "favorite_color": "blue"
}

However, the same dictionary is much nicer written this alternative way:

{
    name: "Sir Lancelot of Camelot",
    quest: "to seek the Holly Grail",
    favorite_color: "blue",     // use forgiving commas for extra niceness
}

Whenever the key of a dictionary could be a valid variable name, you can omit the double quotes of the string. This doesn't change the value of dictionary (both examples correspond to the same thing); this is just syntax sugar: an nicer way of expressing the same thing.

Dictionaries have other few different tricks on their sleeves. For example, it is syntactically valid to repeat a key in the dictionary:

{
    a: 1,
    a: 2,
}

However, only the last occurrence of the same key will count to the final result. The above dictionary evaluates to { a: 2 }. You can also use the name of a variable directly, without the value, if the key coincides with the variable name:

let a = 2;

{
    a,      // ... as opposed to `a: a`
}

The above will also evaluate to { a: 2 }.

You can also specify an if guard at the end of each key, in order to make its insertion in the dictionary optional, like so:

{
    a: 1,
    b: 2 if "abc" == "def",     // wrong!
    c: 3 if 1 == 1,             // quite true...
}

This will evaluate to { "a": 1, "c": 3 }.

Lastly, just like with lists, you can concatenate dictionaries and index them in the very same fashion as you would do a list:

let x = { a: 1, b: 2, c: 3 };
let y = { d: 4, e: 5, f: 6 };
x + y       // -> { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }
x["a"]      // -> 1
x["d"]      // error! Key "d" missing in map

And you can also use flatten expressions with dictionaries, just as if you would do with a list:

let x = { a: 1, b: 2, c: 3 };
{
    d: 4,
    e: 5,
    f: 6,
    ...x
}   // -> { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }

This can be useful when creating record inheritance structures in Ryan.

Lastly, if a certain key could be a valid variable name, one can also use the shorter . operator to index dictionaries:

let x = { a: 1, b: 2, c: 3 };
x.a     // -> 1
a.d     // error! Key "d" missing in map