You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/06/14 01:20:04 UTC

(groovy-website) branch asf-site updated: FizzBuzz post (minor tweaks)

This is an automated email from the ASF dual-hosted git repository.

paulk-asert pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new c51ae41  FizzBuzz post (minor tweaks)
c51ae41 is described below

commit c51ae41b688e14de7e7a93847d9895332d19bbe4
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sun Jun 14 10:29:16 2026 +1000

    FizzBuzz post (minor tweaks)
---
 .../site/blog/fizzbuzz-with-groovy-and-emojis.adoc | 447 +++++++++++++++++++++
 1 file changed, 447 insertions(+)

diff --git a/site/src/site/blog/fizzbuzz-with-groovy-and-emojis.adoc b/site/src/site/blog/fizzbuzz-with-groovy-and-emojis.adoc
new file mode 100644
index 0000000..f28d7cc
--- /dev/null
+++ b/site/src/site/blog/fizzbuzz-with-groovy-and-emojis.adoc
@@ -0,0 +1,447 @@
+= FizzBuzz with Groovy and Emojis
+Paul King <paulk-asert|PMC_Member>
+:revdate: 2026-06-14T08:00:00+00:00
+:keywords: groovy, fizzbuzz, emoji, switch, pattern matching, ginq, streams, virtual threads, concurrency, verification, gep-19
+:description: A fun tour of many ways to write FizzBuzz in Groovy 6 — switches, ternaries, cycles, GINQ, parallel collections and a verified version — all in emoji.
+
+== Introduction
+
+https://en.wikipedia.org/wiki/Fizz_buzz[FizzBuzz] is the little interview
+chestnut: print the numbers `1` to `n`, but say _Fizz_ for multiples of
+three, _Buzz_ for multiples of five, and _FizzBuzz_ for multiples of both.
+It is small enough to fit in a tweet and yet — as a decade of blog posts
+attests — roomy enough to show off whatever language feature you happen to
+be fond of this week.
+
+This post is unapologetically in that spirit. It is *not* a hunt for the one
+true FizzBuzz; it is a tour of several genuinely different ways to write it in
+Groovy 6, each with its own flavour. To keep them comparable we render them
+all with the same emoji instead of words:
+
+[%autowidth]
+|===
+| Rule | Word | Emoji
+
+| `n % 3 == 0`  | Fizz     | 🥤
+| `n % 5 == 0`  | Buzz     | 🐝
+| `n % 15 == 0` | FizzBuzz | 🥤🐝
+|===
+
+The emoji idea is borrowed, with thanks, from Don Raab's
+https://donraab.medium.com/ternary-predicate-and-pattern-matching-for-fizzbuzz-with-java-26-646c812a137b[_Ternary,
+Predicate, and Pattern Matching for FizzBuzz with Java 26_], which dresses up
+its Java variants the same way. It is also where the emoji appear in the
+https://github.com/paulk-asert/groovy-verify#fizzbuzz--element-wise-array-correctness[`groovy-verify`]
+project, one of whose examples we revisit below.
+
+The starting point for this post was a reader's first attempt, which already
+shows how compact Groovy can be:
+
+[source,groovy]
+----
+val result = (1..20).collect { each ->
+    switch (each) {
+        case { each % 15 == 0 } -> "🥤🐝"
+        case { each % 3 == 0 }  -> "🥤"
+        case { each % 5 == 0 }  -> "🐝"
+        default -> each
+    }
+}
+println result.join(' ')
+// 1 2 🥤 4 🐝 🥤 7 8 🥤 🐝 11 🥤 13 14 🥤🐝 16 17 🥤 19 🐝
+----
+
+Every runnable example maps the same range `(1..20)` and asserts the same
+canonical output, so the shapes are directly comparable:
+
+----
+1 2 🥤 4 🐝 🥤 7 8 🥤 🐝 11 🥤 13 14 🥤🐝 16 17 🥤 19 🐝
+----
+
+A companion project at
+https://github.com/paulk-asert/groovy-fizzbuzz[`groovy-fizzbuzz`] holds
+runnable versions of every example, resolving Groovy `6.0.0-SNAPSHOT` from the
+ASF snapshot repository.
+
+== The idiomatic switch
+
+Groovy's `switch` is an _expression_, and its case labels can be closures used
+as predicates — applied to the switch subject, first match wins. This is the
+cleaned-up form of the opening attempt, and the same shape the
+https://groovy.apache.org/blog/gpars-meets-virtual-threads[GPars meets Virtual
+Threads] post used for the word version:
+
+[source,groovy]
+----
+def fizzbuzz = { int n ->
+    switch (n) {
+        case { it % 15 == 0 } -> '🥤🐝'
+        case { it % 3 == 0 }  -> '🥤'
+        case { it % 5 == 0 }  -> '🐝'
+        default               -> n.toString()
+    }
+}
+
+assert (1..20).collect { fizzbuzz(it) }.join(' ') ==
+    '1 2 🥤 4 🐝 🥤 7 8 🥤 🐝 11 🥤 13 14 🥤🐝 16 17 🥤 19 🐝'
+----
+
+== The nested ternary
+
+The most concise of Don Raab's Java variants is a nested ternary, and it
+translates to Groovy almost character for character. It reads top to bottom —
+15, then 3, then 5 — as one expression with no statements:
+
+[source,groovy]
+----
+def fizzbuzz = { int n ->
+    n % 15 == 0 ? '🥤🐝'
+        : n % 3 == 0 ? '🥤'
+        : n % 5 == 0 ? '🐝'
+        : n.toString()
+}
+----
+
+This particular spelling earns its keep later: it is exactly the `spec(n)` the
+verified version proves correct.
+
+== Building the string
+
+Here is a trick that drops the `% 15` special case entirely. Glue the 🥤 and 🐝
+fragments together; when a fragment doesn't apply it contributes the empty
+string and simply vanishes. The _FizzBuzz_ case falls out for free when both
+are present. Groovy's Elvis operator (`?:`) then supplies the number whenever
+the glued string is empty (and therefore falsy):
+
+[source,groovy]
+----
+def fizzbuzz = { int n ->
+    def s = (n % 3 == 0 ? '🥤' : '') + (n % 5 == 0 ? '🐝' : '')
+    s ?: n.toString()
+}
+----
+
+Three rules expressed with two tests — the third emerges from the other two.
+
+== The most elegant FizzBuzz: cycles
+
+The well-travelled Reddit thread on
+https://www.reddit.com/r/coding/comments/59iijd/the_most_elegant_implementation_of_fizzbuzz/[the
+most elegant implementation of FizzBuzz] makes a lovely observation: you don't
+need a divisibility test in the mapping _at all_. The 🥤 pattern repeats with
+period three and the 🐝 pattern with period five; lay them down by position and
+glue them. The classic Haskell rendition zips two infinite lazy cycles:
+
+[source,haskell]
+----
+zipWith (++) (cycle ["","","🥤"]) (cycle ["","","","","🐝"])
+----
+
+Indexing two short period arrays by `(n - 1) % period` is the same idea without
+the laziness, and Elvis again fills in the number:
+
+[source,groovy]
+----
+def fizz = ['', '', '🥤']         // period 3
+def buzz = ['', '', '', '', '🐝'] // period 5
+
+def fizzbuzz = { int n ->
+    (fizz[(n - 1) % 3] + buzz[(n - 1) % 5]) ?: n.toString()
+}
+----
+
+Arithmetic has moved out of the conditionals and into the indexing — the
+divisibility is encoded in the _lengths_ of the two patterns.
+
+== FizzBuzz as data: a predicate list
+
+Don Raab's most readable variant uses Eclipse Collections'
+https://www.eclipse.org/collections/[`CaseFunction`]: an ordered list of
+`(predicate, result)` cases, first match wins, with a default. The same idea
+in plain Groovy is a list of pairs — the cases become _data_ you can build,
+pass around and extend by appending rather than by editing a `switch`:
+
+[source,groovy]
+----
+def cases = [
+    [{ int n -> n % 15 == 0 }, '🥤🐝'],
+    [{ int n -> n % 3 == 0 },  '🥤'],
+    [{ int n -> n % 5 == 0 },  '🐝'],
+]
+
+def fizzbuzz = { int n ->
+    cases.find { pred, _ -> pred(n) }?.last() ?: n.toString()
+}
+----
+
+== A query: GINQ
+
+https://groovy-lang.org/using-ginq.html[GINQ] (Groovy-Integrated
+Query) gives FizzBuzz a SQL-shaped spelling with `from … select …`. It is
+gloriously over-engineered for a problem this small, but it is a genuinely
+different paradigm — and it would scale to filtering, grouping and joins if the
+query grew:
+
+[source,groovy]
+----
+def result = GQ {
+    from n in (1..20)
+    select n % 15 == 0 ? '🥤🐝'
+        : n % 3 == 0 ? '🥤'
+        : n % 5 == 0 ? '🐝'
+        : n.toString()
+}.toList()
+----
+
+== FizzBuzz in parallel
+
+FizzBuzz is _embarrassingly parallel_: each element depends only on its own
+number, so the work splits cleanly. The earlier GPars post ran the parallel
+version on the https://gpars.org/[GPars] library; in Groovy 6 those patterns
+are integrated into the core (link:../wiki/GEP-18.html[GEP-18]).
+`ParallelScope.withPool(Pool.virtual())` binds a virtual-thread pool for the
+scope, and `collectParallel` is the parallel `collect`:
+
+[source,groovy]
+----
+import groovy.concurrent.Pool
+import groovy.concurrent.ParallelScope
+
+def result = ParallelScope.withPool(Pool.virtual()) { scope ->
+    (1..20).collectParallel { fizzbuzz(it) }
+}
+----
+
+This borrows the framing from the _Concurrency "lite"_ section of
+`groovy-verify`: factor a concurrent computation into a *structural guarantee*
+the runtime provides and a *local obligation* you must get right. Here the
+structural half is "an order-preserving parallel map gives the same result as
+the sequential one" — `collectParallel` keeps encounter order — so all that is
+left is the per-element mapping. The mapping is unchanged from the sequential
+versions, which is the whole point: the `fizzbuzz` closure drops straight in.
+
+That same mapping drops just as easily into ordinary Java Streams — the form
+Don Raab uses — where switching to parallel is a one-word change:
+
+[source,groovy]
+----
+import java.util.stream.IntStream
+
+def result = IntStream.rangeClosed(1, 20)
+        .mapToObj { fizzbuzz(it) }   // .parallel() before this to fan out
+        .toList()
+----
+
+== Proving FizzBuzz
+
+If parallel FizzBuzz is "get the per-element mapping right and the structure
+takes care of the rest", it is fair to ask: can we _prove_ we got that element
+right? The
+https://github.com/paulk-asert/groovy-verify[`groovy-verify`] project — an
+SMT-backed `@TypeChecked` extension that discharges
+https://groovy-lang.org/contracts.html[`groovy.contracts`] annotations at
+compile time — does exactly that, in the Dafny style of element-wise array
+verification. A pure `spec(n)` (the nested ternary from earlier), a loop that
+fills `r[i] = spec(i + 1)`, and a postcondition saying *every* slot matches it:
+
+[source,groovy]
+----
+@TypeChecked(extensions = 'verification.VerifyChecker')
+static String[] build(int upTo) {
+    String[] r = new String[upTo]
+    int i = 0
+    @Invariant({ 0 <= i && i <= upTo && r.length == upTo &&
+                 (0..<i).every { int k -> r[k] == FizzBuzz.spec(k + 1) } })
+    @Decreases({ upTo - i })
+    while (i < upTo) {
+        r[i] = FizzBuzz.spec(i + 1)
+        i = i + 1
+    }
+    return r
+}
+----
+
+FizzBuzz counts from `1`, so the 0-based slot `i` holds the value for number
+`i + 1` — that `+ 1` is the bridge between the array index and the FizzBuzz
+number. The module compiles only because the solver can prove no slot holds the
+wrong emoji. Introduce the classic off-by-one — `r[i] = spec(i)` — and the loop
+invariant is no longer preserved; compilation fails with a concrete
+counterexample naming the offending slot:
+
+[source]
+----
+[Static type checking] - Cannot prove loop invariant is preserved by the loop body in build
+    counterexample: i = 0, r.length = 1, upTo = 1
+    r[0] = "🥤🐝" — the spec requires "1"
+    fails on: build(1)
+----
+
+Slot 0 would hold `spec(0) == "🥤🐝"` (zero divides everything, so it's
+FizzBuzz) where number 1 requires `"1"`. "Correct" here means _faithful to `spec`_: the loop provably realises it
+at every index, with no gap, overwrite or off-by-one. It doesn't claim the spec
+itself is the One True FizzBuzz.
+
+== Parallel, checked and proven
+
+The last two sections each told half a story. The parallel version leaned on a
+*structural guarantee* (an order-preserving parallel map) so it only had to get
+the per-element mapping right; the verified version *proved* a per-element
+mapping right. What happens when we put them together — and need to _combine_
+the parallel results rather than just collect them?
+
+When the parallel version assembles its per-number tokens, the _combine_ step
+is the most natural operation in FizzBuzz: gluing the rendered emoji strings
+together. String concatenation is a *monoid* — associative, with the empty
+string as identity — and that associativity is exactly what a parallel
+reduction relies on: partition the range, render each chunk, recombine, and the
+result must not depend on where the splits fell. Groovy 6 ships
+`CombinerChecker` (from the
+https://groovy.apache.org/blog/groovy6-functional[functional-programming post])
+to check that algebraic _shape_; `groovy-verify` proves the _semantics_.
+Turning on *both* checkers over one class is a genuine two-checker compile:
+
+[source,groovy]
+----
+@TypeChecked(extensions = ['groovy.typecheckers.CombinerChecker', 'verification.VerifyChecker'])
+class Join {
+
+    @Reducer(zero = '""')             // string concat is a monoid; CombinerChecker trusts this & checks the seed
+    @Ensures({ result == a + b })     // the combiner's defining equation, proven
+    static String glue(String a, String b) { a + b }
+
+    @Ensures({ (a + b) + c == a + (b + c) })   // associativity — proven for ALL strings (the law sumParallel needs)
+    static void associative(String a, String b, String c) { }
+
+    @Ensures({ a + '' == a && '' + a == a })   // identity — '' is a true zero, proven
+    static void identity(String a) { }
+
+    @Ensures({ '🥤' + '🐝' == '🥤🐝' })          // not just abstract strings — the actual emoji, proven
+    static void emojiGlue() { }
+
+    static void parallelGlue() {
+        (1..20).collect { token(it) + ' ' }.sumParallel(Join::glue)   // certified site
+    }
+}
+----
+
+The two checkers keep their error channels separate but reinforce each other —
+shape beside semantics:
+
+* `CombinerChecker` validates the `sumParallel(Join::glue)` call site: `glue`
+carries `@Reducer`, so the combiner is _declared_ associative and the seedless
+parallel reduction is safe. It trusts the annotation.
+* `VerifyChecker` (the Z3 backend) discharges what the annotation _claims_.
+Groovy's `+` on `String` lowers to Z3's theory of sequences, so the monoid laws
+prove over _arbitrary_ strings — and the solver reasons about the literal emoji
+too: `'🥤' + '🐝' == '🥤🐝'` is a proof, not an assertion.
+
+The synergy cuts both ways. A non-associative _inline_ combiner like
+`injectParallel(0) { a, b -> a - b }` is a `CombinerChecker` error from static
+shape analysis alone. A method _falsely_ annotated `@Associative` is trusted by
+`CombinerChecker` — but refuted by the verifier, which cannot prove the law
+holds. And the proof is genuine, not skipped: concatenation is associative but
+_not_ commutative, and claiming otherwise is rejected with a minimal emoji
+witness:
+
+[source]
+----
+[Static type checking] - Cannot prove postcondition of claim holds on this return path
+    ensured: ((🥤 + a) == (a + 🥤))
+    fails on: claim("A")
+----
+
+That is, `"🥤" + "A"` ≠ `"A" + "🥤"`. One checker reasons about the syntactic
+shape, the other about the values; the combiner has to satisfy both.
+
+Stand the two verified sections side by side and the parallel FizzBuzz is
+correct end to end: every _element_ is proven faithful to `spec` (the array
+build), and the _combine_ step is a proven monoid (this `Join`), so the parallel
+join is guaranteed to equal the sequential one for any split. The runtime
+supplies the parallelism; the compiler supplies the proof that using it changes
+nothing.
+
+And the proof isn't tied to Groovy's parallel collections. The JDK's own
+`Stream.reduce(identity, accumulator)` _documents_ that, for correct parallel
+results, the identity must be a genuine identity and the accumulator must be
+associative — exactly the monoid contract we just proved. So the same `glue`
+drops straight into the ordinary-streams form from earlier, fanned out:
+
+[source,groovy]
+----
+tokens.parallelStream().reduce('', Join::glue)   // == the sequential join
+----
+
+NOTE: Proving the monoid _laws_ is not a detour around the parallel claim — it
+_is_ the claim: associativity and identity are exactly what make `sumParallel`'s
+partition-and-recombine agree with any sequential fold, so the algebra is the
+right thing to prove.
+
+== A speculative Groovy 7 version
+
+Don Raab's Java 26 version finally gets _primitive patterns_ — the payoff of a
+ten-year wait:
+
+[source,java]
+----
+each -> switch (each) {
+    case int i when i % 15 == 0 -> "🥤🐝";
+    case int i when i % 3 == 0  -> "🥤";
+    case int i when i % 5 == 0  -> "🐝";
+    default -> Integer.toString(each);
+};
+----
+
+Groovy didn't have to wait. Its standard answer is the closure-case switch from
+the very first example — available since long before that ten-year clock started
+ticking, and running unchanged on every currently supported JDK:
+
+[source,groovy]
+----
+switch (n) {
+    case { it % 15 == 0 } -> '🥤🐝'
+    case { it % 3 == 0 }  -> '🥤'
+    case { it % 5 == 0 }  -> '🐝'
+    default               -> n.toString()
+}
+----
+
+What _does_ wait on Groovy 7 is the exact match of Java's spelling — a primitive
+type pattern `int i` that binds and narrows within the case body and its `when`
+guard. link:../wiki/GEP-19.html[GEP-19] (_Structural Pattern Matching in
+switch_) sketches it, aligning Groovy's surface syntax with Java's
+pattern-matching trajectory:
+
+[source,groovy]
+----
+switch (n) {
+    case int i when i % 15 == 0 -> '🥤🐝'
+    case int i when i % 3 == 0  -> '🥤'
+    case int i when i % 5 == 0  -> '🐝'
+    default                     -> n.toString()
+}
+----
+
+To be clear, this last form is *speculative* — GEP-19 is a draft targeting
+Groovy 7 and is not implemented today, so it does not compile on Groovy 6; it
+lives in the companion repo (kept out of the build) only to show where the
+alignment leads. The point is the contrast: the _capability_ has been there for
+years and on every JDK; GEP-19 brings the matching _syntax_, not the power.
+
+== Conclusion
+
+None of these is _the_ way to write FizzBuzz, and that was never the goal. The
+fun is in how many shapes the same three rules can take: a switch of
+predicates, a nested ternary, a string you build and let collapse, two periodic
+patterns you index, a list of cases as data, a SQL-shaped query, the same
+mapping fanned across virtual threads, and a version a solver proves correct
+before it ever runs. Pick the one that makes you smile — and add emoji.
+
+== Further information
+
+* Companion code for this post: https://github.com/paulk-asert/groovy-fizzbuzz
+* Don Raab's https://donraab.medium.com/ternary-predicate-and-pattern-matching-for-fizzbuzz-with-java-26-646c812a137b[Ternary, Predicate, and Pattern Matching for FizzBuzz with Java 26] — the source of the emoji idea
+* The https://github.com/paulk-asert/groovy-verify#fizzbuzz--element-wise-array-correctness[`groovy-verify`] FizzBuzz example and its Concurrency "lite" section
+* https://groovy.apache.org/blog/gpars-meets-virtual-threads[GPars meets Virtual Threads]
+* https://www.reddit.com/r/coding/comments/59iijd/the_most_elegant_implementation_of_fizzbuzz/[The most elegant implementation of FizzBuzz] (Reddit)
+* link:../wiki/GEP-18.html[GEP-18 — Integrated Concurrency and Parallel Processing]
+* link:../wiki/GEP-19.html[GEP-19 — Structural Pattern Matching in switch] (Groovy 7 candidate)