essenceDocumentation
essenceDocs
on this pageAt a glance
Coming from JavaScript?

loop and Step

function family

The loop functions and Step: repeat a function while a condition holds, across a range of integers, or until it returns #Done.

Answers the State the loop settles on, or the answer a step stops with.

A startingWith value seeds the State and a body advances it. The entries differ in what ends the loop. The while and until entries check a predicate before each step. The counted entries run over a range of Integers. A positional body runs the loop to its end. A step body answers with a Step and can stop the loop early. Pick the entry by the labels it reads, as with an overloaded Method.

Taught on: Iteration.

At a glance

Declared
overload function loop

9 entries. A call picks one by the labels it writes.

Modes
  • StepWhat a callback answers to say whether a loop goes round again.

Predicate loops

while

loop<infer State>(
	startingWith state: State,
	while condition: (_: State) -> Boolean,
	_ advance: (_: State) -> State,
) -> State

Answers the first State the condition rejects.

The condition is checked before each step. A condition that is false on the seed answers the seed unchanged, and the loop runs zero times. This entry is one of the family's two natives.

NameTypeDescription
startingWithStatethe State the loop begins from.
while(_: State) -> Booleanthe condition, checked against the State before each step; the loop continues while it is true.
_(_: State) -> Statethe body, handed the running State and answering with the next.

the first State the condition rejects.

Example
constant doubled = loop(
	startingWith 1,
	while (n) { <- n::isLessThan(100) },
	(n) { <- n::multiply(with 2) },
)

expect doubled::is(128)

constant unchanged = loop(
	startingWith 500,
	while (n) { <- n::isLessThan(100) },
	(n) { <- n::multiply(with 2) },
)

expect unchanged::is(500)

until

loop<infer State>(
	startingWith state: State,
	until condition: (_: State) -> Boolean,
	_ advance: (_: State) -> State,
) -> State

Answers the first State the condition accepts.

The condition is checked before each step. A condition already true on the seed answers the seed unchanged, and the loop runs zero times.

NameTypeDescription
startingWithStatethe State the loop begins from.
until(_: State) -> Booleanthe condition, checked against the State before each step; the loop continues while it is false.
_(_: State) -> Statethe body, handed the running State and answering with the next.

the first State the condition accepts.

Example
constant tripled = loop(
	startingWith 1,
	until (n) { <- n::isGreaterThan(100) },
	(n) { <- n::multiply(with 3) },
)

expect tripled::is(243)

Counted loops

through

loop<infer State>(
	from start: Integer,
	through end: Integer,
	startingWith state: State,
	_ advance: (_: Integer, _: State) -> State,
) -> State

Runs the body once for each Integer from from up through through, and answers the State after the last step.

Both endpoints are included and the count only runs up. An end below the start runs the body zero times and answers the seed untouched. That mirrors List.of(integersFrom:through:). To count down, use the downTo: entry. The entry always finishes, because the range is fixed before the first step.

NameTypeDescription
fromIntegerthe first Integer the body sees.
throughIntegerthe last Integer the body sees, included.
startingWithStatethe State the first step builds on.
_(_: Integer, _: State) -> Statethe body, handed each Integer and the running State, answering with the next State.

the State after the last step, or the seed when the body never runs.

Example
constant total = loop(from 1, through 10, startingWith 0, (index, sum) {
	<- sum::add(index)
})

expect total::is(55)

constant backwards = loop(from 3, through 1, startingWith "none", (index, text) {
	<- "{text}{index}"
})

expect backwards::is("none")

upTo

loop<infer State>(
	from start: Integer,
	upTo end: Integer,
	startingWith state: State,
	_ advance: (_: Integer, _: State) -> State,
) -> State

Runs the body once for each Integer from from up to, but not including, upTo, and answers the State after the last step.

The count only runs up. The body runs zero times when upTo is not above from, and the seed is answered untouched. That mirrors List.of(integersFrom:upTo:), so a count written from a length is empty rather than inverted.

NameTypeDescription
fromIntegerthe first Integer the body sees.
upToIntegerthe Integer the count stops before.
startingWithStatethe State the first step builds on.
_(_: Integer, _: State) -> Statethe body, handed each Integer and the running State, answering with the next State.

the State after the last step, or the seed when the body never runs.

Example
constant digits = loop(from 1, upTo 4, startingWith "", (index, text) {
	<- "{text}{index}"
})

expect digits::is("123")

downTo

loop<infer State>(
	from start: Integer,
	downTo end: Integer,
	startingWith state: State,
	_ advance: (_: Integer, _: State) -> State,
) -> State

Runs the body once for each Integer from from down through downTo, and answers the State after the last step.

Both endpoints are included and the count only runs down. The first Integer is always seen, so an end above the start runs the body once. That mirrors List.of(integersFrom:downTo:). The entry always finishes, because the range is fixed before the first step.

