GuideThe Silo HandbookComplete Examples

Chapter 21 Complete Examples

Source

Four worked examples that pull together most of the language. Each is a real compilable program; read them front to back to see how the pieces fit.

FizzBuzz

The classic. Iterates a range, matches on divisibility, and prints each line.

:fn fizzbuzz ( Int -> ) +Console
  pop-> count
  0..count [ pop-> i
    i 1 + pop-> n
    n 15 mod 0 = :if
      "FizzBuzz" print
    :else
      n 3 mod 0 = :if
        "Fizz" print
      :else
        n 5 mod 0 = :if
          "Buzz" print
        :else
          n "{}" format print
        :end
      :end
    :end
  ] .for-each
:end

:main +Console
  20 fizzbuzz
:end

Points worth noting:

  • 0..count is a Range that the standard library treats as Foldable/Iterator, so .for-each works directly.
  • The quotation [ pop-> i … ] captures count implicitly — it's a closure.
  • All the arithmetic goes through ordinary trait methods (mod, =, +). :if / :else chains handle the branching.

Runs on an existential-length (Vec Int). Declares +Div because the :loop break condition is runtime-dependent — the compiler can't prove termination statically.

:fn binary-search ( (Vec Int) Int -> (Option AnyUInt) ) +Div
  pop-> target pop-> vec
  0 pop-> lo
  vec .len pop-> hi
  :loop
    lo hi:if None :break :end
    lo hi + 2 / pop-> mid
    mid vec .get .unwrap pop-> val
    val target = :if
      mid Some :break
    :end
    val target < :if
      mid 1 + pop-> lo
    :else
      mid pop-> hi
    :end
  :end
:end

The return type (Option AnyUInt) says "either an index, or none." If the loop breaks with None, the search failed; if it breaks with mid Some, we found the target at position mid.

Temperature converter

Two newtype records — Celsius and Fahrenheit — plus From impls that convert between them, plus Display impls for locale-neutral rendering. The newtypes make sure you can't accidentally pass a Celsius where a Fahrenheit is expected.

:record Celsius    .value F64 :end
:record Fahrenheit .value F64 :end

:impl From Fahrenheit Celsius
  .from ( Celsius -> Fahrenheit )
    .value 9.0 * 5.0 / 32.0 + Fahrenheit ;
:end

:impl From Celsius Fahrenheit
  .from ( Fahrenheit -> Celsius )
    .value 32.0 - 5.0 * 9.0 / Celsius ;
:end

:impl Display Celsius
  .fmt ( Celsius FmtSpec -> Str )
    drop .value "{:.1}°C" format ;
:end

:impl Display Fahrenheit
  .fmt ( Fahrenheit FmtSpec -> Str )
    drop .value "{:.1}°F" format ;
:end

:main +Console
  100.0 Celsius pop-> boiling
  boiling "{}" format print             # "100.0°C"
  boiling (Fahrenheit .from) pop-> f
  f "{}" format print                   # "212.0°F"
  f (Celsius .from) "{}" format print   # "100.0°C"
:end

Notes:

  • (Fahrenheit .from) is qualified dispatch — it resolves to the specific From Fahrenheit Celsius impl.
  • The FmtSpec argument to .fmt carries width/align/fill; here the impl drops it because it doesn't honour any layout beyond the trait-specific {:.1} precision. A richer impl would consult FmtSpec to decide whether to pad.
  • From auto-generates the reverse Into, so .into would also work at the call site if a target-typed context were available.

Simple logger with aspects

The longest example — an aspect-driven logger with a concrete handler, invoked through :with. Brings together unions, aspects, :impl, and scoped handler installation.

:union LogLevel
  | LTrace | LDebug | LInfo | LWarn | LError
:end

:aspect Log
  .log ( Str LogLevel -> ) drop drop ;
:end

:record StdoutLogger :end

:impl Log StdoutLogger
  .log ( Str LogLevel -> ) +Console
    pop-> level
    pop-> msg
    level :match
      | LTrace => "TRACE"
      | LDebug => "DEBUG"
      | LInfo  => "INFO"
      | LWarn  => "WARN"
      | LError => "ERROR"
    :end pop-> label
    label msg "[{}] {}" format print ;
:end

:use
  :aspect logging Log
:end

:fn process-items ( (Vec Str) -> ) +Console
  "starting batch" LInfo .log
  [ pop-> item
    item "processing: {}" format LDebug .log
  ] .for-each
  "batch complete" LInfo .log
:end

:main +Console
  StdoutLogger :with Log
    "a" "b" "c" vec/3 process-items
  :end
:end

Output:

[INFO] starting batch
[DEBUG] processing: a
[DEBUG] processing: b
[DEBUG] processing: c
[INFO] batch complete

A few things worth teasing apart:

  • The .log signature ( Str LogLevel -> ) has the level on top of the stack — you write "msg" LInfo .log, with the level as the "receiver-like" value closest to the call.
  • The default body drop drop ; makes the call a no-op when no handler is installed. That's why .log has no +Log effect in the signatures that call it: the aspect system doesn't need one.
  • StdoutLogger :with Log … :end installs the handler for the body's dynamic extent. Inside, every .log dispatches to StdoutLogger's impl; outside, the default drops.
  • The :aspect logging Log import in :use brings the aspect's operation (.log) into scope as an ordinary method — no +Log annotation anywhere in process-items.

Where to go from here

If you've read the book straight through, you've seen every feature the language ships with at least once. A reasonable set of next steps:

  • Build something small against a CLI host — a line-counter, a config parser, a toy HTTP client. The host-capability story clicks quickly once you have to declare the effects your :main needs.
  • Open the silo:std source and read through the trait implementations for the primitives. The standard library is deliberately written in Silo (not baked into the host) so that its idioms demonstrate how to use the language in production.
  • Reach for a non-trivial dependent-type pattern — a length-preserving function, a refined-integer port number, a phantom-tagged unit — and work it until the type error becomes legible rather than intimidating.
  • When you want to go deeper on a specific topic, the specification (spec/*.md) is the authoritative source. Every rule is numbered (si[...]) and cross-referenced; the book points at specific rules where a subtle decision merits it.

The glossary is an index if you want to jump between concepts; the book root has a table of contents for browsing.

Thanks for reading.