Examples

These are some longer examples than on the home page.

A complex loop

This example is intended to demonstrate how continue is translated, as well as breaking out of a function from within a loop.

import other.module (name1, name2);

func my_func(a: [Int], b: Str) -> Value {
    x = 1;
    y = 0;
    for i in a ++ [5, 6, 7] {
        y += i;
        x *= y;
        func flip(x, y) {
            return (x:y, y:x);
        }

        if i == 5 {
            continue;
        }
        (x, y) = flip(x, y);

        if x > 100 {
            return x;
        }
    }
    z = y + x;
    return z;
}

A search syntax

A language describing searching would typically allow arbitrarily nested parentheses, but that requires a feature, cyclic types, that won’t be immediately available. We can still describe and even evaluate fairly complex expressions, though.

;; In a Union type, a tag, here "name" or "literal" determines what the variant stores.
type Value_Expr := name ~ Str | literal ~ Int | #zero

;; The sharp means the variant type is the "unit" type, which has only one value.

let example_value_1 := name ~ "age"
let example_value_2 := name ~ "count"
let example_value_3 := literal ~ 55

;; We use a Tuple to store possible arguments for a binary relation.
type Binary_Relation := (
    left: Value_Expr,
    right: Value_Expr,
)

;; And another union distinguishes our types of search operators.
type Search_Operator :=
    equals ~ Binary_Relation |
    not_equals ~ Binary_Relation |
    positive ~ Value_Expr

type Search_Expression := [Search_Operator]

let example := [
    equals ~ (left: name ~ "age", right: literal ~ 55),
    positive ~ name ~ "count",
]

func evaluate(expr, names : {Str: Int}) {
    let result := true
    let eval := eval_val(names:, ...)
    for op in expr {
        switch op {
        case equals ~ (left:, right:):
            let result := result and eval(expr: left) == eval(expr: right)
        case not_equals ~ (left:, right:):
            let result := result and eval(expr: left) != eval(expr: right)
        case positive ~ val:
            let result := result and eval_val(val) > 0
        }
    }
    return result
}

func eval_val(expr, names : {Str: Int}) {
    switch expr {
    case name ~ name:
        return names[name]
    case literal ~ num:
        return num
    case #zero:
        return 0
    }
}

A date-time library

This is a comprehensive example of Tenet code and shows what it can do even within current limitations.

