AlbaDsl: Vector, Tuple, and Maybe libraries
In CashVm stack entries are 10K bytes large. This means that we can store collections of smaller sized values in a single stack entry. That entry can then be conveniently moved around on the stack as a unit and be passed as an argument to functions. Two, possibly heterogeneous data types with varying sizes, can be grouped into a Tuple. Tuples can be recursively nested to form larger structures. Data types that have a fixed-size representation (or can be packed/unpacked into one) can be stored in Vectors with O(1) lookups. Optionally present values may be encapsulated in Maybe types.
I have implemented Vector, Tuple, and Maybe types and their corresponding libraries (work in progress) of accessor functions in AlbaDsl. In addition I have added Int8, Int64, and Bytes128 as examples of types that can be packed/unpacked into a fixed size representation and used as elements in Vectors. Tuples of such types can also be held in Vectors (using TupleFs).
The APIs for Vector, Maybe, and Tuple are modeled after the corresponding Haskell APIs and currently offer the following operations:
Vector: length, null, lookup, head, last, init, tail, take, drop, splitAt, uncons, unsnoc, empty, singleton, replicate, generate, iterateN, cons, snoc, append, reverse, map, zip, zipWith, unzip, filter, foldl.
Maybe: just, nothing, isJust, isNothing, fromMaybe, fromMaybe’, ifJust, maybe, map.
Tuple: tuple, untuple, fst, snd.
Of the operations above, the trivial ones are implemented as macros. All other operations are implemented as CashVM functions with macro wrappers. In the Vector library many of the functions need to know the max size of the types they work on and their corresponding pack/unpack operations. The macro wrappers for these functions use Haskell’s Typeclass mechanism to automatically inject the correct type information into the CashVM function so that the user of the library does not need to manually handle it.
The below example uses the Vector library to sum the integers 1…10:
import Alba.Dsl.V1.Bch2026
import Alba.Dsl.V1.Bch2026.Contract.Int8 (TInt8)
import Alba.Dsl.V1.Bch2026.Contract.Vector (foldl, generate)
f :: FN s (s > TInt)
f =
begin
# lambda2 add
# int 0
# (nat 10 # lambda1 (op1Add # cast) # generate)
# foldl
add :: FN (s > TInt > TInt8) (s > TInt)
add = cast # opAdd
The example uses ‘generate’ to build a vector of ten byte-sized integers (TInt8). Then uses ‘foldl’ to collapse it into a sum stored in a standard size integer (TInt). Lambdas are used to inject behavior into ‘generate’ and ‘foldl’ which are higher-order functions. No explicit loops are needed. If the integers to add were larger (such as Satoshi amounts) one could instead use TInt64s to hold them. That is achieved by replacing all the '8’s in the code above with '64’s. No other changes are needed.
The example compiles into 232 bytes and results in this function table:
Module Line Col Function Slot Ops Sites
------------------------------------------------------------------------------------------------
Alba.Dsl.V1.Bch2026.Contract.Int8 39 3 int8PackFs 0 5 2
Alba.Dsl.V1.Bch2026.Contract.Maybe 47 8 just 1 3 1
Alba.Dsl.V1.Bch2026.Contract.PackFs 74 3 mkPackFs 2 11 1
Alba.Dsl.V1.Bch2026.Contract.PackFs 86 11 getSize 3 4 1
Alba.Dsl.V1.Bch2026.Contract.PackFs 90 3 getPack 4 7 1
Alba.Dsl.V1.Bch2026.Contract.PackFs 98 3 getUnpack 5 4 1
Alba.Dsl.V1.Bch2026.Contract.Tuple 43 9 tuple 6 8 1
Alba.Dsl.V1.Bch2026.Contract.Tuple 60 3 untuple 7 5 1
Alba.Dsl.V1.Bch2026.Contract.Vector 258 3 splitAtUnsafeF 8 9 1
Alba.Dsl.V1.Bch2026.Contract.Vector 278 3 unconsF 9 29 1
Alba.Dsl.V1.Bch2026.Contract.Vector 359 3 generateF 10 38 1
Alba.Dsl.V1.Bch2026.Contract.Vector 433 9 snocF 11 5 1
Alba.Dsl.V1.Bch2026.Contract.Vector 684 3 foldlF 12 33 1
Alba.Dsl.V1.Bch2026.Contract.Int8 42 11 <lambda> 13 2 1
Alba.Dsl.V1.Bch2026.Contract.Int8 43 11 <lambda> 14 1 1
Demo 141 11 <lambda> 15 1 1
Demo 143 21 <lambda> 16 1 1
Functions total: 17
This example is clearly an inefficient way of calculating a simple sum (both in terms of code size and op cost). Vectors are not required here. But the example illustrates the expressiveness of the functional programming style and gives a taste of the features of the Vector library. It also shows how far the 2026 CHIPs take us. And how dependency injection via lambdas allow us to write generic functions like foldl that can be reused across many different value types and use cases.