diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-04-06 22:45:02 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-04-06 22:45:02 -0400 |
| commit | 44cd26f2cebd760a53aa4ff1b7779e718a101650 (patch) | |
| tree | 4bdc9144c6825a0c394155712d5e464ee2a61061 /docs | |
| parent | 3406515a44b13d0c290c28ac42bd364ce27560c7 (diff) | |
Rename Array -> List in all code and docs
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/README.md | 2 | ||||
| -rw-r--r-- | docs/arrays.md | 908 | ||||
| -rw-r--r-- | docs/command-line-parsing.md | 6 | ||||
| -rw-r--r-- | docs/lists.md | 909 | ||||
| -rw-r--r-- | docs/metamethods.md | 10 | ||||
| -rw-r--r-- | docs/nums.md | 2 | ||||
| -rw-r--r-- | docs/operators.md | 12 | ||||
| -rw-r--r-- | docs/optionals.md | 8 | ||||
| -rw-r--r-- | docs/paths.md | 10 | ||||
| -rw-r--r-- | docs/pointers.md | 14 | ||||
| -rw-r--r-- | docs/reductions.md | 2 | ||||
| -rw-r--r-- | docs/serialization.md | 6 | ||||
| -rw-r--r-- | docs/sets.md | 8 | ||||
| -rw-r--r-- | docs/tables.md | 6 | ||||
| -rw-r--r-- | docs/text.md | 38 |
15 files changed, 971 insertions, 970 deletions
diff --git a/docs/README.md b/docs/README.md index 05d335cc..e8cd4a2f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,7 +18,7 @@ A few topics that are documented: Information about Tomo's built-in types can be found here: -- [Arrays](arrays.md) +- [Lists](lists.md) - [Booleans](booleans.md) - [Bytes](bytes.md) - [Enums](enums.md) diff --git a/docs/arrays.md b/docs/arrays.md deleted file mode 100644 index ec114442..00000000 --- a/docs/arrays.md +++ /dev/null @@ -1,908 +0,0 @@ -# Arrays - -Tomo supports arrays as a container type that holds a list of elements of any -type in a compact format. Arrays are immutable by default, but use -copy-on-write semantics to efficiently mutate in place when possible. **Arrays -are 1-indexed**, which means the first item in the array has index `1`. - -## Syntax - -Arrays are written using square brackets and a list of comma-separated elements: - -```tomo -nums := [10, 20, 30] -``` - -Each element must have the same type (or be easily promoted to the same type). If -you want to have an empty array, you must specify what type goes inside the array -like this: - -```tomo -empty : [Int] = [] -``` - -For type annotations, an array that holds items with type `T` is written as `[T]`. - -### Array Comprehensions - -Arrays can also use comprehensions, where you specify how to dynamically create -all the elements by iteration instead of manually specifying each: - -```tomo ->> [i*10 for i in (3).to(8)] -= [30, 40, 50, 60, 70, 80] ->> [i*10 for i in (3).to(8) if i != 4] -= [30, 50, 60, 70, 80] -``` - -Comprehensions can be combined with regular items or other comprehensions: - -```tomo ->> [-1, i*10 for i in (3).to(8), i for i in 3] -= [-1, 30, 40, 50, 60, 70, 80, 1, 2, 3] -``` - -## Length - -Array length can be accessed by the `.length` field: - -```tomo ->> [10, 20, 30].length -= 3 -``` - -## Indexing - -Array values are accessed using square bracket indexing. Since arrays are -1-indexed, the index `1` corresponds to the first item in the array. Negative -indices are used to refer to items from the back of the array, so `-1` is the -last item, `-2` is the second-to-last, and so on. - -```tomo -arr := [10, 20, 30, 40] ->> arr[1] -= 10 - ->> arr[2] -= 20 - ->> arr[-1] -= 40 - ->> arr[-2] -= 30 -``` - -If an array index of `0` or any value larger than the length of the array is -used, it will trigger a runtime error that will print what the invalid array -index was, the length of the array, and a stack trace. As a performance -operation, if array bounds checking proves to be a performance hot spot, you -can explicitly disable bounds checking by adding `arr[i; unchecked]` to the -array access. - -## Iteration - -You can iterate over the items in an array like this: - -```tomo -for item in array: - ... - -for i, item in array: - ... -``` - -Array iteration operates over the value of the array when the loop began, so -modifying the array during iteration is safe and will not result in the loop -iterating over any of the new values. - -## Concatenation - -Arrays can be concatenated with the `++` operator, which returns an array that -has the items from one appended to the other. This should not be confused with -the addition operator `+`, which does not work with arrays. - -```tomo ->> [1, 2] ++ [3, 4] -= [1, 2, 3, 4] -``` - -## Implementation Details - -Under the hood, arrays are implemented as a struct that contains a pointer to a -contiguous chunk of memory storing the elements of the array and some other -metadata. Since Tomo has datatypes with different sizes, like `Bool`s which -take one byte and `struct`s which can take up many bytes, it's worth noting -that arrays store the elements compactly and inline, without the need for each -array cell to hold a pointer to where the data actually lives. - -The other metadata stored with an array includes its length as well as the -_stride_ of the array. The stride is not exposed to the user, but it's the gap -in bytes between each element in the array. The reason this is mentioned is -that it is possible to create immutable slices of arrays in constant time by -creating a new struct that points to the appropriate starting place for the -array items and has the appropriate stride. The upshot is that a method like -`array.reversed()` does not actually copy the array, it simply returns a struct -that points to the back of the array with a negative stride. Arrays adhere to -copy-on-write semantics, so we can cheaply create many read-only references to -the same data, and only need to do copying if we plan to modify data. After -doing a modification, future modifications can be done in-place as long as -there is only one reference to that data. - -Internally, we also take advantage of this inside of tables, which compactly -store all of the key/value pairs in a contiguous array and we can return an -immutable slice of that array showing only the keys or only the values by -choosing the right starting point and stride. - -## Copy on Write - -Arrays can be thought of as values that have copy-on-write semantics that use -reference counting to perform efficient in-place mutations instead of copying -as a performance optimization when it wouldn't affect the program's semantics. -Without getting too deep into the details, suffice it to say that when you -create an array, that array can be thought of as a singular "value" in the same -way that `123` is a value. That variable's value will never change unless you -explicitly perform an assignment operation on the variable or call a method on -the variable. - -Because it would be tedious to require users to write all array operations as -pure functions like `array = array.with_value_at_index(value=x, index=i)`, Tomo -provides the familiar imperative syntax for modifying arrays, but keeps the -semantics of the pure functional style. Writing `array[i] = x` is -_semantically_ equivalent to `array = array.with_value_at_index(value=x, -index=i)`, but much more readable and easy to write. Similarly, -`array.insert(x)` is semantically equivalent to `array = -array.with_value_inserted(x)`. We implement these mutating methods as functions -that take a pointer to an array variable, which then either mutate the array's -data in-place (if this is the only thing referencing that data) or construct a -new array and store its value in the memory where the array variable is stored. - -When there is only a single reference to an array value, we can perform these -modifications in-place (arrays typically have a little bit of spare capacity at -the end, so appending usually doesn't trigger a reallocation). When there are -shared references, we must create a copy of the array's data before modifying -it so the other references don't see the effects of the mutation. Here are some -simple examples: - -```tomo -nums := [10, 20, 30, 39] - -// Efficient in-place mutation because data references are not shared: -nums[4] = 40 - -// Constant time operation, but increments the reference count: -tmp := nums ->> tmp -= [10, 20, 30, 40] - -// Now, a mutation will trigger a copy-on-write, -// which resets the reference count to zero: -nums[4] = 999 ->> nums -= [10, 20, 30, 999] - -// Because of the copy-on-write, `tmp` is unchanged: ->> tmp -= [10, 20, 30, 40] - -// Since the reference count has been reset, we can do more -// mutations without triggering another copy-on-write: -nums[4] = -1 ->> nums -= [10, 20, 30, -1] -``` - -Array reference counting is _approximate_, but will only ever err on the side -of correctness at the expense of performance, not the other way around. -Occasionally, unnecessary copying may occur, but you should never experience an -array value changing because of some operation performed on a different array -value. - -## Array Pointers - -Since the normal case of arrays is to treat them like immutable values, what do -we do if we actually want to have a shared reference to an array whose contents -change over time? In that case, we want to use the `@` operator to create a -pointer to a heap-allocated array and pass that pointer around. This is the same -behavior that you get in Python when you create a `list`: - -```tomo -nums := @[10, 20, 30] -tmp := nums - -nums.insert(40) ->> tmp -= @[10, 20, 30, 40] -``` - -Having multiple pointers to the same heap-allocated array does not cause the -array's reference count to increase, because there is only one "value" in play: -the one stored on the heap. It's only when we store the "value" in multiple -places that we need to increment the reference count: - -```tomo -// Increment the reference count, because `value` now has to hold -// whatever data was at the pointer's location at this point in time: -value := nums[] -``` - -The TL;DR is: you can cheaply modify local variables that aren't aliased or -`@`-allocated arrays, but if you assign a local variable array to another -variable or dereference a heap pointer, it may trigger copy-on-write behavior. - -## Array Methods - -- [`func binary_search(arr: [T], by: func(x,y:&T->Int32) = T.compare -> Int)`](#binary_search) -- [`func by(arr: [T], step: Int -> [T])`](#by) -- [`func clear(arr: @[T] -> Void)`](#clear) -- [`func counts(arr: [T] -> {T=Int})`](#counts) -- [`func find(arr: [T], target: T -> Int?)`](#find) -- [`func first(arr: [T], predicate: func(item:&T -> Bool) -> Int)`](#first) -- [`func from(arr: [T], first: Int -> [T])`](#from) -- [`func has(arr: [T] -> Bool)`](#has) -- [`func heap_pop(arr: @[T], by: func(x,y:&T->Int32) = T.compare -> T?)`](#heap_pop) -- [`func heap_push(arr: @[T], item: T, by=T.compare -> Void)`](#heap_push) -- [`func heapify(arr: @[T], by: func(x,y:&T->Int32) = T.compare -> Void)`](#heapify) -- [`func insert(arr: @[T], item: T, at: Int = 0 -> Void)`](#insert) -- [`func insert_all(arr: @[T], items: [T], at: Int = 0 -> Void)`](#insert_all) -- [`func pop(arr: &[T], index: Int = -1 -> T?)`](#pop) -- [`func random(arr: [T], random: func(min,max:Int64->Int64)? = none -> T)`](#random) -- [`func remove_at(arr: @[T], at: Int = -1, count: Int = 1 -> Void)`](#remove_at) -- [`func remove_item(arr: @[T], item: T, max_count: Int = -1 -> Void)`](#remove_item) -- [`func reversed(arr: [T] -> [T])`](#reversed) -- [`func sample(arr: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none -> [T])`](#sample) -- [`func shuffle(arr: @[T], random: func(min,max:Int64->Int64)? = none -> Void)`](#shuffle) -- [`func shuffled(arr: [T], random: func(min,max:Int64->Int64)? = none -> [T])`](#shuffled) -- [`func slice(arr: [T], from: Int, to: Int -> [T])`](#slice) -- [`func sort(arr: @[T], by=T.compare -> Void)`](#sort) -- [`sorted(arr: [T], by=T.compare -> [T])`](#sorted) -- [`to(arr: [T], last: Int -> [T])`](#to) -- [`unique(arr: [T] -> {T})`](#unique) - -### `binary_search` -Performs a binary search on a sorted array. - -```tomo -func binary_search(arr: [T], by: func(x,y:&T->Int32) = T.compare -> Int) -``` - -- `arr`: The sorted array to search. -- `by`: The comparison function used to determine order. If not specified, the - default comparison function for the item type will be used. - -**Returns:** -Assuming the input array is sorted according to the given comparison function, -return the index where the given item would be inserted to maintain the sorted -order. That is, if the item is found, return its index, otherwise return the -place where it would be found if it were inserted and the array were sorted. - -**Example:** -```tomo ->> [1, 3, 5, 7, 9].binary_search(5) -= 3 - ->> [1, 3, 5, 7, 9].binary_search(-999) -= 1 - ->> [1, 3, 5, 7, 9].binary_search(999) -= 6 -``` - ---- - -### `by` -Creates a new array with elements spaced by the specified step value. - -```tomo -func by(arr: [T], step: Int -> [T]) -``` - -- `arr`: The original array. -- `step`: The step value for selecting elements. - -**Returns:** -A new array with every `step`-th element from the original array. - -**Example:** -```tomo ->> [1, 2, 3, 4, 5, 6].by(2) -= [1, 3, 5] -``` - ---- - -### `clear` -Clears all elements from the array. - -```tomo -func clear(arr: @[T] -> Void) -``` - -- `arr`: The mutable reference to the array to be cleared. - -**Returns:** -Nothing. - -**Example:** -```tomo ->> my_array.clear() -``` - ---- - -### `counts` -Counts the occurrences of each element in the array. - -```tomo -func counts(arr: [T] -> {T=Int}) -``` - -- `arr`: The array to count elements in. - -**Returns:** -A table mapping each element to its count. - -**Example:** -```tomo ->> [10, 20, 30, 30, 30].counts() -= {10=1, 20=1, 30=3} -``` - ---- - -### `find` -Finds the index of the first occurrence of an element (if any). - -```tomo -func find(arr: [T], target: T -> Int?) -``` - -- `arr`: The array to search through. -- `item`: The item to find in the array. - -**Returns:** -The index of the first occurrence or `!Int` if not found. - -**Example:** -```tomo ->> [10, 20, 30, 40, 50].find(20) -= 2 : Int? - ->> [10, 20, 30, 40, 50].find(9999) -= none : Int? -``` - ---- - -### `first` -Find the index of the first item that matches a predicate function (if any). - -```tomo -func first(arr: [T], predicate: func(item:&T -> Bool) -> Int) -``` - -- `arr`: The array to search through. -- `predicate`: A function that returns `yes` if the item should be returned or - `no` if it should not. - -**Returns:** -Returns the index of the first item where the predicate is true or `!Int` if no -item matches. - -**Example:** -```tomo ->> [4, 5, 6].find(func(i:&Int): i.is_prime()) -= 5 : Int? ->> [4, 6, 8].find(func(i:&Int): i.is_prime()) -= none : Int? -``` - ---- - -### `from` -Returns a slice of the array starting from a specified index. - -```tomo -func from(arr: [T], first: Int -> [T]) -``` - -- `arr`: The original array. -- `first`: The index to start from. - -**Returns:** -A new array starting from the specified index. - -**Example:** -```tomo ->> [10, 20, 30, 40, 50].from(3) -= [30, 40, 50] -``` - ---- - -### `has` -Checks if the array has any elements. - -```tomo -func has(arr: [T] -> Bool) -``` - -- `arr`: The array to check. - -**Returns:** -`yes` if the array has elements, `no` otherwise. - -**Example:** -```tomo ->> [10, 20, 30].has(20) -= yes -``` - ---- - -### `heap_pop` -Removes and returns the top element of a heap or `none` if the array is empty. -By default, this is the *minimum* value in the heap. - -```tomo -func heap_pop(arr: @[T], by: func(x,y:&T->Int32) = T.compare -> T?) -``` - -- `arr`: The mutable reference to the heap. -- `by`: The comparison function used to determine order. If not specified, the - default comparison function for the item type will be used. - -**Returns:** -The removed top element of the heap or `none` if the array is empty. - -**Example:** -```tomo ->> my_heap := [30, 10, 20] ->> my_heap.heapify() ->> my_heap.heap_pop() -= 10 -``` - ---- - -### `heap_push` -Adds an element to the heap and maintains the heap property. By default, this -is a *minimum* heap. - -```tomo -func heap_push(arr: @[T], item: T, by=T.compare -> Void) -``` - -- `arr`: The mutable reference to the heap. -- `item`: The item to be added. -- `by`: The comparison function used to determine order. If not specified, the - default comparison function for the item type will be used. - -**Returns:** -Nothing. - -**Example:** -```tomo ->> my_heap.heap_push(10) -``` - ---- - -### `heapify` -Converts an array into a heap. - -```tomo -func heapify(arr: @[T], by: func(x,y:&T->Int32) = T.compare -> Void) -``` - -- `arr`: The mutable reference to the array to be heapified. -- `by`: The comparison function used to determine order. If not specified, the - default comparison function for the item type will be used. - -**Returns:** -Nothing. - -**Example:** -```tomo ->> my_heap := [30, 10, 20] ->> my_heap.heapify() -``` - ---- - -### `insert` -Inserts an element at a specified position in the array. - -```tomo -func insert(arr: @[T], item: T, at: Int = 0 -> Void) -``` - -- `arr`: The mutable reference to the array. -- `item`: The item to be inserted. -- `at`: The index at which to insert the item (default is `0`). Since indices - are 1-indexed and negative indices mean "starting from the back", an index of - `0` means "after the last item". - -**Returns:** -Nothing. - -**Example:** -```tomo ->> arr := [10, 20] ->> arr.insert(30) ->> arr -= [10, 20, 30] - ->> arr.insert(999, at=2) ->> arr -= [10, 999, 20, 30] -``` - ---- - -### `insert_all` -Inserts an array of items at a specified position in the array. - -```tomo -func insert_all(arr: @[T], items: [T], at: Int = 0 -> Void) -``` - -- `arr`: The mutable reference to the array. -- `items`: The items to be inserted. -- `at`: The index at which to insert the item (default is `0`). Since indices - are 1-indexed and negative indices mean "starting from the back", an index of - `0` means "after the last item". - -**Returns:** -Nothing. - -**Example:** -```tomo -arr := [10, 20] -arr.insert_all([30, 40]) ->> arr -= [10, 20, 30, 40] - -arr.insert_all([99, 100], at=2) ->> arr -= [10, 99, 100, 20, 30, 40] -``` - ---- - -### `pop` -Removes and returns an item from the array. If the given index is present in -the array, the item at that index will be removed and the array will become one -element shorter. - -```tomo -func pop(arr: &[T], index: Int = -1 -> T?) -``` - -- `arr`: The array to remove an item from. -- `index`: The index from which to remove the item (default: the last item). - -**Returns:** -`none` if the array is empty or the given index does not exist in the array, -otherwise the item at the given index. - -**Example:** -```tomo ->> arr := [10, 20, 30, 40] - ->> arr.pop() -= 40 ->> arr -= &[10, 20, 30] - ->> arr.pop(index=2) -= 20 ->> arr -= &[10, 30] -``` - ---- - -### `random` -Selects a random element from the array. - -```tomo -func random(arr: [T], random: func(min,max:Int64->Int64)? = none -> T) -``` - -- `arr`: The array from which to select a random element. -- `random`: If provided, this function will be used to get a random index in the array. Returned - values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number - generation) - -**Returns:** -A random element from the array. - -**Example:** -```tomo ->> [10, 20, 30].random() -= 20 -``` - ---- - -### `remove_at` -Removes elements from the array starting at a specified index. - -```tomo -func remove_at(arr: @[T], at: Int = -1, count: Int = 1 -> Void) -``` - -- `arr`: The mutable reference to the array. -- `at`: The index at which to start removing elements (default is `-1`, which means the end of the array). -- `count`: The number of elements to remove (default is `1`). - -**Returns:** -Nothing. - -**Example:** -```tomo -arr := [10, 20, 30, 40, 50] -arr.remove_at(2) ->> arr -= [10, 30, 40, 50] - -arr.remove_at(2, count=2) ->> arr -= [10, 50] -``` - ---- - -### `remove_item` -Removes all occurrences of a specified item from the array. - -```tomo -func remove_item(arr: @[T], item: T, max_count: Int = -1 -> Void) -``` - -- `arr`: The mutable reference to the array. -- `item`: The item to be removed. -- `max_count`: The maximum number of occurrences to remove (default is `-1`, meaning all occurrences). - -**Returns:** -Nothing. - -**Example:** -```tomo -arr := [10, 20, 10, 20, 30] -arr.remove_item(10) ->> arr -= [20, 20, 30] - -arr.remove_item(20, max_count=1) ->> arr -= [20, 30] -``` - ---- - -### `reversed` -Returns a reversed slice of the array. - -```tomo -func reversed(arr: [T] -> [T]) -``` - -- `arr`: The array to be reversed. - -**Returns:** -A slice of the array with elements in reverse order. - -**Example:** -```tomo ->> [10, 20, 30].reversed() -= [30, 20, 10] -``` - ---- - -### `sample` -Selects a sample of elements from the array, optionally with weighted -probabilities. - -```tomo -func sample(arr: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none -> [T]) -``` - -- `arr`: The array to sample from. -- `count`: The number of elements to sample. -- `weights`: The probability weights for each element in the array. These - values do not need to add up to any particular number, they are relative - weights. If no weights are given, elements will be sampled with uniform - probability. -- `random`: If provided, this function will be used to get random values for - sampling the array. The provided function should return random numbers - between `0.0` (inclusive) and `1.0` (exclusive). (Used for deterministic - pseudorandom number generation) - -**Errors:** -Errors will be raised if any of the following conditions occurs: -- The given array has no elements and `count >= 1` -- `count < 0` (negative count) -- The number of weights provided doesn't match the length of the array. -- Any weight in the weights array is negative, infinite, or `NaN` -- The sum of the given weights is zero (zero probability for every element). - -**Returns:** -A list of sampled elements from the array. - -**Example:** -```tomo ->> [10, 20, 30].sample(2, weights=[90%, 5%, 5%]) -= [10, 10] -``` - ---- - -### `shuffle` -Shuffles the elements of the array in place. - -```tomo -func shuffle(arr: @[T], random: func(min,max:Int64->Int64)? = none -> Void) -``` - -- `arr`: The mutable reference to the array to be shuffled. -- `random`: If provided, this function will be used to get a random index in the array. Returned - values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number - generation) - -**Returns:** -Nothing. - -**Example:** -```tomo ->> arr.shuffle() -``` - ---- - -### `shuffled` -Creates a new array with elements shuffled. - -```tomo -func shuffled(arr: [T], random: func(min,max:Int64->Int64)? = none -> [T]) -``` - -- `arr`: The array to be shuffled. -- `random`: If provided, this function will be used to get a random index in the array. Returned - values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number - generation) - -**Returns:** -A new array with shuffled elements. - -**Example:** -```tomo ->> [10, 20, 30, 40].shuffled() -= [40, 10, 30, 20] -``` - ---- - -### `slice` -Returns a slice of the array spanning the given indices (inclusive). - -```tomo -func slice(arr: [T], from: Int, to: Int -> [T]) -``` - -- `arr`: The original array. -- `from`: The first index to include. -- `to`: The last index to include. - -**Returns:** -A new array spanning the given indices. Note: negative indices are counted from -the back of the array, so `-1` refers to the last element, `-2` the -second-to-last, and so on. - -**Example:** -```tomo ->> [10, 20, 30, 40, 50].slice(2, 4) -= [20, 30, 40] - ->> [10, 20, 30, 40, 50].slice(-3, -2) -= [30, 40] -``` - ---- - -### `sort` -Sorts the elements of the array in place in ascending order (small to large). - -```tomo -func sort(arr: @[T], by=T.compare -> Void) -``` - -- `arr`: The mutable reference to the array to be sorted. -- `by`: The comparison function used to determine order. If not specified, the - default comparison function for the item type will be used. - -**Returns:** -Nothing. - -**Example:** -```tomo -arr := [40, 10, -30, 20] -arr.sort() ->> arr -= [-30, 10, 20, 40] - -arr.sort(func(a,b:&Int): a.abs() <> b.abs()) ->> arr -= [10, 20, -30, 40] -``` - ---- - -### `sorted` -Creates a new array with elements sorted. - -```tomo -sorted(arr: [T], by=T.compare -> [T]) -``` - -- `arr`: The array to be sorted. -- `by`: The comparison function used to determine order. If not specified, the - default comparison function for the item type will be used. - -**Returns:** -A new array with sorted elements. - -**Example:** -```tomo ->> [40, 10, -30, 20].sorted() -= [-30, 10, 20, 40] - ->> [40, 10, -30, 20].sorted(func(a,b:&Int): a.abs() <> b.abs()) -= [10, 20, -30, 40] -``` - ---- - -### `to` -Returns a slice of the array from the start of the original array up to a specified index (inclusive). - -```tomo -to(arr: [T], last: Int -> [T]) -``` - -- `arr`: The original array. -- `last`: The index up to which elements should be included. - -**Returns:** -A new array containing elements from the start up to the specified index. - -**Example:** -```tomo ->> [10, 20, 30, 40, 50].to(3) -= [10, 20, 30] - ->> [10, 20, 30, 40, 50].to(-2) -= [10, 20, 30, 40] -``` - ---- - -### `unique` -Returns a Set that contains the unique elements of the array. - -```tomo -unique(arr: [T] -> {T}) -``` - -- `arr`: The array to process. - -**Returns:** -A set containing only unique elements from the array. - -**Example:** -```tomo ->> [10, 20, 10, 10, 30].unique() -= {10, 20, 30} -``` diff --git a/docs/command-line-parsing.md b/docs/command-line-parsing.md index 127cf232..bd71ba41 100644 --- a/docs/command-line-parsing.md +++ b/docs/command-line-parsing.md @@ -97,10 +97,10 @@ foo: Invalid value provided for --foo; valid values are: One Two Signature: foo [--help] <foo> ``` -### Arrays of Text +### Lists of Text -Currently, Tomo supports accepting arguments that take an array of text. -Array-of-text arguments can be passed like this: +Currently, Tomo supports accepting arguments that take a list of text. +List-of-text arguments can be passed like this: ```tomo # many-texts.tm diff --git a/docs/lists.md b/docs/lists.md new file mode 100644 index 00000000..e605eea1 --- /dev/null +++ b/docs/lists.md @@ -0,0 +1,909 @@ +# Lists + +Tomo supports lists as a container type that holds a list of elements of any +type in a compact format similar to a C-style array. Lists are immutable by +default, but use copy-on-write semantics to efficiently mutate in place when +possible. **Lists are 1-indexed**, which means the first item in the list has +index `1`. + +## Syntax + +Lists are written using square brackets and a list of comma-separated elements: + +```tomo +nums := [10, 20, 30] +``` + +Each element must have the same type (or be easily promoted to the same type). If +you want to have an empty list, you must specify what type goes inside the list +like this: + +```tomo +empty : [Int] = [] +``` + +For type annotations, a list that holds items with type `T` is written as `[T]`. + +### List Comprehensions + +Lists can also use comprehensions, where you specify how to dynamically create +all the elements by iteration instead of manually specifying each: + +```tomo +>> [i*10 for i in (3).to(8)] += [30, 40, 50, 60, 70, 80] +>> [i*10 for i in (3).to(8) if i != 4] += [30, 50, 60, 70, 80] +``` + +Comprehensions can be combined with regular items or other comprehensions: + +```tomo +>> [-1, i*10 for i in (3).to(8), i for i in 3] += [-1, 30, 40, 50, 60, 70, 80, 1, 2, 3] +``` + +## Length + +List length can be accessed by the `.length` field: + +```tomo +>> [10, 20, 30].length += 3 +``` + +## Indexing + +List values are accessed using square bracket indexing. Since lists are +1-indexed, the index `1` corresponds to the first item in the list. Negative +indices are used to refer to items from the back of the list, so `-1` is the +last item, `-2` is the second-to-last, and so on. + +```tomo +list := [10, 20, 30, 40] +>> list[1] += 10 + +>> list[2] += 20 + +>> list[-1] += 40 + +>> list[-2] += 30 +``` + +If a list index of `0` or any value larger than the length of the list is +used, it will trigger a runtime error that will print what the invalid list +index was, the length of the list, and a stack trace. As a performance +operation, if list bounds checking proves to be a performance hot spot, you +can explicitly disable bounds checking by adding `list[i; unchecked]` to the +list access. + +## Iteration + +You can iterate over the items in a list like this: + +```tomo +for item in list: + ... + +for i, item in list: + ... +``` + +List iteration operates over the value of the list when the loop began, so +modifying the list during iteration is safe and will not result in the loop +iterating over any of the new values. + +## Concatenation + +Lists can be concatenated with the `++` operator, which returns a list that +has the items from one appended to the other. This should not be confused with +the addition operator `+`, which does not work with lists. + +```tomo +>> [1, 2] ++ [3, 4] += [1, 2, 3, 4] +``` + +## Implementation Details + +Under the hood, lists are implemented as a struct that contains a pointer to a +contiguous chunk of memory storing the elements of the list and some other +metadata. Since Tomo has datatypes with different sizes, like `Bool`s which +take one byte and `struct`s which can take up many bytes, it's worth noting +that lists store the elements compactly and inline, without the need for each +list cell to hold a pointer to where the data actually lives. + +The other metadata stored with a list includes its length as well as the +_stride_ of the list. The stride is not exposed to the user, but it's the gap +in bytes between each element in the list. The reason this is mentioned is +that it is possible to create immutable slices of lists in constant time by +creating a new struct that points to the appropriate starting place for the +list items and has the appropriate stride. The upshot is that a method like +`list.reversed()` does not actually copy the list, it simply returns a struct +that points to the back of the list with a negative stride. Lists adhere to +copy-on-write semantics, so we can cheaply create many read-only references to +the same data, and only need to do copying if we plan to modify data. After +doing a modification, future modifications can be done in-place as long as +there is only one reference to that data. + +Internally, we also take advantage of this inside of tables, which compactly +store all of the key/value pairs in a contiguous list and we can return an +immutable slice of that list showing only the keys or only the values by +choosing the right starting point and stride. + +## Copy on Write + +Lists can be thought of as values that have copy-on-write semantics that use +reference counting to perform efficient in-place mutations instead of copying +as a performance optimization when it wouldn't affect the program's semantics. +Without getting too deep into the details, suffice it to say that when you +create a list, that list can be thought of as a singular "value" in the same +way that `123` is a value. That variable's value will never change unless you +explicitly perform an assignment operation on the variable or call a method on +the variable. + +Because it would be tedious to require users to write all list operations as +pure functions like `list = list.with_value_at_index(value=x, index=i)`, Tomo +provides the familiar imperative syntax for modifying lists, but keeps the +semantics of the pure functional style. Writing `list[i] = x` is +_semantically_ equivalent to `list = list.with_value_at_index(value=x, +index=i)`, but much more readable and easy to write. Similarly, +`list.insert(x)` is semantically equivalent to `list = +list.with_value_inserted(x)`. We implement these mutating methods as functions +that take a pointer to a list variable, which then either mutate the list's +data in-place (if this is the only thing referencing that data) or construct a +new list and store its value in the memory where the list variable is stored. + +When there is only a single reference to a list value, we can perform these +modifications in-place (lists typically have a little bit of spare capacity at +the end, so appending usually doesn't trigger a reallocation). When there are +shared references, we must create a copy of the list's data before modifying +it so the other references don't see the effects of the mutation. Here are some +simple examples: + +```tomo +nums := [10, 20, 30, 39] + +// Efficient in-place mutation because data references are not shared: +nums[4] = 40 + +// Constant time operation, but increments the reference count: +tmp := nums +>> tmp += [10, 20, 30, 40] + +// Now, a mutation will trigger a copy-on-write, +// which resets the reference count to zero: +nums[4] = 999 +>> nums += [10, 20, 30, 999] + +// Because of the copy-on-write, `tmp` is unchanged: +>> tmp += [10, 20, 30, 40] + +// Since the reference count has been reset, we can do more +// mutations without triggering another copy-on-write: +nums[4] = -1 +>> nums += [10, 20, 30, -1] +``` + +List reference counting is _approximate_, but will only ever err on the side +of correctness at the expense of performance, not the other way around. +Occasionally, unnecessary copying may occur, but you should never experience an +list value changing because of some operation performed on a different list +value. + +## List Pointers + +Since the normal case of lists is to treat them like immutable values, what do +we do if we actually want to have a shared reference to a list whose contents +change over time? In that case, we want to use the `@` operator to create a +pointer to a heap-allocated list and pass that pointer around. This is the same +behavior that you get in Python when you create a `list`: + +```tomo +nums := @[10, 20, 30] +tmp := nums + +nums.insert(40) +>> tmp += @[10, 20, 30, 40] +``` + +Having multiple pointers to the same heap-allocated list does not cause the +list's reference count to increase, because there is only one "value" in play: +the one stored on the heap. It's only when we store the "value" in multiple +places that we need to increment the reference count: + +```tomo +// Increment the reference count, because `value` now has to hold +// whatever data was at the pointer's location at this point in time: +value := nums[] +``` + +The TL;DR is: you can cheaply modify local variables that aren't aliased or +`@`-allocated lists, but if you assign a local variable list to another +variable or dereference a heap pointer, it may trigger copy-on-write behavior. + +## List Methods + +- [`func binary_search(list: [T], by: func(x,y:&T->Int32) = T.compare -> Int)`](#binary_search) +- [`func by(list: [T], step: Int -> [T])`](#by) +- [`func clear(list: @[T] -> Void)`](#clear) +- [`func counts(list: [T] -> {T=Int})`](#counts) +- [`func find(list: [T], target: T -> Int?)`](#find) +- [`func first(list: [T], predicate: func(item:&T -> Bool) -> Int)`](#first) +- [`func from(list: [T], first: Int -> [T])`](#from) +- [`func has(list: [T] -> Bool)`](#has) +- [`func heap_pop(list: @[T], by: func(x,y:&T->Int32) = T.compare -> T?)`](#heap_pop) +- [`func heap_push(list: @[T], item: T, by=T.compare -> Void)`](#heap_push) +- [`func heapify(list: @[T], by: func(x,y:&T->Int32) = T.compare -> Void)`](#heapify) +- [`func insert(list: @[T], item: T, at: Int = 0 -> Void)`](#insert) +- [`func insert_all(list: @[T], items: [T], at: Int = 0 -> Void)`](#insert_all) +- [`func pop(list: &[T], index: Int = -1 -> T?)`](#pop) +- [`func random(list: [T], random: func(min,max:Int64->Int64)? = none -> T)`](#random) +- [`func remove_at(list: @[T], at: Int = -1, count: Int = 1 -> Void)`](#remove_at) +- [`func remove_item(list: @[T], item: T, max_count: Int = -1 -> Void)`](#remove_item) +- [`func reversed(list: [T] -> [T])`](#reversed) +- [`func sample(list: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none -> [T])`](#sample) +- [`func shuffle(list: @[T], random: func(min,max:Int64->Int64)? = none -> Void)`](#shuffle) +- [`func shuffled(list: [T], random: func(min,max:Int64->Int64)? = none -> [T])`](#shuffled) +- [`func slice(list: [T], from: Int, to: Int -> [T])`](#slice) +- [`func sort(list: @[T], by=T.compare -> Void)`](#sort) +- [`sorted(list: [T], by=T.compare -> [T])`](#sorted) +- [`to(list: [T], last: Int -> [T])`](#to) +- [`unique(list: [T] -> {T})`](#unique) + +### `binary_search` +Performs a binary search on a sorted list. + +```tomo +func binary_search(list: [T], by: func(x,y:&T->Int32) = T.compare -> Int) +``` + +- `list`: The sorted list to search. +- `by`: The comparison function used to determine order. If not specified, the + default comparison function for the item type will be used. + +**Returns:** +Assuming the input list is sorted according to the given comparison function, +return the index where the given item would be inserted to maintain the sorted +order. That is, if the item is found, return its index, otherwise return the +place where it would be found if it were inserted and the list were sorted. + +**Example:** +```tomo +>> [1, 3, 5, 7, 9].binary_search(5) += 3 + +>> [1, 3, 5, 7, 9].binary_search(-999) += 1 + +>> [1, 3, 5, 7, 9].binary_search(999) += 6 +``` + +--- + +### `by` +Creates a new list with elements spaced by the specified step value. + +```tomo +func by(list: [T], step: Int -> [T]) +``` + +- `list`: The original list. +- `step`: The step value for selecting elements. + +**Returns:** +A new list with every `step`-th element from the original list. + +**Example:** +```tomo +>> [1, 2, 3, 4, 5, 6].by(2) += [1, 3, 5] +``` + +--- + +### `clear` +Clears all elements from the list. + +```tomo +func clear(list: @[T] -> Void) +``` + +- `list`: The mutable reference to the list to be cleared. + +**Returns:** +Nothing. + +**Example:** +```tomo +>> my_list.clear() +``` + +--- + +### `counts` +Counts the occurrences of each element in the list. + +```tomo +func counts(list: [T] -> {T=Int}) +``` + +- `list`: The list to count elements in. + +**Returns:** +A table mapping each element to its count. + +**Example:** +```tomo +>> [10, 20, 30, 30, 30].counts() += {10=1, 20=1, 30=3} +``` + +--- + +### `find` +Finds the index of the first occurrence of an element (if any). + +```tomo +func find(list: [T], target: T -> Int?) +``` + +- `list`: The list to search through. +- `item`: The item to find in the list. + +**Returns:** +The index of the first occurrence or `!Int` if not found. + +**Example:** +```tomo +>> [10, 20, 30, 40, 50].find(20) += 2 : Int? + +>> [10, 20, 30, 40, 50].find(9999) += none : Int? +``` + +--- + +### `first` +Find the index of the first item that matches a predicate function (if any). + +```tomo +func first(list: [T], predicate: func(item:&T -> Bool) -> Int) +``` + +- `list`: The list to search through. +- `predicate`: A function that returns `yes` if the item should be returned or + `no` if it should not. + +**Returns:** +Returns the index of the first item where the predicate is true or `!Int` if no +item matches. + +**Example:** +```tomo +>> [4, 5, 6].find(func(i:&Int): i.is_prime()) += 5 : Int? +>> [4, 6, 8].find(func(i:&Int): i.is_prime()) += none : Int? +``` + +--- + +### `from` +Returns a slice of the list starting from a specified index. + +```tomo +func from(list: [T], first: Int -> [T]) +``` + +- `list`: The original list. +- `first`: The index to start from. + +**Returns:** +A new list starting from the specified index. + +**Example:** +```tomo +>> [10, 20, 30, 40, 50].from(3) += [30, 40, 50] +``` + +--- + +### `has` +Checks if the list has any elements. + +```tomo +func has(list: [T] -> Bool) +``` + +- `list`: The list to check. + +**Returns:** +`yes` if the list has elements, `no` otherwise. + +**Example:** +```tomo +>> [10, 20, 30].has(20) += yes +``` + +--- + +### `heap_pop` +Removes and returns the top element of a heap or `none` if the list is empty. +By default, this is the *minimum* value in the heap. + +```tomo +func heap_pop(list: @[T], by: func(x,y:&T->Int32) = T.compare -> T?) +``` + +- `list`: The mutable reference to the heap. +- `by`: The comparison function used to determine order. If not specified, the + default comparison function for the item type will be used. + +**Returns:** +The removed top element of the heap or `none` if the list is empty. + +**Example:** +```tomo +>> my_heap := [30, 10, 20] +>> my_heap.heapify() +>> my_heap.heap_pop() += 10 +``` + +--- + +### `heap_push` +Adds an element to the heap and maintains the heap property. By default, this +is a *minimum* heap. + +```tomo +func heap_push(list: @[T], item: T, by=T.compare -> Void) +``` + +- `list`: The mutable reference to the heap. +- `item`: The item to be added. +- `by`: The comparison function used to determine order. If not specified, the + default comparison function for the item type will be used. + +**Returns:** +Nothing. + +**Example:** +```tomo +>> my_heap.heap_push(10) +``` + +--- + +### `heapify` +Converts a list into a heap. + +```tomo +func heapify(list: @[T], by: func(x,y:&T->Int32) = T.compare -> Void) +``` + +- `list`: The mutable reference to the list to be heapified. +- `by`: The comparison function used to determine order. If not specified, the + default comparison function for the item type will be used. + +**Returns:** +Nothing. + +**Example:** +```tomo +>> my_heap := [30, 10, 20] +>> my_heap.heapify() +``` + +--- + +### `insert` +Inserts an element at a specified position in the list. + +```tomo +func insert(list: @[T], item: T, at: Int = 0 -> Void) +``` + +- `list`: The mutable reference to the list. +- `item`: The item to be inserted. +- `at`: The index at which to insert the item (default is `0`). Since indices + are 1-indexed and negative indices mean "starting from the back", an index of + `0` means "after the last item". + +**Returns:** +Nothing. + +**Example:** +```tomo +>> list := [10, 20] +>> list.insert(30) +>> list += [10, 20, 30] + +>> list.insert(999, at=2) +>> list += [10, 999, 20, 30] +``` + +--- + +### `insert_all` +Inserts a list of items at a specified position in the list. + +```tomo +func insert_all(list: @[T], items: [T], at: Int = 0 -> Void) +``` + +- `list`: The mutable reference to the list. +- `items`: The items to be inserted. +- `at`: The index at which to insert the item (default is `0`). Since indices + are 1-indexed and negative indices mean "starting from the back", an index of + `0` means "after the last item". + +**Returns:** +Nothing. + +**Example:** +```tomo +list := [10, 20] +list.insert_all([30, 40]) +>> list += [10, 20, 30, 40] + +list.insert_all([99, 100], at=2) +>> list += [10, 99, 100, 20, 30, 40] +``` + +--- + +### `pop` +Removes and returns an item from the list. If the given index is present in +the list, the item at that index will be removed and the list will become one +element shorter. + +```tomo +func pop(list: &[T], index: Int = -1 -> T?) +``` + +- `list`: The list to remove an item from. +- `index`: The index from which to remove the item (default: the last item). + +**Returns:** +`none` if the list is empty or the given index does not exist in the list, +otherwise the item at the given index. + +**Example:** +```tomo +>> list := [10, 20, 30, 40] + +>> list.pop() += 40 +>> list += &[10, 20, 30] + +>> list.pop(index=2) += 20 +>> list += &[10, 30] +``` + +--- + +### `random` +Selects a random element from the list. + +```tomo +func random(list: [T], random: func(min,max:Int64->Int64)? = none -> T) +``` + +- `list`: The list from which to select a random element. +- `random`: If provided, this function will be used to get a random index in the list. Returned + values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number + generation) + +**Returns:** +A random element from the list. + +**Example:** +```tomo +>> [10, 20, 30].random() += 20 +``` + +--- + +### `remove_at` +Removes elements from the list starting at a specified index. + +```tomo +func remove_at(list: @[T], at: Int = -1, count: Int = 1 -> Void) +``` + +- `list`: The mutable reference to the list. +- `at`: The index at which to start removing elements (default is `-1`, which means the end of the list). +- `count`: The number of elements to remove (default is `1`). + +**Returns:** +Nothing. + +**Example:** +```tomo +list := [10, 20, 30, 40, 50] +list.remove_at(2) +>> list += [10, 30, 40, 50] + +list.remove_at(2, count=2) +>> list += [10, 50] +``` + +--- + +### `remove_item` +Removes all occurrences of a specified item from the list. + +```tomo +func remove_item(list: @[T], item: T, max_count: Int = -1 -> Void) +``` + +- `list`: The mutable reference to the list. +- `item`: The item to be removed. +- `max_count`: The maximum number of occurrences to remove (default is `-1`, meaning all occurrences). + +**Returns:** +Nothing. + +**Example:** +```tomo +list := [10, 20, 10, 20, 30] +list.remove_item(10) +>> list += [20, 20, 30] + +list.remove_item(20, max_count=1) +>> list += [20, 30] +``` + +--- + +### `reversed` +Returns a reversed slice of the list. + +```tomo +func reversed(list: [T] -> [T]) +``` + +- `list`: The list to be reversed. + +**Returns:** +A slice of the list with elements in reverse order. + +**Example:** +```tomo +>> [10, 20, 30].reversed() += [30, 20, 10] +``` + +--- + +### `sample` +Selects a sample of elements from the list, optionally with weighted +probabilities. + +```tomo +func sample(list: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none -> [T]) +``` + +- `list`: The list to sample from. +- `count`: The number of elements to sample. +- `weights`: The probability weights for each element in the list. These + values do not need to add up to any particular number, they are relative + weights. If no weights are given, elements will be sampled with uniform + probability. +- `random`: If provided, this function will be used to get random values for + sampling the list. The provided function should return random numbers + between `0.0` (inclusive) and `1.0` (exclusive). (Used for deterministic + pseudorandom number generation) + +**Errors:** +Errors will be raised if any of the following conditions occurs: +- The given list has no elements and `count >= 1` +- `count < 0` (negative count) +- The number of weights provided doesn't match the length of the list. +- Any weight in the weights list is negative, infinite, or `NaN` +- The sum of the given weights is zero (zero probability for every element). + +**Returns:** +A list of sampled elements from the list. + +**Example:** +```tomo +>> [10, 20, 30].sample(2, weights=[90%, 5%, 5%]) += [10, 10] +``` + +--- + +### `shuffle` +Shuffles the elements of the list in place. + +```tomo +func shuffle(list: @[T], random: func(min,max:Int64->Int64)? = none -> Void) +``` + +- `list`: The mutable reference to the list to be shuffled. +- `random`: If provided, this function will be used to get a random index in the list. Returned + values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number + generation) + +**Returns:** +Nothing. + +**Example:** +```tomo +>> list.shuffle() +``` + +--- + +### `shuffled` +Creates a new list with elements shuffled. + +```tomo +func shuffled(list: [T], random: func(min,max:Int64->Int64)? = none -> [T]) +``` + +- `list`: The list to be shuffled. +- `random`: If provided, this function will be used to get a random index in the list. Returned + values must be between `min` and `max` (inclusive). (Used for deterministic pseudorandom number + generation) + +**Returns:** +A new list with shuffled elements. + +**Example:** +```tomo +>> [10, 20, 30, 40].shuffled() += [40, 10, 30, 20] +``` + +--- + +### `slice` +Returns a slice of the list spanning the given indices (inclusive). + +```tomo +func slice(list: [T], from: Int, to: Int -> [T]) +``` + +- `list`: The original list. +- `from`: The first index to include. +- `to`: The last index to include. + +**Returns:** +A new list spanning the given indices. Note: negative indices are counted from +the back of the list, so `-1` refers to the last element, `-2` the +second-to-last, and so on. + +**Example:** +```tomo +>> [10, 20, 30, 40, 50].slice(2, 4) += [20, 30, 40] + +>> [10, 20, 30, 40, 50].slice(-3, -2) += [30, 40] +``` + +--- + +### `sort` +Sorts the elements of the list in place in ascending order (small to large). + +```tomo +func sort(list: @[T], by=T.compare -> Void) +``` + +- `list`: The mutable reference to the list to be sorted. +- `by`: The comparison function used to determine order. If not specified, the + default comparison function for the item type will be used. + +**Returns:** +Nothing. + +**Example:** +```tomo +list := [40, 10, -30, 20] +list.sort() +>> list += [-30, 10, 20, 40] + +list.sort(func(a,b:&Int): a.abs() <> b.abs()) +>> list += [10, 20, -30, 40] +``` + +--- + +### `sorted` +Creates a new list with elements sorted. + +```tomo +sorted(list: [T], by=T.compare -> [T]) +``` + +- `list`: The list to be sorted. +- `by`: The comparison function used to determine order. If not specified, the + default comparison function for the item type will be used. + +**Returns:** +A new list with sorted elements. + +**Example:** +```tomo +>> [40, 10, -30, 20].sorted() += [-30, 10, 20, 40] + +>> [40, 10, -30, 20].sorted(func(a,b:&Int): a.abs() <> b.abs()) += [10, 20, -30, 40] +``` + +--- + +### `to` +Returns a slice of the list from the start of the original list up to a specified index (inclusive). + +```tomo +to(list: [T], last: Int -> [T]) +``` + +- `list`: The original list. +- `last`: The index up to which elements should be included. + +**Returns:** +A new list containing elements from the start up to the specified index. + +**Example:** +```tomo +>> [10, 20, 30, 40, 50].to(3) += [10, 20, 30] + +>> [10, 20, 30, 40, 50].to(-2) += [10, 20, 30, 40] +``` + +--- + +### `unique` +Returns a Set that contains the unique elements of the list. + +```tomo +unique(list: [T] -> {T}) +``` + +- `list`: The list to process. + +**Returns:** +A set containing only unique elements from the list. + +**Example:** +```tomo +>> [10, 20, 10, 10, 30].unique() += {10, 20, 30} +``` diff --git a/docs/metamethods.md b/docs/metamethods.md index 0983aff0..f47c8233 100644 --- a/docs/metamethods.md +++ b/docs/metamethods.md @@ -31,17 +31,17 @@ enums. At this time, metamethods may not be overridden. ## Generic Metamethods -Due to the presence of pointers, arrays, tables, and functions, there are +Due to the presence of pointers, lists, tables, and functions, there are potentially a very large number of metamethods that would be required if _every_ type had its own set of metamethods. To reduce the amount of generated code, Tomo uses generic metamethods, which are general-purpose functions that take an object pointer and a type info struct pointer that has metadata about the object's type. That metadata is added automatically at compile time and -used to perform the appropriate operations. As an example, every array follows +used to perform the appropriate operations. As an example, every list follows the same logic when performing comparisons, except that each item is compared -using the item's comparison function. Therefore, we can compile a single array -comparison function and reuse it for each type of array if we pass in some -metadata about how to compare the array's items. +using the item's comparison function. Therefore, we can compile a single list +comparison function and reuse it for each type of list if we pass in some +metadata about how to compare the list's items. When possible, we avoid calling metamethods (for example, doing fixed-sized integer comparisons does not require calling a function), but metamethods are diff --git a/docs/nums.md b/docs/nums.md index 36dedcac..34e513ef 100644 --- a/docs/nums.md +++ b/docs/nums.md @@ -27,7 +27,7 @@ differentiate between possibly-NaN values and definitely-not-NaN values. Tomo has a separate concept for expressing the lack of a defined value: optional types. Consequently, Tomo has merged these two concepts, so `NaN` is called `none` and has the type `Num?` or `Num32?`. In this way, it's no -different from optional integers or optional arrays. This means that if a +different from optional integers or optional lists. This means that if a variable has type `Num`, it is guaranteed to not hold a NaN value. This also means that operations which may produce NaN values have a result type of `Num?`. For example, division can take two non-NaN values and return a result diff --git a/docs/operators.md b/docs/operators.md index 548891e7..d76d625d 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -7,8 +7,8 @@ Tomo supports a number of operators, both infix and prefix: - `^`: exponentiation for integers and floating point numbers - `mod`: modulus for integers and floating point numbers - `mod1`: clock-style modulus, which is equivalent to `1 + ((x-1) mod y)`. This - is particularly useful for doing wraparound behavior on 1-indexed arrays. -- `++`: concatenation (for text and arrays) + is particularly useful for doing wraparound behavior on 1-indexed lists. +- `++`: concatenation (for text and lists) - `<<`, `>>`: bitwise left shift and right shift for integers - `<<<`, `>>>`: unsigned bitwise left shift and right shift for integers - `_min_`/`_max_`: minimum and maximum (see below) @@ -33,7 +33,7 @@ value to the user. = 1[32] ``` -It's particularly handy for using the array `sort()` method, which takes a +It's particularly handy for using the list `sort()` method, which takes a function that returns a signed integer: ```tomo @@ -153,16 +153,16 @@ struct Person(name:Text, age:Int) >> Person("Alice", 33) _max_.age Person("Bob", 20) = Person(name="Alice", age=33) -// Get the longest of two arrays: +// Get the longest of two lists: >> [10, 20, 30, 40] _max_.length [99, 1] = [10, 20, 30, 40] -// Get the array with the highest value in the last position: +// Get the list with the highest value in the last position: >> [10, 20, 999] _max_[-1] [99, 1] = [10, 20, 999] ``` -The keyed comparison can chain together multiple field accesses, array index +The keyed comparison can chain together multiple field accesses, list index operations, method calls, etc. If you wanted to, for example, get the item whose `x` field has the highest absolute value, you could use `_max_.x.abs()`. diff --git a/docs/optionals.md b/docs/optionals.md index 7b100dc7..48e48875 100644 --- a/docs/optionals.md +++ b/docs/optionals.md @@ -32,7 +32,7 @@ conventions and which would generate a lot of unnecessary code. ## Syntax Optional types are written using a `?` after the type name. So, an optional -integer would be written as `Int?` and an optional array of texts would be +integer would be written as `Int?` and an optional list of texts would be written as `[Text]?`. None can be written explicitly using `none` with a type annotation. For @@ -43,7 +43,7 @@ value or `none` and initialize it as none, you would write it as: x : Int = none ``` -Similarly, if you wanted to declare a variable that could be an array of texts +Similarly, if you wanted to declare a variable that could be a list of texts or none and initialize it as none, you would write: ```tomo @@ -127,10 +127,10 @@ for line in lines: ## Implementation Notes The implementation of optional types is highly efficient and has no memory -overhead for pointers, collection types (arrays, sets, tables), booleans, +overhead for pointers, collection types (lists, sets, tables), booleans, texts, enums, nums, or integers (`Int` type only). This is done by using carefully chosen values, such as `0` for pointers, `2` for booleans, or a -negative length for arrays. However, for fixed-size integers (`Int64`, `Int32`, +negative length for lists. However, for fixed-size integers (`Int64`, `Int32`, `Int16`, and `Int8`), bytes, and structs, an additional byte is required for out-of-band information about whether the value is none or not. diff --git a/docs/paths.md b/docs/paths.md index af179ede..cde7f74d 100644 --- a/docs/paths.md +++ b/docs/paths.md @@ -452,13 +452,13 @@ A list of file paths. --- ### `from_components` -Returns a path built from an array of path components. +Returns a path built from a list of path components. ```tomo func from_components(components: [Text] -> Path) ``` -- `components`: An array of path components. +- `components`: A list of path components. **Returns:** A path representing the given components. @@ -476,7 +476,7 @@ A path representing the given components. --- ### `glob` -Perform a globbing operation and return an array of matching paths. Some glob +Perform a globbing operation and return a list of matching paths. Some glob specific details: - The paths "." and ".." are *not* included in any globbing results. @@ -510,7 +510,7 @@ A list of file paths that match the glob. >> (./.*).glob() = [(./.hidden)] -# Globs with no matches return an empty array: +# Globs with no matches return an empty list: >> (./*.xxx).glob() = [] ``` @@ -915,7 +915,7 @@ func write(path: Path, bytes: [Byte], permissions=0o644[32] -> Void) ``` - `path`: The path of the file to write to. -- `bytes`: An array of bytes to write to the file. +- `bytes`: A list of bytes to write to the file. - `permissions`: The permissions to set on the file if it is created (default is `0o644`). **Returns:** diff --git a/docs/pointers.md b/docs/pointers.md index 36cab2c1..0644a6ab 100644 --- a/docs/pointers.md +++ b/docs/pointers.md @@ -13,8 +13,8 @@ replace the value that previously resided there. ```tomo func no_mutation_possible(nums:[Int]): - nums[1] = 10 // This performs a copy-on-write and creates a new array - // The new array is only accessible as a local variable here + nums[1] = 10 // This performs a copy-on-write and creates a new list + // The new list is only accessible as a local variable here ... my_nums := [0, 1, 2] no_mutation_possible(my_nums) @@ -93,17 +93,17 @@ else: For convenience, most operations that work on values can work with pointers to values implicitly. For example, if you have a struct type with a `.foo` field, you can use `ptr.foo` on a pointer to that struct type as well, without needing -to use `ptr[].foo`. The same is true for array accesses like `ptr[i]` and method +to use `ptr[].foo`. The same is true for list accesses like `ptr[i]` and method calls like `ptr.reversed()`. # Read-Only Views -In a small number of API methods (`array.first()`, `array.binary_search()`, -`array.sort()`, `array.sorted()`, and `array.heapify()`), the methods allow you +In a small number of API methods (`list.first()`, `list.binary_search()`, +`list.sort()`, `list.sorted()`, and `list.heapify()`), the methods allow you to provide custom comparison functions. However, for safety, we don't actually want the comparison methods to be able mutate the values inside of immutable -array values. For implementation reasons, we can't pass the values themselves -to the comparison functions, but need to pass pointers to the array members. +list values. For implementation reasons, we can't pass the values themselves +to the comparison functions, but need to pass pointers to the list members. So, to work around this, Tomo allows you to define functions that take immutable view pointers as arguments. These behave similarly to `@` pointers, but their type signature uses `&` instead of `@` and read-only view pointers diff --git a/docs/reductions.md b/docs/reductions.md index 3cbb6d5d..abd612b0 100644 --- a/docs/reductions.md +++ b/docs/reductions.md @@ -102,7 +102,7 @@ nums := [1, 2, -3] ## Comprehensions -Reductions work not only with iterable values (arrays, sets, integers, etc.), +Reductions work not only with iterable values (lists, sets, integers, etc.), but also with comprehensions. You can use comprehensions to perform reductions while filtering out values or while applying a transformation: diff --git a/docs/serialization.md b/docs/serialization.md index d63cb168..a938e316 100644 --- a/docs/serialization.md +++ b/docs/serialization.md @@ -11,7 +11,7 @@ original value. ## Serializing To serialize data, simply call the method `.serialized()` on any value and it -will return an array of bytes that encode the value's data: +will return a list of bytes that encode the value's data: ```tomo value := Int64(5) @@ -19,7 +19,7 @@ value := Int64(5) = [0x0A] : [Byte] ``` -Serialization produces a fairly compact representation of data as a flat array +Serialization produces a fairly compact representation of data as a flat list of bytes. In this case, a 64-bit integer can be represented in a single byte because it's a small number. @@ -72,4 +72,4 @@ only 9 bytes for the whole thing! Unfortunately, not all types can be easily serialized. In particular, types and functions cannot be serialized because their data contents cannot be easily -converted to portable byte arrays. All other datatypes _can_ be serialized. +converted to portable byte lists. All other datatypes _can_ be serialized. diff --git a/docs/sets.md b/docs/sets.md index 88e47e98..1aaba59d 100644 --- a/docs/sets.md +++ b/docs/sets.md @@ -28,7 +28,7 @@ For type annotations, a set that holds items with type `T` is written as `|T|`. ### Comprehensions -Similar to arrays, sets can use comprehensions: +Similar to lists, sets can use comprehensions: ```tomo set := |10*i for i in 10| @@ -38,7 +38,7 @@ set3 := |-10, 10*i for i in 10| ## Accessing Items -Sets internally store their items in an array, which you can access with the +Sets internally store their items in a list, which you can access with the `.items` field. This is a constant-time operation that produces an immutable view: @@ -115,7 +115,7 @@ func add_all(set:@|T|, items: [T] -> Void) ``` - `set`: The mutable reference to the set. -- `items`: The array of items to add to the set. +- `items`: The list of items to add to the set. **Returns:** Nothing. @@ -257,7 +257,7 @@ func remove_all(set:@|T|, items: [T] -> Void) ``` - `set`: The mutable reference to the set. -- `items`: The array of items to remove from the set. +- `items`: The list of items to remove from the set. **Returns:** Nothing. diff --git a/docs/tables.md b/docs/tables.md index 7a50c9dd..605a1486 100644 --- a/docs/tables.md +++ b/docs/tables.md @@ -2,7 +2,7 @@ Tables are Tomo's associative mapping structure, also known as a Dictionary or Map. Tables are efficiently implemented as a hash table that preserves -insertion order and has fast access to keys and values as array slices. Tables +insertion order and has fast access to keys and values as list slices. Tables support *all* types as both keys and values. ## Syntax @@ -25,7 +25,7 @@ For type annotations, a table that maps keys with type `K` to values of type ### Comprehensions -Similar to arrays, tables can use comprehensions to dynamically construct tables: +Similar to lists, tables can use comprehensions to dynamically construct tables: ```tomo t := {i=10*i for i in 10} @@ -123,7 +123,7 @@ Table length can be accessed by the `.length` field: ## Accessing Keys and Values -The keys and values of a table can be efficiently accessed as arrays using a +The keys and values of a table can be efficiently accessed as lists using a constant-time immutable slice of the internal data from the table: ```tomo diff --git a/docs/text.md b/docs/text.md index f0665762..d3063443 100644 --- a/docs/text.md +++ b/docs/text.md @@ -3,7 +3,7 @@ `Text` is Tomo's datatype to represent text. The name `Text` is used instead of "string" because Tomo text represents immutable, normalized unicode data with fast indexing that has an implementation that is efficient for concatenation. -These are _not_ C-style NUL-terminated character arrays. GNU libunistring is +These are _not_ C-style NUL-terminated character lists. GNU libunistring is used for full Unicode functionality (grapheme cluster counts, capitalization, etc.). @@ -12,8 +12,8 @@ etc.). Internally, Tomo text's implementation is based on [Raku/MoarVM's strings](https://docs.raku.org/language/unicode) and [Boehm et al's Cords](https://www.cs.tufts.edu/comp/150FP/archive/hans-boehm/ropes.pdf). -Strings store their grapheme cluster count and either a compact array of 8-bit -ASCII characters (for ASCII text), an array of 32-bit normal-form grapheme +Strings store their grapheme cluster count and either a compact list of 8-bit +ASCII characters (for ASCII text), a list of 32-bit normal-form grapheme cluster values (see below), or a (roughly) balanced binary tree concatenation of two texts. The upside is that repeated concatenations are typically a constant-time operation, which will occasionally require a small rebalancing @@ -33,7 +33,7 @@ non-ASCII text is stored as 32-bit normal-form graphemes. A normal-form grapheme is either a positive value representing a Unicode codepoint that corresponds to a grapheme cluster (most Unicode letters used in natural language fall into this category after normalization) or a negative value -representing an index into an internal array of "synthetic grapheme cluster +representing an index into an internal list of "synthetic grapheme cluster codepoints." Here are some examples: - `A` is a normal codepoint that is also a grapheme cluster, so it would @@ -223,7 +223,7 @@ shorthand for `${}"foo"`. Singly quoted text with no dollar sign (e.g. Concatenation in the typical case is a fast operation: `"{x}{y}"` or `x ++ y`. Because text concatenation is typically fast, there is no need for a separate -"string builder" class in the language and no need to use an array of text +"string builder" class in the language and no need to use a list of text fragments. ### Text Length @@ -433,7 +433,7 @@ for chunk in text.by_split_any(",;"): --- ### `bytes` -Converts a `Text` value to an array of bytes representing a UTF8 encoding of +Converts a `Text` value to a list of bytes representing a UTF8 encoding of the text. ```tomo @@ -443,7 +443,7 @@ func bytes(text: Text -> [Byte]) - `text`: The text to be converted to UTF8 bytes. **Returns:** -An array of bytes (`[Byte]`) representing the text in UTF8 encoding. +A list of bytes (`[Byte]`) representing the text in UTF8 encoding. **Example:** ```tomo @@ -481,7 +481,7 @@ func caseless_equals(a: Text, b:Text, language:Text = "C" -> Bool) --- ### `codepoint_names` -Returns an array of the names of each codepoint in the text. +Returns a list of the names of each codepoint in the text. ```tomo func codepoint_names(text: Text -> [Text]) @@ -490,7 +490,7 @@ func codepoint_names(text: Text -> [Text]) - `text`: The text from which to extract codepoint names. **Returns:** -An array of codepoint names (`[Text]`). +A list of codepoint names (`[Text]`). **Example:** ```tomo @@ -664,14 +664,14 @@ func has(text: Text, target: Text -> Bool) --- ### `join` -Joins an array of text pieces with a specified glue. +Joins a list of text pieces with a specified glue. ```tomo func join(glue: Text, pieces: [Text] -> Text) ``` - `glue`: The text used to join the pieces. -- `pieces`: The array of text pieces to be joined. +- `pieces`: The list of text pieces to be joined. **Returns:** A single `Text` value with the pieces joined by the glue. @@ -739,7 +739,7 @@ exact desired length. --- ### `lines` -Splits the text into an array of lines of text, preserving blank lines, +Splits the text into a list of lines of text, preserving blank lines, ignoring trailing newlines, and handling `\r\n` the same as `\n`. ```tomo @@ -749,7 +749,7 @@ func lines(text: Text -> [Text]) - `text`: The text to be split into lines. **Returns:** -An array of substrings resulting from the split. +A list of substrings resulting from the split. **Example:** ```tomo @@ -935,7 +935,7 @@ the text. --- ### `split` -Splits the text into an array of substrings based on exact matches of a delimiter. +Splits the text into a list of substrings based on exact matches of a delimiter. **Note:** to split based on a set of delimiter characters, use [`split_any()`](#split_any). ```tomo @@ -947,7 +947,7 @@ func split(text: Text, delimiter: Text = "" -> [Text]) empty text, the text will be split into individual grapheme clusters. **Returns:** -An array of subtexts resulting from the split. +A list of subtexts resulting from the split. **Example:** ```tomo @@ -961,7 +961,7 @@ An array of subtexts resulting from the split. --- ### `split_any` -Splits the text into an array of substrings at one or more occurrences of a set +Splits the text into a list of substrings at one or more occurrences of a set of delimiter characters (grapheme clusters). **Note:** to split based on an exact delimiter, use [`split()`](#split). @@ -974,7 +974,7 @@ func split_any(text: Text, delimiters: Text = " $\t\r\n" -> [Text]) splitting the text into chunks. **Returns:** -An array of subtexts resulting from the split. +A list of subtexts resulting from the split. **Example:** ```tomo @@ -1144,7 +1144,7 @@ The uppercase version of the text. --- ### `utf32_codepoints` -Returns an array of Unicode code points for UTF32 encoding of the text. +Returns a list of Unicode code points for UTF32 encoding of the text. ```tomo func utf32_codepoints(text: Text -> [Int32]) @@ -1153,7 +1153,7 @@ func utf32_codepoints(text: Text -> [Int32]) - `text`: The text from which to extract Unicode code points. **Returns:** -An array of 32-bit integer Unicode code points (`[Int32]`). +A list of 32-bit integer Unicode code points (`[Int32]`). **Example:** ```tomo |
