← Yugo home

Yugo by Example

Just a note: some of the below code is already not following “good” practices as the language updates to have better ergonomics. I will update these examples as the core language stabilizes.

protocol Measurement {
    func measure() -> Int
    func is_zero() -> Bool { return self.measure() == 0 }
}

protocol CopyableMeasurement: Measurement, Copyable {}

struct Reading: CopyableMeasurement {
    let value: Int
    func measure() -> Int { return self.value }
}

func total<T: Measurement, const N: Int>(_ values: borrowing [T; N]) -> Int {
    var sum = 0
    for index in 0..<N { sum += values[index].measure() }
    return sum
}

func main() -> Int {
    let values = [Reading(value: 60), Reading(value: 7)]
    let answer = total(values)
    assert(!values[0].is_zero())
    print(answer)
    return answer
}

Views

import core
import text

// This function was made before the Char type was avaliable.
func tokens(_ src: borrowing text.View) -> core.Buffer<ReadSpan<u8>> from src {
    let bytes = text.bytes(src)
    var words = core.buffer<ReadSpan<u8>>(capacity: 4)
    var start = 0
    var index = 0
    while index <= bytes.count {
        if index == bytes.count || bytes[index] == u8(' ') { 
            if index > start {
                if let word = core.slice_read<u8>(bytes, start: start, count: index - start) {
                    words.append(word)
                }
            }
            start = index + 1
        }
        index += 1
    }
    return consume words
}

// A scoped struct is one that's allowed to have borrowed views. It is less flexible than a regular struct.
// I am still working out the semantics on scoped structs.
scoped struct Longest {
    let word: ReadSpan<u8>
    let position: Int
}

// The longest word keeps viewing the same text; the buffer can be dropped first.
func longest(_ words: borrowing core.Buffer<ReadSpan<u8>>) -> Longest from words {
    let view = words.span()
    var best = 0
    for position in 1..<view.count {
        if view[position].count > view[best].count { best = position }
    }
    return Longest(word: view[best], position: best)
}

Closures and Explicit Captures

import core

func apply<F: Call<(Int) -> Int>>(_ f: borrowing F, _ n: Int) -> Int { return f(n) }
func mutate<F: ?Owned & CallMut<() -> Void>>(_ f: inout F) { f() }

func main() -> Int {
    let add = { [offset = 60] (n: Int) -> Int in return offset + n }
    assert(apply(add, 7) == 67)
    // The same callback, its signature taken from apply's bound.
    let offset = 60
    assert(apply({ [offset] n in offset + n }, 7) == 67)
    let erased = Ref<any Call<(Int) -> Int>>(borrow add)
    assert(erased.pointee(7) == 67)

    var value = 60
    var increment = { [target = inout value] mutating () -> Void in target.pointee += 7 }
    mutate(&increment)
    print(value)

    let concrete = core.unique({ [value = value] consuming () -> Int in return value })
    let once = Unique<any CallOnce<() -> Int>>(consume concrete)
    assert((consume once)() == 67)
    return 0
}

Composable Generators

import core

// Lazy sequences with generators. A call creates a suspended frame; each next() runs
// it to its next `yield`. `for x in consume` drives any core.Iterator, and dropping a
// generator early runs the cleanup of wherever it is suspended.
struct Lantern { let city: StaticString; let toll: Int; deinit { print(self.city) } }

func fibonacci(below limit: Int) -> yields Int {
    var a = 0
    var b = 1
    while a < limit {
        yield a
        let next = a + b
        a = b
        b = next
    }
}

// Generators compose: this one consumes any iterator of Int.
func evens<I: core.Iterator>(_ source: I) -> yields Int where I.Element == Int {
    for value in consume source {
        if value % 2 == 0 { yield value }
    }
}

// Borrowed parameters stay borrowed while the generator lives.
func tour(_ stops: borrowing [StaticString; 3]) -> yields Lantern {
    defer { print("home") }
    for i in 0..<3 { yield Lantern(city: stops[i], toll: i + 5) }
}

func main() -> Int {
    var total = 0
    for value in consume evens(fibonacci(below: 100)) { total += value }
    var numbers = fibonacci(below: 100)
    while case .some(_) = numbers.next() { total += 1 }
    let stops: [StaticString; 3] = ["mostar", "izmir", "sarajevo"]
    var visited = 0
    for lantern in consume tour(stops) {
        total += lantern.toll
        visited += 1
        if visited == 2 { break }
    }
    return total
}

Compile-Time Reflection and Serialization

// Compile-time metadata selects ordinary typed field reads, stores and construction.
attribute BalkanWire(_ slot: Int)

