Native types are implemented by operating system. We name native types using a single lowercase letter followed by number of bytes. The maximum range is a power of 2 multiplied with the number of bytes specified.
Type Name | Name | Min | Maxim |
---|---|---|---|
i | Signed | 8 | 64 |
u | Unsigned | 8 | 64 | f | Float | 32 | 64 |
Native types are not usually necesary in Eve scripts. However the system library is using these types to create Primitive Types. A native type can be boked using a primitive type as a box.
Primitive types are predefined in Eve core library. They have an equivalent native type. Primitive types are slower than equivalent native types, but have more features.
Class Name | Description | Minim | Maxim |
---|---|---|---|
Byte | Is a Numeric type having range: (0x00..0xFF) | 0 | 2^8-1 |
Short | Is a Numeric type having range: (0x0000..0xFFFF) | -2^15 | +2^15-1 |
Integer | Signed on 64 bit | -2^63 | 2^63-1 |
Natural | Unsigned on 64 bit | 0 | 2^64-1 | Double | Float precision number on 8 bytes | -n | 1.8 × 10^308 |
Symbol | Single symbol UTF-8 encoded, 4 bytes | 0 | 2^32-1 |
Ordinal | Enumeration of symbols, Equivalent to Short | 0 | 2^16-1 |
Date | Calendar date | 01/01/0001 | 01/01/9999 |
Time | Time of the day (24h) | 0 | 8.64e+7ms |
Duration | Period of time | 0 | 2^16 |
Logic | Is a Ordinal subtype having values: False = 0, True = 1 | 0 | 1 |
In Eve we can have two categories of numbers:
Category | Eve Types |
---|---|
Discrete | Byte, Short, Integer, Natural, Ordinal, Range, Symbol, Duration |
Continuous | Float, Double, Rational, Complex |
Discrete numbers have a range from negative to positive number that can be calculate easly by the number of bytes. We have equivalent native numbers for every number of bytes from 1 to 64.
type | Chars | Equivalent | min | max | maximum number |
---|---|---|---|---|---|
Integer | 20 | i64 | -2^63 | 2^63-1 | ≤ 9,223,372,036,854,775,807 |
Natural | 20 | u64 | 0 | 2⁶⁴-1 | ≤ 18,446,744,073,709,551,615 |
For conversion into characters:
The type Double is represented using floating precision numbers.
Floating decimal numbers are most simply described by 3 Integers:
The numerical value of a finite number is −1ˢ × c × 2ⁿ Using this formula Eve define two floating point types.
Single: is single-precision 32-bit IEEE 754:
Double is double-precision 64-bit IEEE 754:
type | Digits | Equivalent | maximum number |
---|---|---|---|
Float | 7 | f32 | ≤ 3.4 × 10^38 |
Double | 16 | f64 | ≤ 1.8 × 10^308 |
Rational | 20 | q64 | ≤ 2^64(*) |
Note Precision and range for Rational numbers is variable depending on the resolution. Ratiobal numbers are are using fixed precision arithmetics. When precision is 1, Rational numbers are equivalent to int64 that is a very large number using 20 digits.
Example | Description |
---|---|
0 | Integer zero |
123 | Integer number using symbols: {0,1,2,3,4,5,6,7,8,9} |
1/2 | Single number use symbols: {.,0,1,2,3,4,5,6,7,8,9} |
0.5 | Double number use symbols: {.,0,1,2,3,4,5,6,7,8,9} |
#numeric literals demo
driver numeric_literals:
** define global states
set g1 = 0.0 :Double;
set g2 = 0.0 :Float;
** define main process
process
** check equality
expect g1 == g2; -- value is equivalent
expect g1 is not g2; -- not the same
expect g1 not eq g2; -- different types
** define local variables
new i :Integer; -- Initial value = 0
new n :Natural; -- Initial value = 0
new r :Double; -- Initial value = 0.00
** use modifier := to change value
let i := 9223372036854775807; -- maximum
let n := 18446744073709551615; -- maximum
let r := 1/2; -- 0.5
return;
See also: scientific notation
For representing a single character/symbol you can use single quoted strings. Single quoted strings can store a single UTF8 code point but it has a fixed size of 4 bytes. This type is called "rune" in Go language.
process
new letter := 'a';
new capital := 'B';
new number := '5';
new unicode := 'β';
new operator := '¬';
...
return;
Unicode literals start with prefix U+ or U-. Such literals is compatible with single quoted Symbol code point. Unicode literals are case insensitive hexadecimals.
Composite data types are unions of data elements. A composite variable that is not initialized can have Null value. These types are going to be explained later in other pages.
Note: We try the best to enable implicit data types for specific literals. For most cases, impicit data type works. Sometimes you need precise to specify the type using a type hint.
Class Name | Description |
---|---|
Object | Root class for all composite data types |
Record | Flat collection of native data types. (No objects) |
Variant | A union of possible data types. |
Range | Discrete range of numbers equaly distanced (x..y:ratio) |
Pair | A pair is like a list but it has only two elements (k:v) |
String | Double quote delimited symbol: "..." |
List | Dynamic ordered enumeration of values of same type |
HashMap | Enumeration of (key:value) pairs unique sorted by key |
DataSet | Enumeration of unique elements of the same type |
Table | A collection of records indexed by unique key. |
Function | Function object type/ reference to function. |
Vector | Single-dimensional, fixed size array |
Matrix | Multi-dimensiona, fixed size array |
Error | Composite type derived from Object base class |
A range is a notation that can create a class or a collection of discrete elements. Range is not expanded in memory until is assigned to a variable.
Ranges can be used as generators to populate collections, make logic evaluations, or generate values for loops. Range operations should be optimized by the compiler using leasy evaluation. Elements are generated as needed not stored in memory.
new l := (min..max:ratio); // list new a := [min..max:ratio]; // array new d := {min..max:ratio}; // data set
Numeric ranges:
#numeric range demo
driver numeric_range:
** define a validation class
class R5 = (0..5) <: Range;
process
** test range limits
expect 0 in R5;
expect 5 in R5;
** test limits with other ranges
expect 4 in (1..8:2)
expect 5 not in (-10..10:2)
return;
Symbol range, specify Unicode symbols as limits.
# using symbol ranges
driver symbol_range:
process
** elements of these ranges are written one by one
print ('0'..'5') -- ('0','1','2','3','4','5')
print ['a'..'f'} -- ['a','b','c','d','e','f']
print {'A'..'F'} -- {'A','B','C','D','E','F'}
** use range as validation domains
expect '0' in ('0'..'9'); -- range of digits
expect 'c' in ['a'..'z']; -- range of symbols
expect 'X' in {'A'..'Z'}; -- range of symbols
return;
It is possible to create a decimal range. For this, the limits must be decimal or at least the ratio must be decimaL. If ratio is not specified, it is automaticly created: 0.1 or 0.01 depending on precision of first limit. 0.0 or 0.00. In Eve the number of zeros can establish the precision for numeric ranges.
# using fractional ranges
driver fraction_range:
process
** elements of these ranges are written one by one
print (0..0.5:0.1) -- (0.0,0.1,0.2,0.3,0.4,0.5)
** use range as validation domain
expect 0.01 in (0.0..1.0);
expect 0.001 in (0.00..1.00);
expect 0.001 not in (0.0..1.0);
return;
In computer science coercion is used to implicitly or explicitly change an entity of one data type into another of different type. This is ready to take advantage of type hierarchies and type representations. If not designed properly the coercion can be a fatal mistake. Eve is a safe language so we do only safe coercion.
Implicit coercion In Eve the arithmetic operators are polymorphic. Numeric operators can do implicit data conversion to accommodate the data types and return an accurate result. Automatic conversion is possible only when there is no risk of loosing data precision. If there is a loss of precision we can end-up with a run-time error. To prevent this Eve will implement a safe compile-time check.
#example of implicit conversion
driver implicit_coercion:
process
** local variables
new a := 2;
new b := 1.5;
** alter a, b
let b := a; -- this implicit cast is possible b = 2.0
let b := a + 3.5; -- add 3.5 then assign result to b = 5.5
let a := b; -- error: can not assign Double to Integer
let a := 1.5; -- error: can not assign Double to Integer
return;
Explicit coercion Explicit coercion is a forced conversion. Can be used to convert backwards from higher data range to lower data range or from continuous numbers to discrete numbers. This however can cause a data or precision loss. Explicit coercion is using a function.
Examples of explicit coercion:
# explicit coercion in EVE
driver explicit_coercion:
set a = 0 :Integer;
set b = 1.5 :Double;
process
**explicit coercion lose (0.5)
let a := floor(b);
write a; -- will print: 1
**explicit coercion add (0.5)
let a := ceiling(b);
print a; -- will print: 2
**explicit coercion rounding:
let a := round(b);
print a; -- will print: 2
return;
Number to a string
#convert number to string
driver number_to_string:
** local states
set s :String;
set v :Integer;
process
let v := 1000;
let s := format(v); -- explicit coercion s = '1000'
expect (s == '1000');
return;
String to a number
This can be ready using the casting function parse(), only if the string contains a number. Otherwise the conversion fail and will rise and exception.
#string to number conversion
driver string_to_number:
** global states
set v :Integer ;
set b :Double ;
set s = "1000" :String;
set r = "200.02":String;
process
let v := parse(s); -- make v = 1000
let v := parse(r); -- make v = 200 and decimal .02 is lost
let b := parse(r); -- make b = 200.02 and decimal .02 is preserved
return;
Note: Build-in functions that are located in EVE default library: { parse(), format(), ceiling(), floor() round()}. This module is one of the standard modules that are automatically included in any Eve program.
Literals are representations of specific particular data type in source code.
Basic types Next notation use "9" to show any digit in range (0..9).
Literal | Type |
---|---|
9 | Integer |
-9 | Integer |
0x9ABCDEF | Natural |
0b1010101 | Binary |
9.9 | Double |
U+0001 | Word |
U-FFFFFFFF | Binary |
Zero literals
Literal | Type |
---|---|
[] | Vector |
{} | Object/DataSet/Table |
() | List/Range/Pairs |
"" | Text |
'' | String |
0 | Integer |
0.0 | Double |
Collection literals
Literal | Type |
---|---|
(1, 2, 3) | List[Integer] |
("1","2","3") | List[String] |
{a:0, b, c} | Ordinal |
{a:"x",y:2} | Object |
{3, 4, 6} | DataSet[Integer] |
[1, 2, 3] | Vector[Byte](3) |
['a','b','c'] | Vector[Symbol](3) |
["a","b","c"] | Vector[String](3) |
[[1,2],[2,4]] | Matrix[Integer](2,2) |
Type inference is a logical association of data type from a constant literal or expression. Type inference can be used in declarations.
Type inference is enabled by operators {:=, ::}. You can use type inference with keyword "new" or "let". Type inference can improve a type declaration if the declaration is partial.
In next example we use a List data literal to create a shared collection fo type List[Integer]. However the type used is partial. Do not have element type, that is going to be Integer.
#test partial inference
driver type_inference:
** Define a list without element type
set ls :List;
process main
** establish element data type
let ls := (0,1,2,3,4,5,6,7,8,9);
print ls.type();
expect type(ls) is ()Integer;
return;
You can declare empty collections using partial inference and determine the type of elements later, using "let". After type is established, it can't be changed later and start functioning as a contract. That is, you must respect the element types for next expressions.
# gradual type declaration
process
new a := (); -- empty List
new v := []; -- empty Vector
new s := {}; -- empty DataSet
** add elements
let a := (10,11,12) -- establish element type
let a += "4" -- error, elements are Integer
return;
We can verify the type using "is" operator:
# using operator "is" to check type
driver type_check:
** define object and initialize
set r = {name:"test", age:24} :Object;
** define hash table
set t = {'key1':"value1",'ley2':"value2"} :DataSet;
process
** check variable types using introspection
expect type(r.name) is String;
expect type(r.age) is Integer
expect type(t.key) is Symbol;
expect type(t.value) is String;
return;
For type introspection we can use type() built-in function:
# introspection demo
process print_type:
new i := 1.5;
expect type(i) is Double;
print "type of i is \s" ? i.type();
return;
In mathematics there are very few operators: {+, -, ÷ , * } that can operate with any kind of numbers: negative, positive, rational or real. Operators are not bound to specific data types. Therefore these operators are called "polymorphic".
Some languages define different operators for Integers and Floating decimal numbers. For example in OCaml the operator "/" can divide Integers while "/." can divide Floating point decimal numbers. This is unexpected for a mathematician who is expecting to use one single operator for division.
In EVE, operators are mapped to functions. To design polymorphic operators we overload the function signature using type dispatch. The dispatch is using left side operand first, this is the leading operand. For unary operators there is only right side operand so this becomes the leading operand.
In Latin the "falsus" and "verum" are translated in English to "false" and "true". In many languages False and True are two constants. In Fortran we use to have .T. and .F. condtants while in C we use to have {0, 1}. Two values.
name | value | binary |
---|---|---|
False | Logic.False | 00000000 00000000 |
True | Logic.True | 00000000 00000001 |
** explicit initialization
global
set False = 0b0 :Byte;
set True = Ob1 :Byte;
Internal design
Probably best to define Logic type is Ordinal:
class Logic = {False:0 , True} <: Ordinal;
Logical expressions
A logical expression is a demonstration or logical deduction having result True or False. Operator precedence is: {not, and, or, xor}. The order of operations can be controlled using operator precedence and round parentheses.
Result of logical expressions can be used into a conditional statement to make a decision. Also results of logical expressions can be stored in logical variables to be used later in other conditions.
In Eve you can define new data types. Types are derived from system types. When you create a new type you can use { }, [ ] or ( ) to define the type structure.
User types are defined in a region that start with keyword "type". You can define multiple types on several lines separated by semicolumn and end of line.
Gradual typing is a type system in which some variables may be given a type. Some variables or members may be left un-typed. Therefore some type errors are reported at compile-time some other at run-time.
Gradual typing is different than type inference. On type inference the type specification is missing but default type is determined by logic deduction from literals and expressions.
A Variant is a polymorphic variable that can have one of a list of types but only one at a time. When the value is established, one type to hold that value is created.
** define variant subtype
class VNamme = {Type1 | Type2 | ... } <: Variant;
** declare variable (with initial value)
global
set v = value :VNamme;
For this we use a special type Null
** define nullable variant
driver test_nulable:
class Number = { Integer | Double | Null } <: Variant;
** use nullable variant
global
set x: Number;
process
let x = 10; -- x is Integer
let x = 45.5; -- x is Double
let x = Null; -- x is Null
expect x is Null;
return;
A variant can establish its data type at runtime:
In next example we use a variant that can be Double or Integer.
#variant type demo
driver variant_type:
** tefine variants
set v, x ,t: { Double | Integer };
process
** safe conversion
let t := 1 / 2; -- make t Double
print type(t) -- Double
let t := 12; -- change type
print type(t) -- Integer
** unsafe conversion
let x := 1.5; -- x is Double
let v := 1; -- v is Integer
let v := x; -- v becomes Double
print type(v) -- Double
return;
A variant is a way to create a generic routine. For this we use variant parameters:
# variant parameter in routines
driver variant_params:
** define a subroutine that can swap two numbers
** requires input/output parameters marked with @
routine swap(@x, @y: {Integer | Double} ):
** check type to be the same
expect type(x) = type(y);
** swap x, y values
new i := x; -- intermediate
let x := y; -- first swap
let y := i; -- second swap
return;
process
** invert two Integer numbers
new x := 10;
new y := 20;
call swap(x, y);
expect (x == 20) and (y == 10);
** invert two Double numbers
new a := 1.5;
new b := 2.5;
call swap(a, b);
expect (a == 2.5) and (b == 1.5);
return;
In Eve we represent calendar date.
Date storage
Date literals
When can create a date literal using 3 format functions:
Note: A reversible function is overloaded.
#overloaded function
process
new date := "2019/01/30" as YDM;
** convert string in Date type
print date as YDM; -- 2019/01/30
print date as DMY; -- 30/01/2019
print date as MDY; -- 01/30/2019
return;
For time you can use Time or Duration data types.
Time format is created from string literals using two reversible functions: t12() and t24(). These functions are using constants T12 and T24 that represent standard time format.
ss: can be 0..60 seconds
xx: can be: (am/pm)
# time demo
driver time_demo:
process
** define 3 variable of same type
new time1, time2, time3: Time;
** alter variables using "as" operator
let time1 := "00:23:63" as T24;
let time2 := "23:63:59,999" as T24;
let time3 := "11:63:59pm,10" as T12;
** check time
expect time1.h == 23;
expect time1.m == 63;
expect time1.s == 59;
expect time3.ms == 10;
expect time3.t == pm;
return;
Duration is represented as a number on 32 bits. Internally is stored as milissecond. It can hold almost 20 days. For longer duration, you must use Time object.
# duration demo
driver duration_demo:
process
** define 3 variable of same type
new d1 := 10ms; -- 10 millisec
new d2 := 1s; -- 1000 millisec
new d3 := 1m; -- 60000 millisec
** create duration from string
new d4 := Duration("01:01:01");
** check time
print d3 -- 60000ms
print d4 -- 3661000ms
return;
Read next: Classes