Sage-Code Laboratory
index<--

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 type of the collection.

Bookmarks:

These bookmarks enable you to visit a particular collection type. If you are first time here, just scrol down and read the whole page at least one time. Next time you can use this small index to jump to a particular topic. Enjoy!

Usability

Bee uses composite types to declare ...

Pattern

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

type new_type: descriptor <: super_type

Legend:

Note:

Ordinals

Ordinal is an abstract data set. It is a group of identifiers. Each identifier represents an integer value starting from 0 to capacity-1. Associated values can start with a different integer number. Values can be specified using pair up operator ":". All values must be unsigned integer (i ≤ 0) in consecutive ascending order.

Pattern:

type type_name: { name1:0, name2, name3} <: Ordinal;
make a, b, c ∈ type_name; //a, b, c will have same type
rule main():
  alter a := type_name.name1; //a=2
  alter b := type_name.name2; //b=3
  alter c := type_name.name3; //c=4
return;  

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

** using public elements in enumeration
type type_names: {.name0, .name1} <: Ordinal;
make a, b ∈ type_name;
rule main()
  alter  a := name0; //a = 0
  alter  b := name1; //b = 1
return;  

Lists

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:

make name ∈  (element_type); //explicit declaration
make name := (constant,...); //implicit declaration
make name := (constant,...) ∈ Type_name; //full declaration

properties

Example:

** define a diverse lists
make two:() ∈ (Z);  //empty list of integers = ()
make one:(0,);      //initialize list with single element
make two:(1,2);     //initialize list with two elements
** define a list type of unsigned short integer
type Lon:(N) <: List;
** define a list variable of defined type Lou
make myList:(0, 1, 2, 3, 4, 5) ∈ Lon;
** list traversal
rule main():
  given x <- myList do
    write x;
    write "," if (x ≠ myList.head);
  cycle;
  print; //0,1,2,3,4,5
rule;  

Arrays

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.

Syntax:

** diverse array variables
make array_name ∈ [element_type]  ;    //single element array
make array_name ∈ [element_type]();    //undefined capacity array
make array_name ∈ [element_type](c);   //capacity c
make array_name ∈ [element_type](n,m); //capacity c = n·m
** define new kind of array
type Array_Type: [element_type](c) <: Array; 
** use previous  defined type
make array_name ∈ Array_Type;  

Example:

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.

bee-array

Array Index


Lets implement previous array: numbers[] and print it nicely using a cycle.

# define public array
make .numbers[Z](10) := [0,1,2,3,4,5,6,7,8,9]; 
** access .numbers elements one by one
rule main():
  write "numbers = [";
  given i <- (1..10) do
    write numbers[i];
    write ',' if i < 10;
  cycle;
  write "]";
  print; // flush the buffer
return;  

Expected Output

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

Notes:

initialize elements

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

** you can use a single value to initialize all vector elements
make zum:0 ∈ [Z](10); //explicit initialization using single value
rule main():
  ** modify one element by index
  alter zum[1]  := 1; 
  alter zum[-1] := 10; 
  print zum; //expect [1,0,0,0,0,0,0,0,0,10]
  
  ** modify all elements
  alter zum[*] := 0; 
  print zum; //expect [0,0,0,0,0,0,0,0,0,0]
return;  

Deferred initialization: We can define an empty array and initialize its elements later. Epty arrays do not have establish a capacity until array is initialized

** array without capacity (partial type inference)
make vec ∈ [A](); 
make nec ∈ [N](); 
rule main():
   ** arrays are empty
   print vec = []; //True
   print nec = []; //True
   
   ** smart initializer with operator "×" 
   alter vec := 'x' × 10; //10;
   print vec; //expect ['x','x','x','x','x','x','x','x','x','x']
   
   ** smart initializer with 0 values
   alter nec := 0 × 10;
   print nec; //expect [0,0,0,0,0,0,0,0,0,0];
return;   

Matrix

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.

bee-matrix

Matrix Index

Example:

In next example we demonstrate a curious notation for matrix. You have maybe not seen this before in any other language becouse is ridiculous to parse. But from aestetic 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;
make mat ∈ Mat //define matrix variable

rule main()
    ** modify matrix using ":=" operator
    alter 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 optput using array print method              
    apply mat.print;  
return;                

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
make i   := 1;
make mat := [Z](3,3);
make x   := mat.length;
rule main():  
  while (i < x) do
    mat[i] := i+1;
    i += 1;
  cycle;
  apply mat.print; //nice output
return;  

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
make uds ∈ NS;     //define a shared variable of type set
** define shared sets s1, s2 of 3 elements each 
make s1 := {1,2,3} ∈ {N}; 
make s2 := {2,3,4} ∈ {N}; 
** specific operations
make u  := s1 ∪ s2; //{1,2,3,4,5}:union
make i  := s1 ∩ s2; //{2,3}      :intersection
make d1 := s1 - s2; //{1}        :difference 1
make d2 := s2 - s1; //{4}        :difference 2
make d  := s2 ⊖ s1; //{1,4}      :symmetric difference
rule main():
   ** verify expectation
   pass if d = d1 ∪ d2; //equivalent (else fail)
   
   ** belonging check
   print s1 ⊂ s;  //True
   print s  ⊃ s2; //True
   
   ** declare a new set
   make a := {1,2,3};
   
   ** using operator +/- to mutate set a
   alter a += 4;  // {1,2,3,4}
   alter a -= 3;  // {1,2,4}
return;   

Notes:

Hash maps

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

Syntax:

** define a hash map type
type type_name: {(key_type: value_type)} <: Hash;
** declare a new empty hash map
make new_map := {} ∈ type_name;

