Quick tourΒΆ

This is simple but absolutely useless program that help to illustrate some of the most important Arza features

import io (print)
import affirm
include list (range_to as range)
import seq

interface GetSet(I) =
    get(I, key)
    set(I, key, value)

interface Storage(I) =
    contains(I, key)
    use get(I, key)
    use set(I, key, value)

interface StorageKey(I) =
    use get(storage, I)
    use set(storage, I, value)

interface Serializable(I) =
    serialize(storage of I, serializer)

interface Serializer(I) =
    use serialize(storage, serializer of I)

//abstract type

type Nothing
def str(s of Nothing) = "(novalue)"

type First
type Second
type Third

// parens here mean that this is record type
// it will inherit standart behavior like at, put, has, ==, != ...etc
type Collection()

type Single is Collection
    init(s) = s.{first = Nothing}

def set(s of Single, k of First, val) =
    s.{first = val}

def get({first} of Single, k of First) = first

def size(s of Single) = 1


type Pair is Single
    init(p) =
        super(Single, p).{second = Nothing}

// define trait and apply it immidiately to Pair
trait TPair(T) for Pair =
    def set(s of T, k of Second, val) =
        s.{second = val}
    def get(s of T, k of Second) = s.second

// cast Pair to its supertype Single
def size(s of Pair) = size(s as Single) + 1


type Triple is Pair
    init(t) =
        super(Pair, t).{third = Nothing}

trait TTriple(T) for Triple =
    def set(s of T, k of Third, val) =
        s.{third = val}
    def get(s of T, k of Third) = s.third

def size(s of Triple) = size(s as Pair) + 1


// lets create completely unrelated type to Single :> Pair :> Triple
// but use traits for pair and triple to avoid code dublication

type SecondAndThird is Collection (second, third)

instance TPair(SecondAndThird)

instance TTriple(SecondAndThird)

def size(s of SecondAndThird) = 2


type Dictionary is Collection (items)
    init(d) =
        d.{ items = {} }

// do not subtype from Dictionary but use its structure
type Array is Collection
    (size, ...Dictionary)
    init(s, size) =
        //silly idea of arrays implemented as lists
        s.{items = seq:consmany([], Nothing, size), size=size}

// create anonymous trait and apply it serially to list of types
trait (T) for [Dictionary, Array] =
    def size({items} of T) = len(items)

trait TStorageWithItems(T, KeyType) =
    def set(s of T, k of KeyType, val) =
            items = @.{ (k) = val }

    def get(s of T, k of KeyType) = s.items.[k]

instance TStorageWithItems(Dictionary, Symbol)
instance TStorageWithItems(Dictionary, StorageKey)
instance TStorageWithItems(Array, Int)

//redefine array size to avoid list
override (prev) size(a of Array) =

type InvalidKeyError is Error

// redefine set function for Array
// to avoid index out of range problems
// prev is previous method
// override expression do not adds this method to specific signature set(Array, Int)
// but replaces it completely
// so only indexes > 0 and < size will be accepted
override (prev) set(arr of Array, i of Int, val)
    | ({size}, i, val) when i >= 0 and i < size = prev(arr, i, val)
    | (_, i, _) = throw InvalidKeyError(i)

def ==(d of Dictionary, m of Map) = d.items == m

def ==(m of Map, d of Dictionary) = d.items == m


// define method for parent subtype
def contains(s of Collection, k) =
    let val =
        // if method is not implemented for specific key it will throw NotImplementedError exception
        // we catch it and tell user key not exists
            get(s, k)
            | e of NotImplementedError = Nothing
            | e = throw e

    match val
        | type Nothing = False
        | _ = True

/// there are other more preferable way to implement such behavior
//// this method will be called if specific get(Storage, Key) was undefined
//// for example get(Single, Second) will otherwise crash with not implemented error
def get(s of Collection, k of Any) = Nothing
// after this declaration NotImplementedError will never be thrown in get generic


//ensure that all types are satisfiing interface
describe (Dictionary, Array, Pair, Triple, Single, SecondAndThird) as (Storage, GetSet)

def serialize({first, second} of Pair, serializer of Dictionary) =
        |> set(_, First, first)
        |> set(_, Second, second)

def serialize(s of Triple, serializer of Dictionary) =
        |> serialize(s as Pair, _)
        |> set(_, Third, s.third)

def serialize(s of Array, serializer of List) =
    seq:concat(s.items, serializer)

describe (Triple, Pair) as Serializable
describe Dictionary as Serializer
describe Array  as Serializable

fun array_map({items} as arr, fn) =
    // lets pretend this Array implementation is not based on lists
    // and write some ridiculously slow map implementation
    // there are zip in seq module
    // but lets define our own here
    fun zip(seq1, seq2)
        | (x::xs, y::ys) = (x, y) :: zip(xs, ys)
        | (_, _) = []

    // same with foldl but here we call set directly
    fun fold
        | ([], acc) = acc
        | (hd::tl, acc) =
                (index, item) = hd
                new_acc = set(acc, index, fn(item))
                fold(tl, new_acc)

        arrsize = size(arr)
        indexes = range(arrsize)
            seq:zip(indexes, items),

fun test() =
        single = Single()
            |> set(_, First, #one)

        pair = Pair()
            |> set(_, First, #one)
            |> set(_, Second, #two)

        triple = Triple()
            |> set(_, First, #one)
            |> set(_, Second, #nottwo)
            |> set(_, Third, #three)

        arr = Array(10)
            |> set(_, 0, #zero)
            |> set(_, 5, #five)
            |> set(_, 8, #eight)

        dict =
            |> set(_, #one, 0)
            // update
            |> set(_, #one, 1)
            |> set(_, #two, 2)
            get(single, First),
            get(pair, First),
            get(triple, First),

        affirm:is_not_equal(get(triple,  Second), get(pair, Second))

            dict1 = dict.{ items = @.{three = [1,2,3]} }
            //deeply nested update
            dict = dict1.{items.three = 0::@}
            affirm:is_true(dict == {one=1, two=2, three=[0,1,2,3]})

        // this is old dict value
        affirm:is_true(dict == {one=1, two=2})
            // lets try some function composition
            fn = (`++` .. "Val->") << str
            // this is equivalent to
            fn2 = (x) -> "Val->" ++ str(x)
            //where (args) -> expression is lambda expression
            arr_str = array_map(arr, fn)
            arr_str2 = array_map(arr, fn2)
            affirm:is_equal(arr_str.items, arr_str2.items)

            dict_ser = serialize(triple, dict)
            affirm:is_true(dict_ser == {(First) = #one, (Second) = #nottwo, (Third) = #three,  one=1, two=2})

            // using func like infix operator
            affirm:is_true(dict_ser `contains` First)
            affirm:is_true(dict_ser `contains` #two)

        affirm:is_true(single `contains` First)
        affirm:is_false(single `contains` Second)
        affirm:is_true(pair `contains` Second)
        affirm:is_true(triple `contains` Third)

        let arr2 =
                set(arr, 10, 10)
            catch e of InvalidKeyError = Nothing
                set(arr, 9, 42)

        affirm:is_true(get(arr2, 9) == 42)