ferrule

worked examples

α1

worked examples


Parsing with Refinements

type Port = u16 where self >= 1 && self <= 65535

error Invalid { message: String }
domain ParseError = Invalid

function parsePort(s: String) -> Port effects [fail<ParseError>] {
    const trimmed = text.trim(s)
    const n = check number.parse_u16(trimmed) context { op: "parse_u16" }
    if n < 1 || n > 65535 {
        return err Invalid { message: "port out of range" }
    }
    return ok Port(n)
}

File I/O with Capabilities

error NotFound { path: Path }
error Denied { path: Path }
domain IoError = NotFound | Denied

function readAll(path: Path, cap fs: Fs) -> Bytes effects [fs, fail<IoError>] {
    const file = check fs.open(path)
    return check fs.readAll(file)
}

Region Management

function processWithArena(input: View<u8>) -> Output effects [alloc] {
    const arena = region.arena(1 << 20)
    defer arena.dispose()
    
    const temp = arena :: Vec.new<u8>()
    // ... process into temp ...
    
    return result.clone()    // clone to caller's region
}

function handleRequest(req: Request, cap fs: Fs) -> Response
    effects [fs, alloc, fail<AppError>]
{
    const arena = region.arena(1 << 20)
    defer arena.dispose()
    
    const file = arena :: check openFile(path, fs)
    const data = arena :: check file.readAll()
    const result = transform(data)
    
    return result.clone()
}

Traits and Impl

type Eq<T> = { eq: (T, T) -> Bool }
type Hash<T> = { hash: (T) -> u64 }
type Show<T> = { show: (T) -> String }

type UserId = { id: u64 }

impl Eq<UserId> {
    eq: function(a: UserId, b: UserId) -> Bool { return a.id == b.id },
}
impl Hash<UserId> {
    hash: function(u: UserId) -> u64 { return u.id },
}
impl Show<UserId> {
    show: function(u: UserId) -> String { return fmt"User({u.id})" },
}

function dedupe<T>(items: View<T>) -> Vec<T>
    where T impl Eq & Hash
    effects [alloc]
{
    // compiler resolves T.Eq and T.Hash
}

const unique = dedupe(users)

HTTP Client with Timeout

error Timeout { url: Url, ms: u64 }
error Network { message: String }
domain FetchError = Timeout | Network

function fetch(url: Url, cap net: Net, cap clock: Clock) -> Response
    effects [net, time, fail<FetchError>]
{
    scope timeout(Duration.seconds(30), clock) {
        const sock = check net.connect(url.host, url.port)
        return check request(sock, url)
    } on timeout {
        return err Timeout { url: url, ms: 30000 }
    }
}

Parallel Processing

function processAll(items: View<Item>, cap fs: Fs, cap clock: Clock) -> Vec<Result>
    effects [fs, time, alloc, suspend, fail<ProcessError>]
{
    scope timeout(Duration.seconds(30), clock) {
        scope parallel {
            items.map(function(item: Item) -> Result {
                return check processItem(item, fs)
            })
        }
    } on timeout {
        return err ProcessError.Timeout {}
    }
}

Compile-Time Table Generation

comptime function crc16Table(poly: u16) -> Array<u16, 256> {
    var table: Array<u16, 256> = [0; 256]
    var i: u32 = 0
    
    while i < 256 {
        var crc: u16 = u16(i) << 8
        var j: u32 = 0
        
        while j < 8 {
            if (crc & 0x8000) != 0 {
                crc = (crc << 1) ^ poly
            } else {
                crc = crc << 1
            }
            j = j + 1
        }
        
        table[i] = crc
        i = i + 1
    }
    
    return table
}

const CRC16_TABLE = comptime crc16Table(0x1021)

function crc16(data: View<u8>) -> u16 {
    var crc: u16 = 0xFFFF
    
    for byte in data {
        const idx = ((crc >> 8) ^ u16(byte)) & 0xFF
        crc = (crc << 8) ^ CRC16_TABLE[usize(idx)]
    }
    
    return crc
}

Context Ledgers

function handleRequest(req: Request, cap fs: Fs, cap net: Net) -> Response
    effects [fs, net, fail<AppError>]
{
    with context { request_id: req.id, user_id: req.user } in {
        const config = check loadConfig(fs)
        const data = check fetchExternal(req.url, net)
        
        // all errors within this block carry request_id and user_id
        return ok processData(data, config)
    }
}

Generics with Variance

// covariant: can only output T
type Producer<out T> = { get: () -> T }

// contravariant: can only input T  
type Consumer<in T> = { accept: (T) -> Unit }

// Producer<Cat> is assignable to Producer<Animal>
function printAnimal(p: Producer<Animal>, cap io: Io) -> Unit effects [io] {
    io.println(p.get().name)
}

const catProducer: Producer<Cat> = { get: function() -> Cat { return myCat } }
printAnimal(catProducer, io)  // OK: Cat is Animal

Conditional Types

type Unwrap<T> = if T is Result<infer U, infer E> then U else T

type UnwrappedConfig = Unwrap<Result<Config, Error>>  // Config

type ElementType<A> = if A is Array<infer T, infer N> then T else Never

type StringElement = ElementType<Array<String, 10>>  // String

Higher-Order Functions with Effect Spread

function map<T, U, E>(arr: View<T>, f: (T) -> U effects [fail<E>, ...]) -> Vec<U>
    effects [alloc, fail<E>, ...]
{
    const result = builder.new<U>(region.current())
    for item in arr {
        builder.push(result, f(item))
    }
    return builder.finish(result)
}

// caller's effects include alloc + whatever the passed function has
function processAll(items: View<Item>, cap fs: Fs) -> Vec<Output>
    effects [alloc, fs, fail<IoError>]
{
    return map(items, function(item: Item) -> Output effects [fs, fail<IoError>] {
        return check fs.process(item)
    })
}

On this page