Code Compare
The same task, eight ways
Pick a topic to see it written in guji, Go, OCaml, Haskell, Perl, Raku, Rust, and Python, side by side, with a note on what's idiomatic in each.
Hello, World
Every language's smallest complete program: name an entry point and print one line to standard output. Watch how much ceremony each one demands - from Perl's and Python's bare one-liners, to Go's explicit package/func main, to Guji's sub main() (its return type is optional; add : Int only when you want to set the exit code). Notice too how the string reaches the screen: a statement (print), a function (println!), a method, or a top-level expression.
Variables, Binding & Basic Types
The same tiny program in eight languages: bind four immutable values of the four basic scalar types (an Int, a Float, a Str, and a Bool), then use one mutable accumulator to sum 1..count and print a summary. Watch two axes vary: how mutability is opted into (immutable-by-default with mut/let/ref, versus mutable-by-default), and how a value's type is signalled - a leading sigil that is part of the name ($, @, % in Guji, Perl, and Raku) versus a bare name whose type lives only in a declaration or annotation (Go, OCaml, Haskell, Rust, Python).
Functions & Closures
The same small task in every language: define a named function square, write a closure factory make_adder(n) that captures n and returns a new function, then map a list through both - squaring each element, then adding 5. Watch for how a closure captures n (does it need an explicit return? a named lambda keyword? an arrow?), how anonymous functions read ({ ... } topic blocks vs. lambda/fun/|x|/sub {}), and how functions compose over a list (map with a lambda vs. a comprehension).
Collections: map, filter, reduce
The same pipeline in all eight languages: take a list of integers, keep the even ones, double each, then sum the result (answer: 24). Watch where each language puts the transformation - chained methods on the data (Guji, Raku, Rust), free functions wrapping the data (Python, OCaml), or right-to-left composition you read inside-out (Perl). Notice too whether reduce needs an explicit seed and whether the intermediate steps allocate new lists or stream lazily.
Sum Types & Pattern Matching
Model a value that is one of several shapes and take it apart by structure, not by a chain of type tests. The task is the canonical one: a Shape is either a Circle (radius) or a Rect (width, height), and area matches on the variant to pull out its fields. Watch which languages have real sum types with exhaustiveness checking (Guji, OCaml, Rust), which bolt structural match onto classes (Python 3.10+), and which have no native sum type at all and must fake it - Go with an interface, Perl with a tagged array and an if/elsif ladder.
Regex & text processing: split, match, replace
One small task in all eight languages: take the string "alice=30, bob=25, carol=35", split it into key=value entries, match each with a named-capture regex to print "alice is 30; bob is 25; carol is 35", then replace every name=age with age:name to get "30:alice, 25:bob, 35:carol". Watch where the regex lives - a first-class literal woven into the language (Guji, Perl, Raku), a compiled object from a library (Rust, Python, OCaml), or the standard library's regexp package (Go). Notice especially how each names its captures ((?<name>…) vs (?P<name>…)) and how the replacement template refers back to them.
Errors: Result, Option & Exceptions
The same fallible task in all eight languages: parse a string to an integer and compute 100 / n, where two distinct things can go wrong - the string isn't a number, and the number is zero. Watch the fundamental split: do errors travel in the type (a Result/Either value you must destructure, with ?-style early-return) or out-of-band as exceptions you try/catch? Notice how Guji, Rust, OCaml, and Haskell make every failure visible in the signature, while Python and Perl reach for thrown exceptions, and Go threads an explicit err value through each step.
Concurrency: threads, goroutines & channels
The same job in all eight languages: fan out five concurrent workers, each computing the square of a number, send each result over a channel (or queue), and have the main task collect and sum them (answer: 55). Watch the two big axes: how a task is spawned (hatch/go/thread/async/Thread) and how results travel back - Guji and Go pass them through a typed channel with no shared mutable state, Rust moves ownership across a mpsc channel, while Perl and Python hand them through a thread-safe queue. Notice who has to close the channel and who joins the workers before reading the total.
Reading Input: lines, files & basic I/O
The same little cat -n: read input line by line and print each line prefixed with its 1-based line number. Watch how each language gets a stream of lines and how it counts: a buffered scanner with a manual counter (Go), a buffered reader whose .lines() iterator chains straight into enumerate (Rust, Python), the implicit line-number variable $. (Perl), built-in .lines with .kv (Raku), or a recursive read-until-End_of_file loop (OCaml). Guji v0 is the outlier: its only I/O primitive is print, so input is modelled as an in-program value split with .lines().
Types & Records
The same record in all eight languages: define a Point type with integer x and y fields, construct one, then translate it by (3, 4) to get a second point. Watch how a record type is declared (a class/struct/record/blessed hash) and whether updates mutate or copy: the functional-first languages here return a brand-new Point rather than changing the original. Notice too how much boilerplate each needs - a derived constructor, explicit field accessors, or just an annotated field list.
Closures
A closure is a function that captures variables from the scope where it was defined and keeps them alive after that scope returns. The same task in every language: a factory make_between(lo, hi) that returns a one-argument predicate closing over both bounds, then uses it to filter a list down to [3, 4, 5, 6, 7]. Watch how each language spells the captured function (a named lambda keyword? an arrow? move? a bare sub?) and whether the capture is by value or by reference - the detail that decides what a closure sees when its surrounding variables change.
Generics & Parametric Polymorphism
The same small task in every language: a generic container Box[T] that wraps a value of any type and carries a map from T to a new type U, plus a generic free function first that returns the first element of any list as an optional. Watch how the type parameter is introduced ([T] brackets vs. <T> angle brackets vs. 'a type variables vs. nothing at all in dynamic languages), and whether the reuse is checked at compile time - a statically-typed Box[Int] and Box[Str] share one definition but stay distinct types, while Perl and Python reuse the very same code with no type machinery.
Strings: building, slicing & formatting
One task in all eight languages: from name = "Ada Lovelace" and year = 1815, build a formatted line - "Hello, Ada Lovelace! That is 211 years since 1815." - by interpolating the bindings and an embedded 2026 - year expression, then slice out the first word and upper-case it to print "First name: ADA". Watch how each language formats: weaving values straight into the literal (Guji, Perl, Raku, Python f-strings), a positional printf-style template (Go, OCaml), or Rust's {} placeholder syntax. Notice too how each carves out a substring - splitting on a space versus indexing a range of bytes, chars, or code points.
JSON: Parse and Produce
The same round-trip in all eight languages: take the JSON text {"name":"ada","age":36}, parse it, bump age by one, and produce the JSON text back out - printing {"age":37,"name":"ada"} with keys in sorted order. Watch where JSON lives: a batteries-included standard module (Python, Go, Raku, Perl) versus an external library (Rust's serde_json, OCaml's Yojson), and - for Guji - a hand-rolled sum type rendered by match, since v0 has no JSON in its prelude. Notice too which languages give you sorted keys for free and which need an explicit nudge.
Sorting: by natural order and by a custom key
The same task in all eight languages: sort a list of integers into ascending order, then sort a list of people by a custom key - their age, descending. Watch how each language expresses the key: a function that extracts the field to compare (Guji, Python, Rust, Raku), a two-argument comparator that returns the ordering (Go, OCaml, Perl), or both. Note too whether sorting returns a new list or rearranges the original in place.
16Recursion
A function that calls itself, with a base case that stops the descent and a recursive case that shrinks the problem toward it. The task is the canonical one - factorial, where 0! = 1 is the base case and n! = n * (n-1)! is the recursion. Watch how each language spells the base case: Guji, OCaml, Rust, and Python lean on match/if, Raku splits it across two multi definitions by signature, and Perl and Go fall back on a guard return. The shapes differ; the divide-and-stop idea is the same everywhere.
Modules & Packages: Defining and Importing
The same small task in every language: split code into a reusable module - a circle module that exports a public area while keeping a square helper private - then import it from a main program and call across the boundary. Watch what a module is (a single file in Guji, Python, and OCaml; a directory of files in Go; a named package in Perl), how a name is exported (pub vs. an uppercase initial vs. an export list vs. nothing at all), and how privacy is enforced (a compile-time error in Guji, Go, and Rust versus mere convention in Python and Perl).
Interfaces, Traits & Duck Typing
The same task in every language: model "any shape with an area", give a Circle and a Square their own area, then dispatch over a heterogeneous list and sum the results. Watch how each language abstracts over behavior rather than data - a Go interface, a Rust trait, an OCaml object's structural type, Perl's and Python's duck typing, and - because user-defined traits are deferred in Guji v0 - a Guji enum with a method that matches on itself.
Hash Maps
A hash map (also called a dictionary or associative array) stores key/value pairs and offers fast insert, look-up, and iteration. The three core operations shown here are inserting a pair, looking a key up safely (handling the absent case), and iterating over every entry.
20Ranges and Iteration
Two everyday loops side by side: iterating a numeric range, and walking a collection while tracking the index of each element. Each language ships its own idiom, from C-style index loops to range objects, enumerate-style pairing, and key/value iterators.
Read Command-Line Arguments
Read the arguments a program was invoked with, skipping the program name itself. Most languages expose them as a list or array on a standard global, then the program counts and iterates over them.
22Testing
A minimal unit test for a function. We define a tiny add function and check it against a couple of expected results. Most languages ship a test idiom in the standard library or runtime; where none exists, an assertion that aborts on failure plays the same role.