Sage-Code Laboratory

Bee Collections

Collections are composite data types. A composite type is a data structures that group a limited number of values together. You can have access to individual values using different methods, depending on the collection type.

Next links enable you to find definition of a particular collection type. Collections have different features and performance. Use browser back-button to jump back here and compare collections later.


Bee uses composite types to declare ...


A new type is defined from a super-type using symbol "<:"

** type declaration
type new_type: descriptor <: super_type;




Ordinal is an ordered small set of identifiers. Each identifier represents an integer value starting from a specified number with interval of one. It can be used for ranking, selection or codification.

The set of elements is enclosed in curly brackets, separated by coma. Usually the first element has value 1, but this can be specified using (n) in front of the curly bracket: (n){elements}.


type Type: (1){name1, name2, name3} <: Ordinal;
rule main:
  new a, b, c ∈ Type; -- a, b, c will have same type

  let a := Type.name1; -- 1
  let b := Type.name2; -- 2
  let c := Type.name3; -- 3

Note: When element name start with "." no need to use qualifiers for the individual values. This is because values starting with "." are public by default and known in the scope where ordinal is defined (or loaded).

** using public elements in ordinal
type Type: (0){.name0, .name1} <: Ordinal;
rule main()
   new a, b ∈ Type;

   let a := name0; -- a = 0
   let b := name1; -- b = 1


A list is a dynamic collection of elements connected by two references:

A list has two very important elements:

bee rule

Chained List

list type

You can define a list type using empty list: ()

type Type_name: (element_type) <: List;

variable declaration You can use one of three forms of declarations:

** declare emty list without type
new name1: ();  -- element type will be established later
** declare empty list, using explicit type
new name1: (element_type);  

** declare populated lists using type inference
new name2 := (e1,e2...); -- implicit declaration

new name3 := (e1,e2...) ∈ Type_name; -- full declaration



## define a diverse lists
new two:(Z) <: List;  -- empty list of integers = ()
new one:(0);       -- initialize list using type inference
new two:(1,2);     -- initialize list with two elements


## list traversal demo
rule main:
  ** define a list variable of defined type Lou
  new myList := (0, 1, 2, 3, 4, 5);
  ** list traversal
  cycle: for ∀ x ∈ myList do
    write x;
    write "," if (x ≠ myList.head);
  print; -- 0,1,2,3,4,5


Bee define Arrays using notation: [type](c), where [type] is the data type of elements and (c) is the capacity (total number of elements). Arrays are automatically initialized. However, if the array contains composite types all elements are null until initialized.


** diverse array variables
new array_name1: [element_type]  ;    -- single element array
new array_name3: [element_type](c);   -- capacity c
new array_name4: [element_type](n,m); -- capacity c = n * m

** define new sub-type of array
type AType:[element_type] < Array;

** use previous defined sub-type
new array_name5 := AType(c);


In next example we declare an array and use index "i" to access each element of the array by position. Later we will learn smarter ways to initialize an arrays and access its elements by using a visitor pattern.


Array Index

Lets implement previous array: numbers[] and print its elements using a cycle. For initialization we use an explicit array literal that contains all the elements.

# define array
new numbers[Z](10) := [0,1,2,3,4,5,6,7,8,9];

** access .numbers elements one by one
rule main:
    write "numbers = [";
    cycle: for ∀ i ∈ (1..10) do
        write numbers[i];
        write ',' if i < 10;
    write "]";
    print; -- flush the buffer

Expected Output

numbers = [0,1,2,3,4,5,6,7,8,9]


initialize elements

Initial value for elements can be set during declaration or later:

** you can use a single value to initialize all vector elements
rule main:
    new zum:[Z](10) ∈ Vector; 
    ** explicit initialization using single value
    let zum[*]  := 0;
    print zum; -- expect [0,0,0,0,0,0,0,0,0,0]

    ** modify two special elements:
    let zum.first  := 1;
    let zum.last   := 10;
    print zum; -- expect [1,0,0,0,0,0,0,0,0,10]

Deferred initialization: We can define an empty array and initialize its elements later. Empty arrays have capacity zero until array is initialized.

** array without capacity
rule main:
    new vec:[A]();
    new nec:[N]();

    ** arrays are empty
    print vec = []; -- True
    print nec = []; -- True

    ** smart initializer with operator "++"
    let vec ++ 10; -- add 10 elements;
    print vec; -- expect ['','','','','','','','','','']
    ** smart initializer with 0 values
    let nec ++ 10;
    print nec; -- expect [0,0,0,0,0,0,0,0,0,0];


A matrix is an array with 2 or more dimensions. In next diagram we have a matrix with 4 rows and 4 columns. Total 16 elements. Observe, matrix index start from [1,1] as in Mathematics.


Matrix Index


In next example we demonstrate a curious notation for matrix. You have maybe not seen this before in any other language because is ridiculous to parse. But from an esthetic point of view we think this is the way a matrix literal should look like:

# define a subtype of Matrix
type Mat:[R](4,4) ∈ Matrix