NameTypeDescription
fromIntegerthe first Integer the body sees.
downToIntegerthe last Integer the body sees, included.
startingWithStatethe State the first step builds on.
_(_: Integer, _: State) -> Statethe body, handed each Integer and the running State, answering with the next State.

the State after the last step.

Example
constant countdown = loop(from 3, downTo 1, startingWith "", (index, text) {
	<- "{text}{index}"
})

expect countdown::is("321")

constant once = loop(from 1, downTo 3, startingWith "", (index, text) {
	<- "{text}{index}"
})

expect once::is("1")

Loops the body can stop

step

loop<infer State, infer Answer>(
	startingWith state: State,
	step advance: (_: State) -> Step<State, Answer>,
) -> Answer

Answers the value the first #Done carries.

Each step answers with a Step. A #Continue carries the next State, and the loop goes again. A #Done carries the answer, and the loop finishes with it. This entry ends on a decision the State makes, rather than on a count or a fixed predicate. It is the family's other native.

NameTypeDescription
startingWithStatethe State the loop begins from.
step(_: State) -> Step<State, Answer>the body, handed the running State and answering with a Step: #Continue to go again, #Done to stop.

the value the first #Done carries.

Example
constant answer = loop(startingWith 1, step (n) {
	if n::isGreaterThan(50) {
		<- #Done("the first power of two over 50 is {n}")
	}

	<- #Continue(n::multiply(with 2))
})

expect answer::is("the first power of two over 50 is 64")

through-step

loop<infer State>(
	from start: Integer,
	through end: Integer,
	startingWith state: State,
	step advance: (_: Integer, _: State) -> Step<State, State>,
) -> State

Runs the body over the Integers from from up through through, and lets the body stop before the end.

Each step answers with a Step. A #Continue carries the next State, and the count goes on. A #Done carries the answer, and the loop finishes with it at once. The count running out answers the State the last step carried. An end below the start runs the body zero times and answers the seed.

NameTypeDescription
fromIntegerthe first Integer the body sees.
throughIntegerthe last Integer the body sees, included.
startingWithStatethe State the first step builds on.
step(_: Integer, _: State) -> Step<State, State>the body, handed each Integer and the running State, answering with a Step.

the State the count ran out on, or the value the first #Done carries.

Example
constant firstOverTen = loop(
	from 1,
	through 100,
	startingWith 0,
	step (index, total) {
		constant next = total::add(index)

		if next::isGreaterThan(10) {
			<- #Done(next)
		}

		<- #Continue(next)
	},
)

expect firstOverTen::is(15)

upTo-step

loop<infer State>(
	from start: Integer,
	upTo end: Integer,
	startingWith state: State,
	step advance: (_: Integer, _: State) -> Step<State, State>,
) -> State

Runs the body over the Integers from from up to, but not including, upTo, and lets the body stop before the end.

Each step answers with a Step, as the through: entry beside it reads. The body runs zero times when upTo is not above from, and the seed is answered untouched.

NameTypeDescription
fromIntegerthe first Integer the body sees.
upToIntegerthe Integer the count stops before.
startingWithStatethe State the first step builds on.
step(_: Integer, _: State) -> Step<State, State>the body, handed each Integer and the running State, answering with a Step.

the State the count ran out on, or the value the first #Done carries.

Example
constant firstBigSquare = loop(
	from 0,
	upTo 10,
	startingWith 0,
	step (index, found) {
		if index::multiply(with index)::isGreaterThan(20) {
			<- #Done(index)
		}

		<- #Continue(found)
	},
)

expect firstBigSquare::is(5)

downTo-step

loop<infer State>(
	from start: Integer,
	downTo end: Integer,
	startingWith state: State,
	step advance: (_: Integer, _: State) -> Step<State, State>,
) -> State

Runs the body over the Integers from from down through downTo, and lets the body stop before the end.

Each step answers with a Step, as the through: entry reads. The first Integer is always seen, so an end above the start runs the body once and answers what that step carried.

NameTypeDescription
fromIntegerthe first Integer the body sees.
downToIntegerthe last Integer the body sees, included.
startingWithStatethe State the first step builds on.
step(_: Integer, _: State) -> Step<State, State>the body, handed each Integer and the running State, answering with a Step.

the State the count ran out on, or the value the first #Done carries.

Example
constant stopped = loop(
	from 5,
	downTo 1,
	startingWith "",
	step (index, text) {
		if index::is(2) {
			<- #Done("{text}stop")
		}

		<- #Continue("{text}{index} ")
	},
)

expect stopped::is("5 4 3 stop")

Choices

Step

choice Step<State, Answer> {
	Continue { state: State },
	Done { value: Answer },
}

What a callback answers to say whether a loop goes round again.

#Continue carries the next State, and #Done carries the answer the loop finishes with. A match checks the two Cases like any other Choice.

State is what a loop threads from turn to turn, and Answer is what it finishes with. The two are independent. The step entries of loop and List::reduce's early-stopping entry both read one.

Cases #Continue { state: State }, #Done { value: Answer }

Taken by

esc