soma.cell

group_by returns a Map<String, List>; iterate its entries to compute a custom reduction the built-in agg doesn't cover (here: weighted average)

Demonstrates group_by + manual fold with reduce over each bucket.

A Soma program from the data part of the verified corpus: 58 lines, cells GroupReduce, GroupReduceTests. It passes soma check with soma 3.2.4.

How it is built

The cell GroupReduce has no face and no memory. Handlers: votes (line 6), tallies (line 18), total_weight (line 25), options_per_poll (line 30) and run (line 39).

The file also holds 1 test cell (GroupReduceTests) with 5 assertions, run by soma test.

Generated from soma describe on this file.

The source

group_reduce.cell

// group_by returns a Map<String, List>; iterate its entries to compute a
// custom reduction the built-in agg doesn't cover (here: weighted average).
// Demonstrates group_by + manual fold with reduce over each bucket.

cell GroupReduce {
    on votes() {
        return list(
            map("poll", "lunch", "option", "pizza", "weight", 3),
            map("poll", "lunch", "option", "sushi", "weight", 5),
            map("poll", "lunch", "option", "pizza", "weight", 2),
            map("poll", "movie", "option", "scifi", "weight", 4),
            map("poll", "movie", "option", "drama", "weight", 1),
            map("poll", "movie", "option", "scifi", "weight", 6)
        )
    }

    // Total weight per option within each poll, via agg.
    on tallies() {
        return votes()
            |> agg("option", "weight:sum")
            |> sort_by("weight_sum", "desc")
    }

    // Manual reduction: total weight across ALL votes via reduce.
    on total_weight() {
        return votes() |> reduce(0, p => p.acc + p.val.weight)
    }

    // Use group_by to count options per poll.
    on options_per_poll() {
        let grouped = group_by(votes(), "poll")
        let out = list()
        for poll in grouped.keys {
            out = out.push(map("poll", poll, "votes", len(grouped.get(poll))))
        }
        return out |> sort_by("votes", "desc")
    }

    on run() {
        for t in tallies() { print("{t.option}: {t.weight_sum}") }
        return total_weight()
    }
}

cell test GroupReduceTests {
    rules {
        // pizza: 3+2 = 5, sushi: 5, scifi: 4+6 = 10, drama: 1
        assert (tallies() |> filter_by("option", "=", "scifi"))[0].weight_sum == 10
        assert (tallies() |> filter_by("option", "=", "pizza"))[0].weight_sum == 5
        // scifi is the heaviest option overall
        assert tallies()[0].option == "scifi"
        // total weight = 3+5+2+4+1+6 = 21
        assert total_weight() == 21
        // each poll has 3 votes
        assert options_per_poll()[0].votes == 3
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check group_reduce.cell
✓ All checks passed.
$ soma test group_reduce.cell
test GroupReduceTests ...
  ✓ assert (tallies() |> filter_by("option", "=", "scifi"))[0].weight_sum == 10
  ✓ assert (tallies() |> filter_by("option", "=", "pizza"))[0].weight_sum == 5
  ✓ assert tallies()[0].option == "scifi"
  ✓ assert total_weight() == 21
  ✓ assert options_per_poll()[0].votes == 3

5 tests: 5 passed, 0 failed
$ soma verify group_reduce.cell
Verifying group_reduce.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)

Builtins used

agg, filter_by, group_by, len, list, map, print, reduce, sort_by

Source file: /corpus/data/group_reduce.cell · examples/corpus/data/group_reduce.cell