;; I wanted to put together an example tenet model based on an earlier project I had done
;; that implemented an organizer. That, of couse, depends pretty heavily on dates and times,
;; and I realized I'll need at least rudimentary support for it. Poking around, it seems
;; like a naive (in that it doesn't handle timezones) implementation of iso8601 gets you
;; 99% of what you need. It also stresses our ability to do math and such, even if the math
;; is suprisingly easy. Those monks were some clever bastards!

;; The two epochs here are posix, which most people are familiar with, and 'CE' meaning
;; Jan 1 of year 1 of the common era. Actual dates are implemented according to the ISO 8601
;; standard, which is the proleptic Gregorian calendar, except that years are astronomical,
;; year +0001 corresponds with 1 AD, year 0000 corresponds with 1 BC, -0001 is 2 BC, etc.

;; Timezone support would just be adding more fields.

;; Define an epoch date; if we want to improve our date handling
;; methods later, they can be backwards compatible.

type Iso8601Date = (year: Int, month: Int, day: Int)
type Iso8601Time = (hour: Int, minute: Int, second: Int)

type Duration = seconds ~ Int

;; type Iso8601Simple = Iso8601Date * Iso8601Time
type Iso8601Simple = (
    year: Int,
    month: Int,
    day: Int,
    hour: Int,
    minute: Int,
    second: Int,
)

type DateTime =
    epoch_ce ~ Int    |  ;; 0001-Jan-01 = 0
    epoch_posix ~ Int |  ;; 1970-Jan-01 = 0
    iso8601 ~ Iso8601Simple

;;
;; Datetime arithmetic
;;

func dt_plus_dur(left: DateTime, right: Duration) {
    let dt_seconds = as_epoch_ce(left)
    let dur_seconds = as_dur_seconds(right)
    return epoch_ce ~ (dt_seconds + dur_seconds)
}

func dt_minus_dur(left: DateTime, right: Duration) {
    let dt_seconds = as_epoch_ce(left)
    let dur_seconds = as_dur_seconds(right)
    return epoch_ce ~ (dt_seconds - dur_seconds)
}

func dt_minus_dt(left:DateTime, right:DateTime) {
    let left_seconds = as_epoch_ce(left)
    let right_seconds = as_epoch_ce(right)
    return dur_seconds ~ (left_seconds - right_seconds)
}

func dur_plus_dur(left: Duration, right: Duration) {
    return dur_seconds ~ (as_dur_seconds(left) + as_dur_seconds(right))
}

;;
;; Conversion between forms.
;;

func as_iso8601(value: DateTime) {
    switch value {
    case epoch_posix ~ epoch_seconds:
        return epoch_posix_to_iso8601(epoch_seconds)
    case epoch_ce ~ epoch_seconds:
        return epoch_ce_to_iso8601(seoncds)
    case iso8601 ~ datetime:
        return datetime
    }
}

func as_epoch_ce(value: DateTime) {
    switch value {
    case epoch_posix ~ epoch_seconds:
        return epoch_posix_to_ce(epoch_seconds)
    case epoch_ce ~ epoch_seconds:
        return epoch_seconds
    case iso8601 ~ datetime:
        return iso8601_to_epoch_ce(datetime)
    }
}

func as_epoch_posix(value: DateTime) {
    switch value {
    case epoch_posix ~ epoch_seconds:
        return epoch_seconds
    case epoch_ce ~ epoch_seconds:
        return epoch_ce_to_posix(epoch_seconds)
    case iso8601 ~ datetime:
        return iso8601_to_epoch_posix(datetime)
    }
}

func as_dur_seconds(value: Duration) {
    return value ? seconds
}

;;
;; Internal DateTime conversion functions
;;

func epoch_ce_to_iso8601(epoch_seconds: Int) {
    let (epoch_days, day_seconds) = split_epoch_seconds(epoch_seconds:)
    let (year, month, day) = epoch_days_to_year_month_day(epoch_days:)
    let (hour, minute, second) = seconds_to_hour_minute_second(day_seconds:)
    return (year:, month:, day:, hour:, minute:, second:)
}

func epoch_ce_to_posix(epoch_seconds: Int) {
    return epoch_seconds + ce_to_posix_offset
}

func epoch_posix_to_ce(epoch_seconds: Int) {
    return epoch_seconds - ce_to_posix_offset
}

func epoch_posix_to_iso8601(epoch_seconds: Int) {
    return epoch_ce_to_iso8601(epoch_posix_to_ce(epoch_seconds))
}

func iso8601_to_epoch_ce(datetime: Iso8601Simple) {
    let (year, month, day, hour, minute, second) = datetime
    let epoch_days = year_month_day_to_epoch_days(year:, month:, day:)
    let day_seconds = time_of_day_to_seconds(hour:, minute:, second:)
    return combine_epoch_date(epoch_days:, day_seconds:)
}

func iso8601_to_epoch_posix(datetime: Iso8601Simple) {
    return epoch_ce_to_posix(iso8601_to_epoch_ce(datetime))
}

let ce_to_posix_offset = 719162 * seconds_in_day

;;
;; Converting seconds from an epoch to epoch days and time of day
;;

let seconds_in_day = 24 * 60 * 60

func split_epoch_seconds(epoch_seconds: Int) {
    let (div, mod) = div_mod(epoch_seconds, seconds_in_day)
    return (epoch_days: div, day_seconds: mod)
}

func split_epoch_ce(value: DateTime) {
    return split_epoch_seconds(as_epoch_ce(value))
}

func combine_epoch_date(epoch_days: Int, day_seconds: Int) {
    return seconds_in_day * epoch_days + day_seconds
}

func seconds_to_hour_minute_second(seconds: Int) {
    let (div: minutes, mod: second) = div_mod(seconds, 60)
    let (div: hour, mod: minute) = div_mod(minutes, 60)
    return (hour:, minute:, second:)
}

func hour_minute_second_to_seconds(hour:Int, minute:Int, second:Int) {
    return 3600 * hour + 60 * minute + second
}

;;
;; Converting days to y-m-d
;;

;; Note: year, month, day are all 0-based in calculations, but 1-based when exposed to the user.
;; Epochs are at 0 seconds.

func month_start(month: Int, is_leap_year: Boolean) {
    if is_leap_year {
        let starts = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
    } else {
        let starts = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
    }
    return starts[month]
}

let days_in_quad_century = 146097
let days_in_century = 36524
let days_in_quad_year = 1461
let days_in_std_year = 365

func epoch_day_to_year_month_day(epoch_days: Int) -> Iso8601Date {
    let (div: quad_century, mod: quad_centry_day) = div_mod(epoch_days, days_in_quad_century)

    ;; The first, second and third century have 36524,
    ;; the fourth has 36525.
    let (div: century_p, mod: century_day) = div_mod(quad_century_day, days_in_century)

    ;; Thus, century_p may be 0, 1, 2, 3 or 4.
    ;; 4 is only reported on the last day of a "leap century."
    if century_p == 4 {
        let century = 3
    } else {
        let century = century_p
    }

    let (div: quad_year, mod: quad_year_day) = div_mod(century_day, days_in_quad_year)
    ;; As before, year will be 4 on dec 31 of the leap year
    let (div: year_p, mod: year_day) = div_mod(quad_year_day, days_in_year)
    if year_p == 4 {
        let year = 3
    } else {
        let year = year_p
    }

    ;; Real day of the year, allowing Dec 31 on a leap year
    if century_p == 4 or year_p == 4 {
        let year_day = 365
    } else {
        let year_day = year_day_p
    }
    let is_leap_year = century_p == 4 or year_p > 2 or quad_year == 24

    ;; This clever approximation came from python's datetime.
    let month_p = (year_day + 50) // 32

    ;; We then get when the estimated month starts...
    let month_start_day = month_start(month:month_p, is_leap_year:)

    ;; ... which may be over by one
    if month_start_day > year_day {
        let month = month_p - 1
        let month_start_day = month_start(month:, is_leap_year:)
    } else {
        let month = month_p
    }
    let month_day = year_day - month_start_day

    return (year: 400 * quad_century + 100 * century + year + 1,
            month: month + 1, day: month_day + 1)
}

;;
;; Convert year-month-day to seconds after the epoch.
;;

func year_month_day_to_epoch_days(year:Int, month:Int, day:Int) -> Index {
    let (div: quad_century, mod: quad_century_year) = div_mod(year - 1, 400)
    let (div: century, mod: century_year) = div_mod(quad_century_year, 100)
    let (div: quad_years, mod: quad_year) = div_mod(century_year, 4)
    let is_leap_year = quad_year == 3 and (century_year != 99 or century == 3)
    let month_start_day = month_start(month:, is_leap_year:)

    return (day - 1 +
            month_start_day +
            days_in_year * quad_year +
            days_in_quad_year * quad_years +
            days_in_century * century +
            days_in_quad_century * quad_century)
}

;;
;; Month name functions
;;

type Month = #jan | #feb | #mar | #apr | #may | #jun | #jul | #aug | #sep | #oct | #nov | #dec

func month_from_index(index:Int) -> Month {
    return [#jan, #feb, #mar, #apr, #may, #jun, #jul, #aug, #sep, #oct, #nov, #dec][index - 1]
}

func index_of_month(month: Month) -> Int {
    return {
        #jan: 1,
        #feb: 2,
        #mar: 3,
        #apr: 4,
        #may: 5,
        #jun: 6,
        #jul: 7,
        #aug: 8,
        #sep: 9,
        #oct: 10,
        #nov: 11,
        #dec: 12
    }[month]
}

;;
;; Days of the week and business day calculations
;;

type DayOfWeek = #sun | #mon | #tue | #wed | #thu | #fri | #sat

func day_of_week_from_index(index: Int) -> DayOfWeek {
    return [#sun, #mon, #tue, #wed, #thu, #fri, #sat][index]
}

func index_of_day_of_week(day: DayOfWeek) -> Int {
    return {
        #sun: 0,
        #mon: 1,
        #tue: 2,
        #wed: 3,
        #thu: 4,
        #fri: 5,
        #sat: 6
    }[day]
}

;; Get the index of the day of week, where Sunday is 0.

func day_of_week_of_epoch_days(epoch_days: Int) {
    ;; Jan 1, 0001 is a Saturday.
    return (6 + epoch_days) % 7
}

func day_of_week_date_time(value: DateTime) {
    return day_of_week_of_epoch_days(split_epoch_ce(value).epoch_days)
}

func closest_business_day_forwards(value: Int) {
    switch day_of_week_of_epoch_days(epoch_days:value) {
    case 0:
        return value + 1
    case 6:
        return value + 2
    default:
        return value
    }
}

func closest_business_day_backwards(value: Int) {
    switch day_of_week_of_epoch_days(epoch_days:value) {
    case 0:
        return value - 2
    case 6:
        return value - 1
    default:
        return value
    }
}


;; Increments a time by a number of business days, ensuring that the specific number of business days exist
;; within the interval. Incrementing by 0 will force the time to the open of the next business day.
;; open-seconds and close-seconds allow you to pick a window during which business is conducted, so it
;; handles "lunch hours" reasonably well.

func business_days_after(point:DateTime, days_change:Int, open_seconds:Int, close_seconds:Int) {
    let (epoch_days, day_seconds) = split_epoch_ce(point)
    let after_hours = day_seconds > close_seconds
    let before_hours = day_seconds < open_seconds
    if after_hours {
        let day = 1 + epoch_days
    } else {
        let day = epoch_days
    }
    if after_hours or before_hours {
        let seconds_p = open_seconds
    } else {
        let seconds_p = day_seconds
    }

    ;; Bump our answer to the next business day if we've landed on a weekend.
    let day = closest_business_day_forwards(day)

    ;; Figure out how many weeks we're adding, and the remaining days
    let (div: weeks_change, mod: days_change) = div_mod(days_change, 5)
    let day += 7 * weeks_change + days_change

    ;; And now bump our answer to the next business day again.
    let day = closest_business_day_forwards_p(day)

    ;; And then combine the day and time.
    return epoch_ce ~ combine_epoch_date(epoch_days:day, day_seconds:seconds_p)
}

;; Decrements a time by a number of business days, ensuring that the specific number of business days exist
;; within the interval. Decrementing by 0 will force the time to the close of the previous business day.
;; open-seconds and close-seconds allow you to pick a window during which business is conducted, so it
;; handles "lunch hours" reasonably well.
func business_days_prior(point:DateTime, days_change:Int, open_seconds:Int, close_seconds:Int) {
    let (epoch_days, day_seconds) = split_epoch_ce(point)

    let after_hours = day_seconds > close_seconds
    let before_hours = day_seconds < open_seconds
    if before_hours {
        let day = epoch_days - 1
    } else {
        let day = epoch_days
    }
    if after_hours or before_hours {
        let seconds_p = close_seconds
    } else {
        let seconds_p = day_seconds
    }

    ;; Bump our answer to the previous business day if we've landed on a weekend.
    let day = closest_business_day_backwards(day)

    ;; Figure out how many weeks we're subtracting, and the remaining days
    let (div: weeks_change, mod: days_change) = div_mod(days_change, 5)
    let day += 7 * weeks_change + days_change

    ;; And now bump our answer to the previous business day again.
    let day = closest_business_day_backwards(day)

    ;; And then combine the day and time.
    return epoch_ce ~ combine_epoch_date(epoch_days:day, day_seconds:seconds_p)
}