Bee use 2 kind of data types:
Without data types, a programming language is a dynamic language. We think dynamic typing is more fragile and prone to errors than the static typing. Bee language is strongly typed language that uses type inference. That means any data is constrained by fixed "data type" rules.
Bee has predefined data types. You can create new data types based on predefined types using a special declaration. You can create sub-types or composite types having new constraints and rules that can improve data validation further.
A data type can be manipulated using rules and operators. You can have a variable of type: Type. To detect a data type of any variable you can use function type(). Therefore type(type_variable) = Type.
Primitive data types are defined using one capital letter.
Alias | Code | Description + Default representation |
---|---|---|
Logic | L | Numeric enumeration of two values: 0 = False, 1 = True |
Alpha | A | Alpha-numeric code point E-ASCII ('0'..'9') ('a'..'Z') |
Binary | B | Binary number on 8 bit, max: 0b11111111 (0..255) (0..0xFF) |
Unicode | U | Unsigned code point on 32 bit, max: U-FFFFFFFF (UTF32) |
Rational | Q | Fix point representation number: like 1/2. Notation: Q(14,17) |
Natural | N | Unsigned large positive integer 64 bit [0..+] |
Integer | Z | Signed large integer 64 bit [-..+] Z(64) |
Real | R | Double precision float 64 bit (-..+) R(64) |
These are symbolic representations for primitive data types:
Example | Type | Literal characters |
---|---|---|
'a' | A | (+-) & (0..9) & (a..z) & (A..Z) |
'Ω' | U | (Δ Λ Φ Γ Ψ Ω Σ Π π ⊥ ǁ α β ɣ ε δ μ ω ...) |
"str" | S | (∀ UTF8) |
0b11111111 | B | (0b) & (0,1) |
1234567890 | N | (0,1,2,3,4,5,6,7,8,9) |
+0 | Z | (-+) & (0,1,2,3,4,5,6,7,8,9) |
0xFFFF | A | (0x) & (0,1,2,3,4,5,6,7,8,9) & ABCDEF |
U+FFFF | U | (U+) & (0,1,2,3,4,5,6,7,8,9) & ABCDEF |
U-FFFFFFFF | U | (U-) & (0,1,2,3,4,5,6,7,8,9) & ABCDEF |
0.05 | R | (-.) & (0,1,2,3,4,5,6,7,8,9) |
-1/2 | Q | (-/) & (0,1,2,3,4,5,6,7,8,9) |
1E10 | R | (-1E)& (0,1,2,3,4,5,6,7,8,9) |
1e10 | R | (-1e)& (0,1,2,3,4,5,6,7,8,9) |
note
Predefined composite types have an alias starting with capital letter:
Alias | Type | Description |
---|---|---|
Complex | C | Double precision pair of double float numbers (9r+9j) |
String | S | UTF8 encoded double quoted string "α β ɣ ε δ μ ω" |
Text | X | Multi-line large block of text ... |
Date | D | "DD/MM/YYYY" |
Time | T | "hh:mm,ms" |
Error | E | Error object: {code, message, line} |
File | F | File handler |
Bee define a collection literal using a special notation based on brackets.
delimiter | collection types |
---|---|
() | List |
[] | Array / Matrix |
{} | Ordinal / Set / Hash / Object |
User can define super-types and sub-types using operators ":" and "<:".
** declare new type
type Type_Identifier: type_descriptor <: super_type;
** declare variables using new type
make var_name,var_name ... ∈ Type_Identifier;
A range is a notation that designate a sub-set of consecutive integer numbers between two limits: lower limit and upper limit included in round paranthesis and separated by two dots (..) or (!).
Range ::= (min..max); Range ::= (min.!max); //exclude upper limit Range ::= (min!.max); //exclude lower limit
#integer domain
rule main():
print (0..5); // 0,1,2,3,4,5
print (0.!5); // 0,1,2,3,4
print (0!.5); // 1,2,3,4,5
pass if 32667 ∈ (0..+); //expect to pass
pass if -32668 ∈ (-..0); //expect to pass
return;
Ranges can use symbols that are ASCII or Unicode. In this case the symbols must be included in single quotes: 'X' or use U+ notation:
** sub-type declarations
type Digit: ('0'..'9') <: Z;
type Capital: ('A'..'Z') <: A;
type Lowercase:('a'..'z') <: A;
type Latin: (U+0041..U+FB02) <: U;
rule main():
** following statements should pass
pass if '0' ∈ Digit;
fail if 'x' ∈ Capital;
pass if 'X' ∈ Capital;
pass if 'e' ∈ Latin;
return;
Domain is very symilar to range except a domain has a ratio. This is the difference, the numbers are not integers, can be fractionar or Q umber.
Domain ::= (min..max:ratio) <: Super_Type
** generate rational numbers
print (0..1:1/4); // 0/4, 1/4, 2/4, 3/4, 4/4
** generate float numbers
print (0..1:0.25); // 0.00, 0.25, 0.50, 0.75, 1.00
Constants are defined identifiers for literal constants.
** using explicit type
stow constant_name: constant_literal ∈ type_name;
** using implicit type
stow constant_name:= constant_literal;
#symbol constants
stow n: U+2200 ∈ A; //Symbol: ∀
stow n: U-00002200 ∈ U; //Symbol: ∀
Variables are defined using keyword make plus one of the operators:
operator | purpose |
---|---|
∈ | declare variable/element type |
: | define type | set initial value | pair up operator |
:= | type inference | share a reference | assign operator |
:: | deep copy | duplicate object | cloning operator |
** primitive variable declarations with type
make var_name ∈ type_name; // declaration only type without initial value
make var_name: value ∈ type_name; // declaration with initial value and type
** Variable declaration using type inference
make var_name := expression; //expression ":=" do not require type hint ("∈").
** Multiple variables can be define in one single line using comma separator:
make var1, var2 ... ∈ TypeName; //default initial values
make var1, var2 ... := Expression; //use type inference for all initial values
** Initialize multiple variables, of the same type (type is required)
make var1:con1, var2:con2 ... ∈ TypeName;
One can modify variables using alter statement.
#fragment of code
make a:10, b:0 ∈ Z; // initialize two variables
alter b := a + 1; // modify b using binding operator :=
alter b += 1; // modify b using modifier +=
print b; // expected 12
** declare a constant
stow pi: 3.14 ∈ R;
** declare multiple variables
make a ∈ Z; //Integer
make x,y ∈ R; //Double
make q,p ∈ L; //Logic
rule main():
** using modifiers
alter a := 10; //modify value of a := 10
alter a += 1; //increment value of a := 11
alter a -= 1; //decrement value of a := 10
** modify two variables using one single constant
alter x, y := 10.5;
** modify two variables using two constants
alter q, p := True, False;
** swapping two variables
alter p, q := q, p;
return;
When data type mismatch you must perform explicit conversion.
make a: 0, b:20 ∈ Z;
make v: 10.5, x: 0.0 ∈ R;
rule main():
** explicit conversion
alter a := v :> N;
print a; //truncated to 10
** explicit conversion
alter x := b :> R;
print x; //expect 20.00
return;
Bee define A as single UTF-8 code point with representation: U+HH
make a, b ∈ A; //ASCII
make x, y ∈ B; //Binary integer
rule main():
alter a :='0'; //ASCII symbol '0'
alter x := a :> B; //convert to binary 30
alter y := 30; //decimal code for '0'
alter b := y :> A; //convert to ASCII symbol '0'
return;
You can use symbol ":=" to initialize variables using type inference.
** declare constants
stow a := 4; //integer constant
stow b := 2.5; //real constant
stow c := 1/8; //rational constant
** declare variables
make x := 0; //integer variable
make y := 0.0; //real variable
make z := 0/0; //rational variable
We can use variable type to validate expression type.
** using type inference
make a := 0; //integer variable
make b := 0.0; //real variable
rule main():
alter a:= 10.5; //Warning: a is of type: Integer
alter b:= 10; //Warning: b is of type: Real
print a, b; // 10, 10.00
return;
You can use operator "∈" to verify data type:
make a := 0 ∈ Z;
rule main():
** expected: Integer
fail if ¬ (a ∈ Z); // fail if a is not integer
return;
Logic type is an enumeration of two public symbols: False and True
type .L: {.False: 0, .True: 1} <: Ordinal;
** printing logical values
rule main():
print True; //1
print False; //0
return;
Bee uses several familiar logic operators from mathematics:
Precedence: { ¬, ∧, ∨, ⊕, = }. Symbol ¬ apply first (has higher precedence)
bitwise
In Bee we define special operators to perform bitwise operations. One opperator is overwrite: ⊕. This operator (xor) can operate on both, logical values or expressions and also on integer numbers.
# betweese opperations
print 4 | 3;//out:7 is becouse 100 ∨ 011 = 111 = 7;
print ~4 //out:3 is becouse: 100 ⊕ 100 = 011 = 3;
print 4 & 7 //out:4 is becouse: 100 ∧ 111 = 100 = 4;
print 4⊕4 //out:0 is becouse: 100 ⊕ 100 = 000 = 0;
print 1<<2 //out:2 is becouse: 001 << 2 = 100 = 4;
print 6<<2 //out:1 is becouse: 110 << 2 = 001 = 1;
comparison Comparison operators will create a logical response: 1 = True or 0 = False
make (x, y):4 ∈ Z; //primitive integer
rule main():
** value comparison
print x = 4; //1 (equal)
print x ≡ 4; //1 (identical)
print x = y; //1
print x ≡ y; //1
print x ≠ 5; //1 (different)
print x!≡ 5; //1 (not identical)
** reference ordering
print x ≥ y; //1: x and a are actually equal
print x ≥ 4; //1: greater or equivalent to 4
print x ≤ 4; //1: less than or equivalent to 4
print x > 4; //0: not greater than 4
print x < 4; //0: not less than 4
** arithmetic expressions have primitive results
print x - 4 = 0; //1
print x - 4 ≡ 0; //1
return;
singleton
Primitive types are unique. That means they are identical.
** primitive values are singleton
print 1 ≡ 1; //1
print `s` ≡ `s`; //1
Precedence:
Logic operators have greater precedence than comparison operators.
Logical expression have value { 0 = False, 1 = True }
make x := False ∈ L;
make y := True ∈ L;
rule main():
** expressions with single operant
print x; //0
print ¬ x; //1
** expressions with two operands
print (x = y); //0
print (x ≡ y); //0
print (x ≠ y); //1
print (x < y); //1
print (x > y); //0
print (x ∧ y); //0
print (x ∨ y); //1
print (x ⊕ y); //1
return;
coercion Any numeric expression can be converted to a logic value using coercion operator ":>" (easy to memorize if you think is like an arrow).
make (x, y) ∈ L;
make (a:0.0, b:1.5) ∈ R;
rule main():
alter x := a :> L; //0
alter y := b :> L; //1
return;
design
** logical values are singleton
print True ≡ True; //1
print False ≡ False; //1
** logical values are numeric
print False - True; //-1
print True + True; //+2
** Null value is a singleton
print Null ≡ Null //1
** Null is not the same as false
print Null ≡ False //0
print Null = False //0
A conditional is a logic expression used to control statement execution.
statement if condition;
The statement is executed only if the expression evaluate to True.
restrictions:
make a := 0 ∈ Z;
rule main():
** conditional execution
alter a := 1 if a = 0;
** conditional print
print "a is 0" if a = 0;
return;
Instead of ternary operator we use conditional expressions. These expressions are separated by coma and enclosed in ().
make var ∈ type;
rule main():
** single condition matching
alter var := (xp1 if cnd1, xp);
** multiple matching with default value
alter var := (xp1 if cnd1, xp2 if cnd2,...xp);
** alternative code alignment
alter var :=
(xp1 if cnd1
,xp2 if cnd2
,xp);
return;
Legend:
rule main():
make x := '0'; //symbol
write "x:"
read x;
make kind := ("digit" if x ∈ ['0'..'9'], "letter" if x ∈ ['a'..'z'],or "unknown");
print ("x is " + kind); //expect: "x is digit"
return;
Type inference is a logical deduction of data type from constant literals.
Each literal has associated a default type, induced by operators {":=", "::"}.
** string expressions
make c := 'a' ; //type = A
make s := '∈' ; //type = U
make s := "str" ; //type = S
make b := <Text> ; //type = Text
** numeric expressions
make i := 0; //type = Z
make j := 0.50; //type = R
** define synonyms for logic constants
stow false := False; //type L = 0
stow true := True; //type L = 1
** multiple variables get same value
make x, y, z := 5; //type = Z for all
** multiple variables get multiple values
make int, rea := 4, 4.44;
print type(int); // Z
print type(rea); // R
Composite structures are using () [] and {} to create different data types. Next you can study some examples. We wil explain later all these types: List, Map, Array, Object, String. All of these are also called Bee collections.
** boxed integer (type = Z)
make i := [10]; //array of one value!
** list of one value (Z)
make a := (1,);
** list of integers (Z)
make b := (1,2);
** list of symbols (type = A)
make l := ('a','b');
** list of Unicode symbol (type = U)
make u := ('Δ', 'Λ', 'Γ');
** array with capacity of 4 integers: Z
make d := [1,2,3,4];
** array with capacity of 10 real numbers: R
make e := [0.00](10);
** matrix with capacity of 10x10 real numbers: R
make m := [0.00](10,10);
** data set of 4 integers: Z
make s := {1,2,3,4};
** hash map of (Z: String)
make c := {(1:"storage"),(2:"string")};
** object with two attributes: name ∈ String, age ∈ Z
make b := {name:"Goliath", age:30};
When we define parameters we can use type inference only for optional parameters:
Optional Parameters:
** in rule foo, parameters a, b are optional.
rule foo(a: 0, b: 0 ∈ Z) => (r ∈ Z):
alter r := a + b;
rule;
rule main():
print foo(); // 0
print foo(1); // 1
print foo(1,2); // 3
return;
Multiple parameters:
** parameters: a, b are mandatory, c is optional.
rule foo(a, b, c: 0 ∈ Z) => (r ∈ Z):
alter r := (a + b + c);
return;
rule main():
print foo(1,2); // 3
print foo(1,2,3); // 6
print foo(1); // Error: expected 2 arguments
return;
Pass arguments by name:
We can use parameter name and pair-up ":" symbol for argument value.
** rule with optional parameters (Z)
rule bar(a: 0, b: 0, c: 0 ∈ Z) => (result ∈ Z):
alter result := (a+b+c);
return;
** observe we use pair-up ":" to give value for each argument
rule main():
print bar(a: 1); //print 1 because b,c = 0
print bar(b: 1); //print 1 because a,b = 0
print bar(c: 1); //print 1 because a,b = 0
return;
In mathematics rational number is any number that can be expressed as the fraction p\q of two integer numbers: numerator "p" of type integer and a non-zero denominator "q" of type natural> 0.
Since "q" may be equal to 1, every binary integer is also a rational number.
Note: Q numbers are approximated numbers.
Other precision constants:
Literal Notation: p/q
It can be used with type inference to create Q numbers:
make x := 0 ∈ Q; //0
make a := 1/2 ∈ Q; //0.5
make b := 1/4 ∈ Q; //0.25
make c := 1/8 ∈ Q; //0.125
make d := 1/8 ∈ Q; //0.062
make e := 1/8 ∈ Q; //0.031
Qm.n is m+n+1 bit signed integer container with n fractional bits.
Qm is a m+1 bit signed integer containing 0 fractional bits.
number of bits = m+n+1
precision is 2⁻ⁿ
range is [-(2ᵐ)..(2ᵐ-2⁻ⁿ)]
For example: A number format "Q5.2" can store in range (-32.00..31.75) on 8 bits.
make v ∈ Q5.2;
alter v := -32; //minim value
alter v := 31.75; //maxim value
See also: wikipedia
Next I have predefined some numbers for orientation.
rezolution -> | 1\4 ≈ | 1\8 ≈ | 1\16 ≈ | 1\32 ≈ | 1\64 ≈ |
---|---|---|---|---|---|
↓ memory space | ±0.25 | ±0.125 | ±0.062 | ±0.031 | ±0.015 |
8 bytes | Q(5.2 ) | Q(4.3 ) | Q(3.4 ) | Q(2.5 ) | Q(1.6 ) |
16 bytes | Q(13.2 ) | Q(12.3 ) | Q(11.4 ) | Q(10.5 ) | Q(9.6 ) |
32 bytes | Q(29.2 ) | Q(28.3 ) | Q(27.4 ) | Q(26.5 ) | Q(25.6 ) |
64 bytes | Q(61.2 ) | Q(60.3 ) | Q(59.4 ) | Q(58.5 ) | Q(56.6 ) |
128 bytes | Q(125.2) | Q(124.3) | Q(123.4) | Q(122.5) | Q(121.6) |
Note: r ≈ is the approximate resolution.
A very large number with high resolution on 64 bit:
Q(50.12)
A number on 32 bit with resolution = 0.0005:
Q(20.11)
Q(14.17)
Default Q number has precision 10⁻⁵ = 2⁻¹⁷ ≈ 0.00001 and occupy 32 bit.
smallest fraction: 1/10000 largest fraction: 32766/2
Rational numbers and other numbers can be compared using "≈" instead of "=".
In next example b = 0.33(3), delta = (b - a) = 0.083
** override default precision
$precision := 0.01;
make a := 0.25; //real
make b := 1/3; //rational
** using specified precision 0.01 < 0.083
print (a ≈ b); //false
print (a ≈ b ± 0.1); //true
print (a ≈ c ± 0.5); //true
print (b ≈ c ± 0.5); //true
Read next: Control