Example:

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.
bee-map

Hash-Map Anatomy

** initial value of map
make map := {('key1':"value1"), ('key2':"value2")};
rule main():
   ** create new element
   alter 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")};
return;

Notes:

Check for inclusion

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

type  Tmap: {(A:U)} <: Hash;
make map  := {('a':"first"), ('b':"second")} ∈ Tmap;
rule main():
   when ('a' ∈ map) do
     print("a is found");
   else
     print("not found");
   done;
return;

Strings

Bee has 4 kind of string literals:

Notes:

quote used for
`_` Back quotes, ASCII single symbol
'_' UTF32 Unicode single symbol
"_" Double quoted UTF8 Unicode string or string template

Text

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

Example:

make query := 
<sql>
   select name, age
     from persons
    where age < 24;    
</sql>;

Array of symbols

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

** fixed capacity array or ASCII symbols
type A128: [A](128) <: Array; 
rule main()
  ** populate array using spreading operator (*)
  make str ∈ A128;
  alter *str := "test"; //spreading a ASCII literal
  print  str;   // [`t`,`e`,`s`,`t`]
  
  ** fixed capacity array of symbols UTF32
  make   uco ∈ [U](128);
  alter *uco := "∈≡≤≥÷≠"; //spreading a Unicode literal
  print  uco; // ['∈','≡','≤','≥','÷','≠'];   
return;  

String literals

Double quoted string literals are Unicode UTF8 strings.

Example:

rule main():
   ** fixed capacity string UTF8
   make  uco ∈ S; //Unicode string unknown capacity
   alter uco := "∈ ≡ ≤ ≥ ÷ ≠ · × ¬ ↑ ↓ ∧ ∨";
   
   ** strings are printed with quotes
   print uco; //"∈ ≡ ≤ ≥ ÷ ≠ · × ¬ ↑ ↓ ∧ ∨"
return;      

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

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

output:

this represents 
new line in string

Notes:

Concatenation

Below operators will concatenate two strings.

symbol description
* Concatenate a string with itself multiple times
+ Concatenate two strings as they are no trim is performed
- Concatenate two strings and reduce white spaces to one
. Concatenate path using using '/' or '\' depending on OS type
/ URL/Path concatenation: trim and use single separator: "/"
\ Windows path concatenation: trim and use single separator: "\"

examples

make m: ('-' * 10) ∈ S; //m = "----------"
make (u, c, s) ∈ S; //default length is 128 octets = 1024 bit
rule main():
   ** string concatenation
   alter c := "This is " + "a large string"; 
   
   ** automatic conversion to string
   alter s := "40" + 5; // "405"
   
   ** URL/path concatenation
   make test_file := $pro.'src'.'test.bee';
   
   ** when $platform = "Windows"
   ** Let's say $pro = "c:\work\project\"
   print test_file; // "c:\work\project\src\test.bee"
   
   ** when $platform = "Linux"
   ** Let's say $pro = "/work/project/"
   print test_file; // "/work/project/src/test.bee"
return;

Conversion

Conversion of a string into number is done using parse rule:

make x,y ∈ R;
rule main():
  ** rule parse return; a Real number
  alter x := parse("123.512",2);     //convert to real 123.5
  alter y := parse("10,000.3333",2); //convert to real 10000.33
return;  

Objects

Object type is a data structure with elements enclosed in curly brackets { , , ,} and separated by comma. Each attribute of an object has a name and a data type. This is a heterogenous collection of values.

Pattern:

** declare a category of objects
type  Type_name: {attribute ∈ type_name, ...} <: Object;
rule main():
  ** create an object instance using default constructor
  make item_name := {
         attribute : constant,
         ...
         } ∈ Type_name;
  
  ** modify one object attribute
  alter object_name.attribute := new_value;
return;  

Object structure can be ...

type size:

Type size is a constant that can be calculated using: size(type).

Flat object

Example:

type  Person: {name ∈ S, age ∈ N} <: Object;
** array of 10 persons
make catalog ∈ [Person](10);
  
** initialize value using literals
make catalog[0] := {name:"Cleopatra", age:15};
make catalog[1] := {name:"Martin", age:17};
rule main():
   ** using one element with dot operators
   print caralog[0].name;  //will print Cleopatra
   print caralog[1].name;  //will print Martin
   
   ** member type can be check using _type()_ built in
   print type(Person.name); //will print U
   print type(Person.age);  //will print W
   
   ** print size of structure
   print size(Person);
return;   

Recursive

We can limit how deep a call stack become using a directive. $recursion:1000

** example of single recursive node
type Node: { 
  data ∈ Z,       /* integer data */
  previous ∈ Node /* reference to previous node */
} <: Object;

This kind of structure can be used to create a data chain.

** example of double recursive node
type Node <: {
  data  ∈ Z,    /* integer data */
  prior ∈ Node, /* reference to previous node */
  next  ∈ Node  /* reference to next node */
} <: Object;

Exceptions

An exception is a recoverable error. It can be declared by the user or by the system. Exception is also known as "error". Bee has predefined type: Error that can be used to declare your own errors.

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 exception type
type Error: {code ∈ Z, message ∈ S, line ∈ Z} <: Object;
** global system error
make @error ∈ Error;

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

Pattern:

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

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

Example:

rule main():
   make  flag ∈ L;
   read (flag, "enter flag (0/1):");
   
   make my_error := {201,"error:#(s)"} ∈ Error;
   fail (my_error ? "test") if flag;
return;   

Output:

error:"test"

Notes:

Unrecoverable:

Next we create unrecoverable exception:

abort -1; //end program and error code = -1
abort -2; //end program and error code = -2

See also:


Read next: Data Processing