Practical Geblang examples

Build command-line tools, HTTP services, typed data pipelines, database programs, concurrent workers, and tests. Every complete Geblang program shown here is checked and executed against the current language before publication.

These examples focus on tasks rather than isolated syntax. Each section explains when the feature is useful, shows a complete program or identifies an excerpt, and links to deeper documentation and repository examples.

Type-safe reuse

Build a reusable generic page

A generic class keeps one implementation while preserving the caller's concrete type. Here constructor inference produces Page<string>, first() returns ?string, and the generic binding remains available to instanceof at runtime.

This pattern applies to paginated API results, queues, repositories, caches, and domain-specific collections. Read the type-system guide or the complete generics example.

import io;

class Page<T> {
    list<T> items;
    int total;

    func Page(list<T> items, int total) {
        this.items = items;
        this.total = total;
    }

    func first(): ?T {
        return this.items.length() > 0
            ? this.items[0]
            : null;
    }
}

let users = Page(["Mara", "Devi"], 42);
string first = users.first() ?? "nobody";

io.println("${first} of ${users.total}");
io.println(users instanceof Page<string>);

Run it with geblang run page.gb. It prints Mara of 42 and then true.

Command-line applications

Parse options for a deploy command

The command library provides long and short options, defaults, boolean flags, required arguments, generated help, and subcommands. This is enough to build an operational CLI without adding an argument-parsing dependency.

After testing the script, geblang build packages it with the runtime as one executable for Linux, macOS, or Windows. See the full command example.

import cli.command as command;
import io;

let deploy = command.newCommand("deploy", "Deploy an application");
deploy.option(
    command.newOption("env", "string")
        .short("e")
        .default("dev")
        .help("target environment")
);
deploy.option(
    command.newOption("dry-run", "bool")
        .help("show the deployment plan")
);

let options = deploy.parse(["--env", "prod", "--dry-run"]);
io.println("environment: ${options["env"]}");
io.println("dry run: ${options["dry-run"]}");

Save it as deploy.gb and run geblang run deploy.gb. The parsed environment is prod and the dry-run flag is true.

Backend services

Create a typed JSON HTTP endpoint

The standard library HTTP server accepts a rich Request and returns a Response. Requests run concurrently on goroutines, while the blocking http.serve call keeps the process alive.

This low-level API is useful for a health endpoint, protocol adapter, webhook receiver, or custom server. For application routing, validation, dependency injection, authentication, and OpenAPI, follow the Gebweb web-development guide.

import http;
import sys;

func handle(Request request): Response {
    return http.jsonResponse({
        "method": request.method,
        "path": request.path,
    });
}

let port = sys.getenv("EXAMPLE_PORT") ?? "8080";
http.serve("127.0.0.1:${port}", handle);

Run geblang run server.gb, then request http://127.0.0.1:8080/status. The response is JSON containing the GET method and /status path.

Structured data

Deserialize nested JSON into typed classes

json.parseAs constructs the requested class and recursively converts nested fields. Application code receives a Customer, not an untyped dictionary that must be cast at every use.

The same serializer understands lists of classes, nullable fields, custom serialization hooks, and module-qualified types. See the JSON and data-formats reference.

import io;
import json;

class Address {
    string city;
    string postcode;

    func Address(string city, string postcode) {
        this.city = city;
        this.postcode = postcode;
    }
}

class Customer {
    string name;
    Address address;

    func Customer(string name, Address address) {
        this.name = name;
        this.address = address;
    }
}

let customer = json.parseAs(
    '{"name":"Mara","address":{"city":"London","postcode":"SE1"}}',
    Customer
);
io.println("${customer.name}: ${customer.address.city}");

SQL databases

Execute parameterized queries

A db.Connection wraps a connection pool. Queries accept named parameters rather than requiring values to be concatenated into SQL, and rows can be streamed and closed deterministically.

This example uses an in-memory SQLite database and needs no external service. Change the driver and DSN to use PostgreSQL or MySQL, then size the pool for the application's measured concurrency. The database reference covers transactions, prepared statements, streaming rows, and pool options.

import io;
import db;

let conn = db.Connection("sqlite", ":memory:");
defer conn.close();

conn.exec("create table users (id integer, name text)");
conn.exec(
    "insert into users (id, name) values (:id, :name)",
    {"id": 1, "name": "Mara"}
);

let rows = conn.query(
    "select name from users where id = :id",
    {"id": 1}
);
defer rows.close();

io.println(rows.first()["name"]);

Concurrent processing

Coordinate work through a typed channel

Goroutines provide parallel execution and a typed channel provides ownership and backpressure. Closing the channel tells the worker that no more jobs will arrive; awaiting the task retrieves its typed result.

Production workers can add more consumers, cancellation scopes, timeouts, and atomic metrics. The repository's worker-pool example demonstrates several concurrent workers.

import io;
import async;
import async.channel as channel;

let jobs = channel.Channel<int>(4);

let worker = async.run(func(): int {
    let total = 0;
    while (true) {
        let job = jobs.recv();
        if (job == null) {
            break;
        }
        total = total + (job as int);
    }
    return total;
});

for (int n = 1; n <= 4; n++) {
    jobs.send(n);
}
jobs.close();

io.println(await worker);

Expected failures

Use Result and Option without losing types

Result<T, E> represents an operation that can return either a typed value or a typed error. Option<T> represents a value that may be absent. Both make expected outcomes visible in function signatures.

Use ordinary exceptions for exceptional failures that should unwind control flow. The complete Option and Result example demonstrates mapping and inspection.

import io;
import option;
import result;

func price(string sku): result.Result<int, string> {
    dict<string, int> prices = {"book": 20, "pen": 3};
    if (!prices.hasKey(sku)) {
        return result.err("unknown product: ${sku}");
    }
    return result.ok(prices.get(sku));
}

func displayName(int id): option.Option<string> {
    dict<int, string> users = {1: "Mara"};
    return option.ofNullable(users.get(id));
}

io.println(price("book").unwrapOr(0));
io.println(displayName(99).unwrapOr("guest"));

Testing

Write tests with the included framework

The toolchain includes assertions, test discovery, setup and teardown hooks, data providers, mocks, HTTP test clients, and machine-readable reports. Tests are ordinary typed Geblang classes, so application code and tests use the same language rules.

Run a test tree with geblang test tests/. This standalone program invokes the same runner directly and prints 2 tests passed. See the testing reference.

import io;
import test;

func total(list<int> prices): int {
    let sum = 0;
    for (price in prices) {
        sum = sum + price;
    }
    return sum;
}

class PriceTest extends test.Test {
    @test
    func totalsSeveralPrices(): void {
        this.assertEquals(15, total([4, 5, 6]));
    }

    @test
    func emptyTotalIsZero(): void {
        this.assertEquals(0, total([]));
    }
}

let outcome = test.run(PriceTest);
io.println("${outcome["passed"]} tests passed");

Check, test, and build the program

Before deployment, run the static checker and formatter, execute the test suite, then build one self-contained binary. The target machine does not need a separate Geblang installation.

geblang check src/
geblang fmt src/
geblang test tests/
geblang build --entry main --out myapp