Array
In the prelude: every iyi program has it, with no import.
Array is a compiler-known generic class, like String: an array literal is lowered to Array(T).unsafe_build and to_unsafe before any of this is read (LiteralExpander#expand), so those two names are not ours to choose.
A buffer, a size and a capacity. Growth doubles, which is the only policy that keeps a run of << linear, and there is no shrinking; shift steps the buffer forward, so the capacity is what is left past it.
Array(T)class
class Array(T)
# The elements in both, each once, in this array's order. Taking a value
# out of the table as it is kept is what keeps it once.
def &(other : Array(T)) : Array(T)
# This array *times* times over, one after the other: `[1, 2] * 2` is
# `[1, 2, 1, 2]`, and zero times is empty.
def *(times : Int32) : Array(T)
# The two in order, as a new array; neither operand changes.
def +(other : Array(T)) : Array(T)
# The elements not in *other*, duplicates and order kept: `[1, 1, 2, 3] -
# [2]` is `[1, 1, 3]`. Equality is `==`, asked of a table when *other* is
# long enough for a search of it per element to cost more than building
# one, as the other library decides at sixteen.
def -(other : Array(T)) : Array(T)
def <<(value : T) : Array(T)
def ==(other : Array(T)) : Bool
# `a[1, 2]`: *count* elements from *start*, or as many as there are. A
# start that wraps below zero or lies past the end is refused, as an index
# is; a start of exactly `size` is the empty tail, `[]`, as in the other
# library, because the position after the last is where an append goes.
def [](start : Int32, count : Int32) : Array(T)
# A negative index counts from the end, so `a[-1]` is the last element.
# Still out of range after that wrap (or empty) raises, same message.
def [](index : Int32) : T
# `a[1..2]`, `a[1...-1]`, `a[2..]`, `a[..1]`: either end may be negative,
# and an end past the last element stops at it. The start is held to what
# `[](start, count)` holds it to, so `[1, 2, 3][3..]` is `[]` and
# `[1, 2, 3][4..]` is refused: a range that begins outside the array asks
# about elements that are not there.
def [](range : Range(B, E)) : Array(T) forall B, E
def []=(start : Int32, count : Int32, values : Array(T)) : Array(T)
# `a[1, 2] = x` puts one element where the span was, and `a[1, 2] = [x,
# y, z]` puts all of them there: the array grows or shrinks by the
# difference, as the other library's does. A start of `size` appends.
def []=(start : Int32, count : Int32, value : T) : T
def []=(index : Int32, value : T) : T
def []=(range : Range(B, E), values : Array(T)) : Array(T) forall B, E
def []=(range : Range(B, E), value : T) : T forall B, E
# The same span, or nil where `[]` would refuse the start. A negative
# count is a mistake in the call rather than an answer about this array,
# and is refused by both.
def []?(start : Int32, count : Int32) : Array(T) | ::Nil
def []?(index : Int32) : T | ::Nil
def []?(range : Range(B, E)) : Array(T) | ::Nil forall B, E
def all?(& : (T -> Bool)) : Bool
def any?(& : (T -> Bool)) : Bool
# The elements that are not nil, in an array whose type says so: what a
# list of `to_i?` answers becomes once the failures are dropped.
def compact
# `a.concat(a)` is a doubling, not a loop: `each` walked *other*'s growing
# size while `<<` grew it, so `[1, 2].concat(itself)` grew the buffer until
# the capacity arithmetic overflowed. The length is read once.
def concat(other : Array(T)) : Array(T)
# The questions a list gets asked with one answer. `find` answers nil
# when none does, the way Crystal's does and `index` below already did.
def count(& : (T -> Bool)) : Int32
# Every element equal to `value` out, answering the last or nil, as the
# other library's: it took the first alone, `[1, 2, 1]` left `[2, 1]`.
def delete(value : T) : T | ::Nil
def dup : Array(T)
def each(& : (T -> Nil)) : Nil
def each_with_index(& : (T, Int32 -> Nil)) : Nil
def empty? : Bool
def find(& : (T -> Bool)) : T | ::Nil
# The first `n`, or all of them when there are fewer: a prefix, not a
# question, so it never panics.
def first(n : Int32) : Array(T)
# `first` on an empty array is a mistake rather than a nil: the array knows
# it is empty and so does whoever wrote the call. `last?` above answers the
# other question, and is the one the trait defaults use.
def first : T
# And the nilable question beside it, as `last?` is beside `last`: without
# an import a program had `last?` and not `first?`.
def first? : T | ::Nil
# Every element that is not an array, from every array nested inside at
# any depth, in the order they are written: `[[1, [2]], 3]` is `[1, 2,
# 3]`. The element type is computed the same way, through every level, so
# `[[1], [[2]]].flatten` is an `Array(Int32)`.
def flatten
# Hashing follows `==`, mixed as a tuple's members are (object.iyi): an
# array writes `==`, so `Reference#hash` answered its type's id and every
# array key shared one slot - 40,000 of them took 1.2 s to insert.
def hash : Int32
def includes?(value : T) : Bool
# Where a value first is, or nil.
def index(value : T) : Int32 | ::Nil
def inspect : String
def join(separator : String) : String
# Here rather than on `Enumerable`: the trait is iyi's, written in
# `src/std`, and every one of its methods that returns a sequence
# returns one of these. So an `Array` is what a sample ends up holding, and
# `join` is what it ends up calling.
# With nothing between: `["a", "b"].join` is `"ab"`.
def join : String
# The last `n`, in their order, or all of them when there are fewer.
def last(n : Int32) : Array(T)
# `last` on an empty array is a mistake rather than a nil, as `first` is.
def last : T
def last? : T | ::Nil
def map(& : (T -> U)) : Array(U) forall U
# The element whose key is largest, or smallest, the first of equals; a
# NaN key is above every other, as `<=>` places it. Empty is a mistake.
def max_by(& : (T -> U)) : T forall U
def min_by(& : (T -> U)) : T forall U
def new(capacity : Int32)
def new
# Removes and returns the last element. Empty is a mistake, as with `last`.
def pop : T
# The last element or nil, the stack's twin of `shift?`: `while item =
# stack.pop?` empties it from the top.
def pop? : T | ::Nil
# `<<` by the other library's name, and with several values at once: the
# room for all of them is made in one step.
def push(value : T) : Array(T)
def push(*values : T) : Array(T)
def reduce(initial : U, & : (U, T -> U)) : U forall U
def reject(& : (T -> Bool)) : Array(T)
# A copy in the other order.
def reversed : Array(T)
def select(& : (T -> Bool)) : Array(T)
# Removes and returns the first element. The buffer steps past it rather
# than the rest moving down: moving them made draining a queue quadratic,
# and `while item = queue.shift?` - how the prelude says a queue is
# drained - took 1.6 s over 100,000 items. The slot stepped past is still
# the buffer's chunk, which the collector keeps by the pointer into it,
# and the next growth copies only what is left.
def shift : T
# The first element or nil, which is how a queue is drained: `while
# item = queue.shift?`.
def shift? : T | ::Nil
def size : Int32
def sort_in_place(& : (T, T -> Int32)) : self
# The pair for elements that already compare, by `<`. SPEC.md III.1.7a
# pairs `sort_in_place` with `sorted` and the hint for `sort!` names both,
# but only `sorted` had this form: "expected to be invoked with a block".
def sort_in_place : self
# Sorted on a key each element answers, ascending by `<`: the same pair
# as above, one name longer. Each element's key is computed once, and the
# pairs of key and element are what the merge sort orders.
def sort_in_place_by(& : (T -> U)) : self forall U
# The copy, with the comparison written out. One body, because the difference
# between the pair is which array it runs on. The block is handed on through
# `yield`, not captured: a captured block is kept to call later, and `!` in
# it was refused as "maybe by a task" although the sort runs it here.
def sorted(& : (T, T -> Int32)) : Array(T)
def sorted : Array(T)
def sorted_by(& : (T -> U)) : Array(T) forall U
# The sum of the elements, and `T.zero` (src/iyi/number.iyi) when there
# are none, as `std/enumerable`'s `sum` answers: this one raised "sum of
# an empty array" where `Set(Int32).new.sum` was 0. `["a", "b"].sum` is a
# compile error, as in the other library; `join` puts strings together.
def sum : T
# The sum of what the block answers for each element, and the block's
# own zero when there are none.
def sum(& : (T -> U)) : U forall U
# `[1, 2, 3]`, the way it is written: an array printed is its elements
# inspected, so a string inside keeps its quotes and a nil says so. Cast,
# or `Tuple.new.to_a`'s `Array(NoReturn)` "can't infer block return type".
def to_s : String
# The same values as a `Set`, in the order first met.
def to_set : Set(T)
def to_unsafe : Pointer(T)
# Rows into columns: `[[1, 2], [3, 4]].transpose` is `[[1, 3], [2, 4]]`.
# Rows of different lengths have no columns to give, and are refused with
# the first row that differs.
def transpose
# Keeps only the span, in place, and answers this array: `truncate(1, 2)`
# leaves the second and third, refusing a start `[](start, count)` refuses.
def truncate(start : Int32, count : Int32) : Array(T)
def truncate(range : Range(B, E)) : Array(T) forall B, E
# Each value once, in the order first met, found through a table: a
# search of what was kept so far made it quadratic, 284 ms for 200,000
# elements with 10,000 distinct.
def uniq : Array(T)
# Each element whose key is the first of its kind, in the order met: the
# key is the block's, so `words.uniq(&.downcase)` keeps one spelling.
def uniq(& : (T -> U)) : Array(T) forall U
# :nodoc:
def unsafe_set_size(size : Int32) : Nil
# In front, in the order written: `[3].unshift(1, 2)` is `[1, 2, 3]`.
# Every element moves up to make the room, so a queue that grows at the
# front is quadratic where one that grows at the back and drains with
# `shift` is not.
def unshift(value : T) : Array(T)
def unshift(*values : T) : Array(T)
# Pairs, element by element, as many as this array holds: a shorter
# *other* is refused at its first missing index, as the other library does.
def zip(other : Array(U)) : Array(Tuple(T, U)) forall U
# The same pairs handed to a block, with no array of them made: the
# other library's `a.zip(b) { |x, y| ... }`, which a training loop calls
# a few million times, and which refuses a shorter *other* the same way.
def zip(other : Array(U), & : (T, U -> _)) : Nil forall U
# The elements in either, each once, this array's first: `[1, 2] | [2,
# "a"]` is `[1, 2, "a"]`, whose element type is both arrays'.
def |(other : Array(U)) : Array(T | U) forall U
# `Array.new(729, 0)`: *size* copies of *value*, the other library's fill
# form. A second argument used to be an arity error beside a one-argument
# form that means capacity, which a port reads as the fill it was.
def self.new(size : Int32, value : T) : Array(T)
# `Array(T).new(n) { |i| ... }`: n elements, each the block's answer for
# its index - a row of a grid, a table of squares.
def self.new(size : Int32, & : (Int32 -> T)) : Array(T)
# An array of *capacity* elements that are not written yet. The literal
# expander fills them through `to_unsafe` immediately afterwards, which is
# why this is allowed to hand out a size it has not stored anything at.
def self.unsafe_build(capacity : Int32) : Array(T)
end
The one sequence — SPEC.md, "0.1.0", item 3.