rule main()
    new mat ∈ Mat -- define matrix variable

    ** modify matrix using ":=" operator
    let mat := [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
    print mat[1,1]; -- 1  = first element
    print mat[4,4]; -- 16 = last element

    ** support for 2D matrix literals
    pass if mat =  ⎡ 1,  2 , 3,  4 ⎤
                   ⎢ 5,  6 , 7,  8 ⎥
                   ⎢ 9, 10 ,11, 12 ⎥
                   ⎣13, 14 ,15, 16 ⎦;

    ** nice output using array print method
    apply mat.print;

Expected output:

⎡ 1,  2 , 3,  4 ⎤
⎢ 5,  6 , 7,  8 ⎥
⎢ 9, 10 ,11, 12 ⎥
⎣13, 14 ,15, 16 ⎦

Internal Order

Memory is linear, so we fake a matrix. In reality elements are organized in row-major order. That means first row, then second row...last row. We can access the entire matrix like it would be a longer array. So next program can initialize a matrix in a normal cycle, not nested!

** initialize matrix elements
rule main:
  new mat: [Z](3,3) <: Matrix;
  ** initialize matrix elements
    new i   := 1;
    new x   := mat.length;
  while (i < x) do
    let mat[i] := i;
    let i += 1;
  apply mat.print; -- nice output


⎡ 1  2  3 ⎤
⎢ 4  5  6 ⎥
⎣ 7  8  9 ⎦

Data sets

A data set is a sorted collection of unique values. Elements of a data set can be accessed sequential. There is no index associated with elements like we have in Arrays so the access to an element is slow.

** user defined set
type NS:{N} <: Set -- define a set of natural numbers

rule main:
   new uds ∈ NS;     -- define a shared variable of type set
   ** define shared sets s1, s2 of 3 elements each
   new s1 := {1,2,3} ∈ {N};
   new s2 := {2,3,4} ∈ {N};
   ** specific operations
   new u  := s1 ∪ s2; -- {1,2,3,4,5}:union
   new i  := s1 ∩ s2; -- {2,3}  :intersection
   new d1 := s1 - s2; -- {1}        :difference 1
   new d2 := s2 - s1; -- {4}        :difference 2
   new d  := s2 Δ s1; -- {1,4}      :symmetric difference

   ** verify expectation
   expect d = d1 ∪ d2;

   ** belonging check
   print s1 ⊂ s;  -- True
   print s  ⊃ s2; -- True

   ** declare a new set
   new a := {1,2,3};

   ** using operator +/- to add/remove elements
   let a += 4;  -- {1,2,3,4}
   let a -= 3;  -- {1,2,4}


Hash Map

A hash map is a set of (key:value) pairs sorted by key.


** declare a new empty hash map
new new_map ∈ {key_type: value_type};


In next example we show a map that has 3 elements. Each element contains a key a value and a reference to next element. References to next elements are not visible to you. The Map functions will take chare to maintain this data for you internally. You will use just key and value from each element.

Hash-Map Anatomy

** initial value of map
rule main:
   new map := {key1:"value1", key2:"value2"};

   ** create new element
   let map['key3'] := "value3";

   ** finding elements by key
   print map['key1']; -- "value1"
   print map['key2']; -- "value2"
   print map['key3']; -- "value3"

   ** remove an element by key
   scrap map['key1']; -- remove "first" element
   apply map.print;   -- expected: {'key2':"value2", 'key3':"value3"};


Check for inclusion

We can check if an element is included in a collection using "∈".

type  Tmap: {A:U} <: Map;

rule main:
   new map  := {a:"first"), b:"second"} ∈ Tmap;
   when ('a' ∈ map) do
     print("a is found");
     print("not found");


Bee has 4 basic types for characters and strings:


quote used for
'_' Byte / ASCII single symbol
"_" Double quoted UTF32 Unicode string or string template


For large text literals (X) we can use a markup tag:


  Bee language has suport for large text literal.
  A text can be SQL, XML, HTML or report template.


select name, age 
  from persons
 where age < 24;


   <p>Hello World</p>
   <p>Bee is a great language.</p>

Array of symbols

Single quoted or back quoted literals can contain a single symbol.

** fixed capacity vector of ASCII symbols
type A128: [A](128) <: Vector;
rule main()
  ** declare a string of type A128
  new str ∈ A128;

  ** populate vector using spreading operator (*)
  let *str := 'test'; -- spreading the ASCII literal
  print str;   -- ['t','e','s','t']

  ** fixed capacity vector of symbols UTF32
  new  uco: [U](128);
  let *uco := "∈≡≤≥÷≠"; -- spreading a Unicode literal
  print  uco; -- ["∈","≡","≤","≥","÷","≠"];

String literals

Double quoted string literals are Unicode strings.


rule main:
   ** variable capacity string UTF32
   new uco ∈ S; -- Unicode string unknown capacity
   let uco := "∈ ≡ ≤ ≥ ÷ ≠ × ¬ ↑ ↓ ∧ ∨";

Escape You can use this literal with escape sequence: \n to break a line

print("this represents \n new line in string");


this represents
new line in string



Next example demonstrate working with strings. We use "+" operator to make several concatenations and "*" operator to replicate a character and create a longer string.

rule main:
   new (c, s) ∈ S; -- default length is 128 octets = 1024 bit

   ** string concatenation
   let c := "This is 
             a large unicode string";
   ** automatic conversion to string
   let s := 'This is an ASCII string';


An error is when program enter a difficult state that is confusing. An error can be declared by the user or by the system. Bee has predefined type: Error that can be used to declare your own kind of errors. In other languages we use therm Exception, that is synonim to Error.

Internal Definition:

Parts of Bee compiler will be created using Bee language. Here is the definition of global variable $error, that is available for introspection after you call a rule.

** global error type
define Error: {code ∈ Z, message ∈ S, line ∈ Z} <: Object;
** global system error
new $error ∈ Error;

You can define errors with code > 200 and raise error with 3 statements:


new my_error := {200,"message"} ∈ Error;
fail my_error if condition;
pass if condition;

String interpolation "?" can be used to customize the error messages:


rule main:
   new  flag ∈ L;
   read (flag, "enter flag (0/1):");

   new my_error: {201,"error:#(s)"} ∈ Error;
   fail (my_error ? "test") if flag;





Next we create unrecoverable error. In this case the program crash and exit. The operating system receive a number that signal the error code:

panic -1; -- end program immediately
panic 2; -- end program and error code = 2

See also:

Read next: Data Processing