public struct Sarajevo: ReflectConstructible {
    @BalkanWire(1)
    private let first: Int
    @BalkanWire(0)
    private var second: Int
}

func belgrade_encode<T: Reflectable, const N: Int>(_ value: borrowing T,
    into output: inout [Int; N]) {
    const if #is_struct(T) {
        const for nis in 0..<#field_count(T) {
            const if #field_type(T, nis) == Int {
                const slot = #field_attribute_argument(T, nis, 0, 0)
                output[slot] = #field(value, nis)
            } else {
                #error("BalkanWire requires Int fields")
            }
        }
    } else {
        #error("BalkanWire requires a struct")
    }
}

func belgrade_decode<T: ReflectConstructible, const N: Int>(_ input: borrowing [Int; N]) -> T {
    const if #is_struct(T) {
        let builder = #builder(T)
        const for nis in 0..<#field_count(T) {
            const if #field_type(T, nis) == Int {
                const slot = #field_attribute_argument(T, nis, 0, 0)
                #field(builder, nis) = input[slot]
            } else {
                #error("BalkanWire requires Int fields")
            }
        }
        return #finish(builder)
    } else {
        #error("BalkanWire requires a struct")
    }
}

func mostar_equal<T: Reflectable>(_ first: borrowing T, _ second: borrowing T) -> Bool {
    const if #is_struct(T) {
        const for nis in 0..<#field_count(T) {
            if #field(first, nis) != #field(second, nis) { return false }
        }
        return true
    } else {
        #error("mostar_equal requires a struct with comparable fields")
    }
}

const func istanbul_table(_ nis: Int) -> [Int; 3] {
    var result = [0; 3]
    for izmir in 0..<3 { result[izmir] = nis + izmir }
    return result
}
const ankara = istanbul_table(65)
const func BelgradeArray(_ element: type, count: Int) -> type {
    return #array_type(element, count)
}
const Tuzla = BelgradeArray(Int, count: 2)
enum Skopje { case ready(Int); case waiting }

