runtime library
Ghul.Runtime ships alongside the compiler and supplies Pipe[T] and other everyday building blocks used throughout this site. The reference below covers Ghul.Pipes, the sequence-processing library behind filter, map, reduce and the thread-first operator.
Each entry is a real, compiled declaration checked against the current ghul.runtime package - hover over a name for its full signature, exactly as an editor would show it.
A pipe combinator chain can be written with the thread-first operator |> over free functions, or fluently with . over Pipe[T] methods after wrapping a source with | or pipe(). Both forms call the same underlying code:
sum of even squares: 220
or, fluently:
sum of even squares: 220
how a pipe runs
The combinators come in two kinds. A stage returns a new Pipe[T], which is what lets stages chain: map returns a pipe that maps, filter returns a pipe that filters. A terminal returns something else - a value, a list, a count - so it is where a pipe ends.
Elements travel through a pipe one at a time, and the terminal is what pulls them through. It asks the pipe it was called on for an element, that pipe asks the one it was built from, and so on back to the iterable at the start; the element then makes its way down the pipe, each stage working on it before passing it on to the next stage. No stage buffers the whole sequence - typical stages hold only one element at a time - so a map over a million elements doesn't construct a million-element list.
Pipes are lazy: until something - a terminal - asks a pipe for elements, no stage runs. An inert pipe can be held or passed around until it's needed. And if the consumer stops pulling elements from the pipe, the pipe will stop pulling elements from its source iterator. If and when the consumer starts up again, the pipe will begin producing elements again, pulling them through its chain of stages from the source.
Because pipes are lazy, they can consume a source with an infinite number of elements. The consumer can stop pulling, and discard the pipe. When the pipe is disposed, that disposal flows back up the pipe to the source iterator, which is then also disposed.
One way to bound consumption is to use a stage like take(...), which stops pulling after a given number of elements have passed through it.
This combines neatly with infinite generators - a generator can yield indefinitely, leaving it to the pipe downstream to decide when to stop consuming.
reverse and the sort family are the exceptions, listed separately below: they need to see the whole sequence of elements before they can start producing results, and so they buffer the whole source as soon as they are called.
reading the signatures
The pure on a function type - predicate: (T) -> bool pure - asks that the function you pass only reads, and writes nothing to the heap. Most anonymous functions satisfy it without any thought; see type narrowing for what the compiler does with the guarantee.
Ghul.MAYBE[T] is an optional type: it holds a T or holds nothing. Combinators that might not find anything say so in their return type, and ??, ! and if let read the value out.
making a pipe
pipe
Turns any Iterable[T] - an array, a LIST[T], a MAP[T]'s values, anything with an .iterator - into a Pipe[T]. This is what the | operator calls to wrap its left operand.
stages
A stage returns a new pipe, so stages chain onto one another.
filter
or, as a method:
map
or, as a method:
flat_map
Maps each element to an iterable and runs the results together into one sequence.
or, as a method:
skip
or, as a method:
take
or, as a method:
skip_while
or, as a method:
take_while
or, as a method:
The four set operations that follow all discard duplicates. This is what they do to the same pair of sources:
distinct
Removes duplicates, keeping the first occurrence of each element. distinct, union_with, intersect_with and except all do this, so each produces a sequence with no repeats, in the order first seen. Elements are compared with =~ and get_hash_code, so a type used with these needs both.
or, as a method:
union_with
Every element of both sources with duplicates removed, taking the left source's elements first.
or, as a method:
intersect_with
Elements the left and right sources have in common, in the order the left source has them.
or, as a method:
except
Elements of the left source that the right source doesn't have.
or, as a method:
peek
Calls action on each element and passes it through unchanged.
or, as a method:
chunk and windows both produce groups of elements, and differ in how the groups are cut:
chunk
The first size elements, then the next size, and so on, each element appearing in one group only. The last group is short when the source doesn't divide evenly. Compare windows, below.
or, as a method:
windows
Every run of size neighbouring elements: the first size, then the same run moved along by one, and so on. Each window therefore shares all but one of its elements with the window before it. A window is always size long, so a source with fewer than size elements produces none.
or, as a method:
cat
Concatenation: every element of the left source, then every element of the right.
or, as a method:
index
Pairs each element with its index. INDEXED_VALUE[T] has index and value, and destructures positionally, so for (i, x) in xs | .index() do reads the pair apart. The second form starts the index at a given number rather than at 0.
or, as a method:
zip
Pairs elements of the source with elements of other, stopping when either side runs out. The second form combines each pair with a mapper instead of yielding a tuple.
or, as a method:
stages that buffer
These return a pipe, like any other stage, but they cannot work out their first element without having seen the last one. So they buffer the whole source the moment they are called, rather than passing elements along one at a time.
reverse
Yields the source's elements last to first.
or, as a method:
sort
Yields the source's elements in order. The first form uses the element type's own ordering; the other two take an IComparer[T] or a comparison function returning negative, zero or positive.
or, as a method:
sort_descending
or, as a method:
sort_by
or, as a method:
sort_by_descending
or, as a method:
terminals
A terminal returns something other than a pipe, so it is where a pipe ends. They fall into three loose groups: finding a single element, collecting the elements into a container, and folding or consuming the sequence as a whole.
The searching combinators come in pairs. find-style ones take a predicate or a mapper and scan; first-style ones look only at the leading element. Each has a variant returning MAYBE[T] and one that throws instead:
find
The first element matching the predicate, absent if none does. first is the same question with no predicate.
or, as a method:
find_map
Calls mapper on each element in turn and returns the first present result. first_map differs: it calls the mapper on the first element only, and gives up if that one declines.
or, as a method:
find_or_throw
As find, throwing instead of returning absent when nothing matches.
or, as a method:
find_map_or_throw
As find_map, throwing instead of returning absent when nothing maps.
or, as a method:
first
The leading element, absent when the source is empty.
or, as a method:
first_map
Calls mapper on the leading element only. Compare find_map, above, which keeps going.
or, as a method:
first_or_throw
As first, throwing instead of returning absent when the source is empty.
or, as a method:
first_map_or_throw
As first_map, throwing instead of returning absent.
or, as a method:
only
The single element the source holds, throwing when it holds none or more than one.
or, as a method:
any
or, as a method:
all
or, as a method:
count
or, as a method:
min
The smallest element, absent when the source is empty. min and max have no method form.
max
min_by
or, as a method:
max_by
or, as a method:
The collecting combinators differ in what they hand back:
collect
Collects into the read-only Collections.List[T]. collect_list gives back the mutable LIST[T] instead, and the others collect into an array, a set, or a map.
or, as a method:
collect_array
or, as a method:
collect_list
or, as a method:
collect_set
or, as a method:
collect_map
or, as a method:
partition
Splits the source in two on a predicate. The elements matching the predicate come first, then the elements not matching.
or, as a method:
group_by
Collects the elements into a map, keyed by what key_selector returns for each.
or, as a method:
reduce
Folds the source into a single value, starting at seed and calling accumulator with the running value and each element in turn. The second form passes the final running value through a mapper before returning it.
or, as a method:
each
Calls action on every element. It returns nothing and, alone among these, is not pure - it exists for its side effects.
or, as a method:
append_to
Appends each element to a StringBuilder, separated by separator, or by ", " when that is left off. join is the same thing answering a fresh string.
or, as a method:
join
Renders the elements into one string, separated by separator, or by ", " when left off.
or, as a method: