Skip to content
Playground

std::control::nondet

The nondet module provides nondeterminism through the nondet algebraic effect.

yulang
use std::control::nondet::*

The effect

yulang
pub act nondet:
    pub branch: () -> bool
    pub reject: () -> never

branch makes a binary choice; reject prunes the current branch. Higher-level helpers below are built on top of these.

each

yulang
(each [1, 2, 3]).list   // [1, 2, 3]
(each 1..).once         // opt::just 1

each xs returns one element of xs, signalling its choice through the nondet effect. xs can be any value implementing Fold (lists, ranges, …).

guard

yulang
{
    guard: true
    "kept"
}.once

{
    guard (1 == 1)
    "kept"
}.once

When the condition is false, guard calls reject and prunes the current branch.

Collectors

Collectors are written as method calls on a nondeterministic expression:

MethodResult typeDescription
.listlist 'aAll results, in branch order
.logiclist 'aAll results with breadth-first scheduling (suitable for infinite branches)
.onceopt 'aFirst result if any, otherwise nil

Each collector handles branch and reject, removing the nondet effect from the type.

Example: Pythagorean triples

yulang
{
    my a = each 1..
    my b = each a<..
    my c = each b<..

    guard: a * a + b * b == c * c

    (a, b, c)
} .once

The result is just (3, 4, 5).

The same search can use three independent each 1.. choices and guard: a <= b / guard: b <= c, but the bounded form above is friendlier to the current VM and to browser Wasm stacks.

Junctions

The companion module std::control::junction exposes all xs and any xs. They are not part of nondet, but they share the same theme — they wrap a collection so a single comparison covers every element via the junction effect:

yulang
if all [1, 2, 3] < any [2, 3, 4]:
    1
else:
    0

all/any are reachable through the prelude.

Yulang