func main() -> Int {
    let input = [7, 67]
    let sarajevo = belgrade_decode<Sarajevo, 2>(input)
    var output: Tuzla = [0; 2]
    belgrade_encode<Sarajevo, 2>(sarajevo, into: &output)
    let copy = belgrade_decode<Sarajevo, 2>(output)
    assert(mostar_equal<Sarajevo>(sarajevo, copy))
    assert(output[0] == 7 && output[1] == 67)
    const for nis in 0..<#field_count(Sarajevo) {
        print(#field_name(Sarajevo, nis))
        print(#field_offset(Sarajevo, nis))
    }
    print(#size_of(Sarajevo))
    let skopje = #case(Skopje, 0, ankara[2])
    print(#case_name(of: skopje))
    assert(#case_index(of: skopje) == 0)
    return 67
}

Capability Effects and Custom Handlers

import core
import system

// There are affects for Clock, IO, and of course you can define your own.
effect Audit {
    mutating func record(_ seconds: Int)
}
struct CountingAudit: Audit {
    var records: Int
    var lastSeconds: Int
    mutating func record(_ seconds: Int) {
        self.records += 1
        self.lastSeconds = seconds
    }
}

// A clock stopped at one second, which this example moves by providing another.
struct StoppedClock: system.Clock {
    let seconds: Int
    func monotonic() -> Result<system.Time, system.Error> {
        return .ok(system.Time(seconds: self.seconds, nanoseconds: 0))
    }
    func wall() -> Result<system.Time, system.Error> { return self.monotonic() }
    func sleep(seconds: Int, nanoseconds: Int) -> Result<(), system.Error> { return .ok(()) }
    func share() -> Shared<any system.Clock & Send & Sync> {
        return Shared<any system.Clock & Send & Sync>(core.shared(StoppedClock(seconds: self.seconds)))
    }
}

struct Lease {
    let token: UInt
    let granted: Int
}
func now() -> Int uses system.Clock {
    switch system.Clock.monotonic() {
    case .ok(let time): return time.seconds
    case .err(_): return 0
    }
}
func grant() -> Lease uses system.Clock, system.Entropy, Audit {
    var bytes: [u8; 8] = [0; 8]
    _ = system.Entropy.fill(core.writeSpan(&bytes))
    var token: UInt = 0
    for i in 0..<8 { token = (token << 8) | UInt(bytes[i]) }
    let lease = Lease(token: token, granted: now())
    Audit.record(lease.granted)
    return lease
}
func expired(_ lease: borrowing Lease, ttl: Int) -> Bool uses system.Clock {
    return now() - lease.granted >= ttl
}

func main() -> Int {
    var audit = CountingAudit(records: 0, lastSeconds: 0)
    var answer = 0
    provide system.Clock = StoppedClock(seconds: 60), Audit = &audit {
        let lease = grant()
        print(expired(lease, ttl: 7))                       // false
        provide system.Clock = StoppedClock(seconds: 67) {
            print(expired(lease, ttl: 7))                   // true
            answer = now()
        }
    }
    print(audit.records)                                    // 1
    return answer
}
import core

// Proof declarations and ghost state are erased from generated programs.
spec func successor(_ value: MathInt) -> MathInt { return value + 1 }
proof func successorGrows(_ value: MathInt)
    ensures successor(value) > value
{ assert(successor(value) == value + 1) }

@verify
func fill<const N: Int>(_ values: inout [Int; N])
    modifies values
    ensures forall (i: MathInt) { 0 <= i && i < MathInt(N) ==> values[i] == 67 }
{
    for i in 0..<N
        invariant forall (j: MathInt) { 0 <= j && j < MathInt(i) ==> values[j] == 67 }
    { values[i] = 67 }
}

// Returns the first index whose element is >= key, or values.count.
@verify
func lowerBound(_ values: ReadSpan<Int>, _ key: Int) -> Int
    requires forall (i: MathInt, j: MathInt) {
        0 <= i && i <= j && j < MathInt(values.count) ==> values[i] <= values[j]
    }
    ensures 0 <= result && result <= values.count
    ensures forall (i: MathInt) { 0 <= i && i < MathInt(result) ==> values[i] < key }
    ensures forall (i: MathInt) {
        MathInt(result) <= i && i < MathInt(values.count) ==> key <= values[i]
    }
{
    var lower = 0
    var upper = values.count
    while lower < upper
        invariant 0 <= lower && lower <= upper && upper <= values.count
        invariant forall (i: MathInt) { 0 <= i && i < MathInt(lower) ==> values[i] < key }
        invariant forall (i: MathInt) {
            MathInt(upper) <= i && i < MathInt(values.count) ==> key <= values[i]
        }
        decreases MathInt(upper) - MathInt(lower)
    {
        let middle = lower + (upper - lower) / 2
        if values[middle] < key { lower = middle + 1 }
        else { upper = middle }
    }
    return lower
}

@verify
func main() -> Int ensures result == 67 {
    var values = [0, 0, 0]
    fill<3>(&values)
    ghost let before = snapshot(values)
    proof { successorGrows(MathInt(values[0])) }
    let found = lowerBound(core.readSpan<Int, 3>(values), 67)
    proof { assert(snapshot(values) == before) }
    return values[found]
}

Hash Maps (and other algorithms)

import core

func precedes(_ first: borrowing Int, _ second: borrowing Int) -> Bool { return first < second }

// Count repeated city visits, queue work in arrival order, then schedule counts
// by priority. The explicit seed makes this workload reproducible.
func main() {
    var visits = core.hash_map_seeded<StaticString, Int>(capacity: 0, seed: core.HashSeed(low: 67, high: 7))
    var pending = core.deque<StaticString>(capacity: 0)
    let cities = ["Belgrade", "Ankara", "Belgrade", "Sarajevo", "Ankara", "Belgrade"]
    for city in borrow cities { pending.push_back(city.pointee) }
    for city in consume pending {
        var count = 0
        switch visits.get(city) { case .none: count = 0; case .some(let value): count = value.pointee }
        visits.insert(city, count + 1)
    }
    var distinct = core.hash_set<StaticString>(capacity: 0)
    var work = core.priority_queue<Int, (borrowing Int, borrowing Int) -> Bool>(capacity: 0, before: precedes)
    for entry in borrow visits {
        distinct.insert(entry.pointee.0)
        work.push(entry.pointee.1)
    }
    assert(distinct.count() == 3)
    var total = 0
    for count in consume work { print(count); total += count }
    assert(total == 6)
    let key = "Belgrade"
    switch visits.get(key) { case .none: assert(false); case .some(let count): assert(count.pointee == 3) }
    print(67)
}

Quine (Program printing itself)

let sarajevo: StaticString = #"""
func main() {
    print("let sarajevo: StaticString = #\"\"\"")
    print(sarajevo)
    print("\"\"\"#\n")
    print(sarajevo)
}
"""#

func main() {
    print("let sarajevo: StaticString = #\"\"\"")
    print(sarajevo)
    print("\"\"\"#\n")
    print(sarajevo)
}