Sage-Code Laboratory
index<--

Data Types

The purpose of any programming language is to manipulate data. On the lowest level, data is basically 0 and 1 signals. Eve is a high level language. It has complex data types. Eve has a gradual-typing system that will be explained below.

Page bookmarks



Native Types

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

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
Note: Primitive types get zero initial values automatically and can not be Null. This is a major thing in EVE. No Null primitive-types! Numeric values have initial value zero. An emty symboll is NIL.

Numeric Types

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:

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:

Continuous numbers

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.

Numeric literals

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}

Example:


#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

Single Symbol

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.

Examples


process
  new letter   := 'a';
  new capital  := 'B';
  new number   := '5';
  new unicode  := 'β';
  new operator := '¬';
  ...
return;

Unicode Literal

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 Types

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

Range

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.

Usecases:

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.

Syntax:

new l := (min..max:ratio); // list
new a := [min..max:ratio]; // array
new d := {min..max:ratio}; // data set

Notes:

Example:

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;

Example:

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;

Decimal range

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;

Data Coercion

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.

Notes:

#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.

Default types

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

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.

Example 1:

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;

Example 2

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;

Type verification

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;

Printing type()

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;

Polymorphic operators

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.

Logical type

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

Syntax:


** 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.

User types

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

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.

Variant Types

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.

Syntax:

** define variant subtype
class VNamme = {Type1 | Type2 | ... } <: Variant;

**  declare variable (with initial value)
global
  set v = value :VNamme;

Properties

Making a null-able type

For this we use a special type Null

Example:


** 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;

Usability

A variant can establish its data type at runtime:

Example 1:

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;

Example 2:

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;

Calendar date

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;

Time & Duration

For time you can use Time or Duration data types.

Time

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)

Example:

# 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

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.

Example

# 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