Skip to Content
Mélodium 0.10.3 is now available!
DocsExamples01. Hello, Mélodium

Hello, Mélodium

Source: tutorial/01_hello_melodium See in Playground

The smallest useful Mélodium program. It has no procedural entry point: main is a graph of treatments connected together, not a function body that runs top to bottom. startup() fires once when the engine starts, and every other treatment reacts as soon as the data it needs is available.

Running

cd tutorial/01_hello_melodium melodium run Compo.toml --name "World" --times 3

The program computes a single greeting with a pure function and writes it to hello.txt (the Block half), and generates the same name repeated times times as a stream, numbering each occurrence (the Stream half). Both are logged, so a run shows the two execution styles side by side.

Optional: add --api-report and an API token (MELODIUM_API_TOKEN) to see this run’s full trace on Cadence.CI.

How it works

This example uses no models: only stateless treatments and functions.

Data flow

startup.trigger fires once and fans out to both halves of the graph: nothing here runs “first” or “second”, both start as soon as the trigger arrives.

The Block side: one greeting, written once

|format and |map/|entry build the greeting string from the name parameter, purely, with no I/O involved. emit turns it into a Block<string>, and stream widens it into a one-element Stream<string> so writeTextLocal can consume it:

greeting: emit<string>(value=|format("Hello, {name}! Welcome to Mélodium.", |map([|entry("name", name)]))) asText: stream<string>() write: writeTextLocal(path=output) logWritten: logInfoMessage(label="hello", message="greeting written to file") startup.trigger -> greeting.trigger,emit -> asText.block,stream -> write.text write.finished -> logWritten.trigger

std/flow::stream is the standard way to turn a one-shot value into a one-element stream when a downstream treatment expects a Stream rather than a Block.

The Stream side: the same name, repeated and numbered

emit<u128>(value=times) supplies the length to generate, which repeats name that many times as a Stream<string>. That stream fans out directly to logInfos, and separately through count and toString before being logged again:

howMany: emit<u128>(value=times) names: generate<string>(data=name) tally: count<string>() tallyText: toString<u128>() logNames: logInfos(label="greeting-stream") logCount: logInfos(label="greeting-count") startup.trigger -> howMany.trigger,emit -> names.length,stream -> tally.stream names.stream -> logNames.messages tally.count -> tallyText.value,into -> logCount.messages

count numbers elements starting at 1, so a run with --times 3 logs 1, 2, 3.

Functions vs treatments

|format, |map, |entry are pure functions: they have no ports and are called inline as values. emit, stream, generate, count are treatments: they have ports and live in the graph. Mélodium functions take positional arguments in their declared order, e.g. |format(format_string, entries), which does not always match alphabetical order, so when in doubt check a working example or validate with melodium check.

Dependencies

[dependencies] std = "0.10.3" # core flows, logging, data structures fs = "0.10.3" # local file I/O