Complex Values & Types
So far we only considered atomic values and types, such as string values or integers, which are not sufficient for most contracts. In Ergo, values and types are based on the Concerto Modeling (often referred to as CTO models after the .cto
file extension). This provides a rich vocabulary to define the parameters of your contract, the information associated to contract participants, the structure of contract obligation, etc.
In Ergo, you can either import an existing CTO model or declare types directly within your code. Let us look at the different kinds of types you can define and how to create values with those types.
Arrays
Array types lets you define collections of values and are denoted with []
after the type of elements in that collection:
String[] // a String array
Double[] // a Double array
You can write arrays as follows:
["pear","apple","strawberries"] // an array of String values
[3.14,2.72,1.62] // an array of Double values
You can construct arrays using other expressions:
let pi = 3.14;
let e = 2.72;
let golden = 1.62;
[pi,e,golden]
Ergo also provides functions to manipulate arrays as parts of its standard library. The following example uses the sum
function to calculate the sum of all the elements in the prettynumbers
array.
let pi = 3.14;
let e = 2.72;
let golden = 1.62;
let prettynumbers : Double[] = [pi,e,golden];
sum(prettynumbers)
You can access the element at a given position inside the array using an index:
let fruits = ["pear","apple","strawberries"];
fruits[0] // Returns: some("pear")
let fruits = ["pear","apple","strawberries"];
fruits[2] // Returns: some("strawberries")
let fruits = ["pear","apple","strawberries"];
fruits[4] // Returns: none
Note that the index starts at 0
for the first element and that indexed-based access returns an optional value, since Ergo compiler cannot statically determine whether there will be an element at the corresponding index. You can learn more about how to handle optional values and types in the Optionals Section below.
Classes
You can declare classes in the Concerto Modeling Language (concepts, transactions, events, participants or assets) by importing them from a CTO file or directly within your Ergo program:
define concept Seminar {
name : String,
fee : Double
}
define asset Product {
id : String
}
define asset Car extends Product {
range : String
}
define transaction Response {
rate : Double,
penalty : Double
}
define event PaymentObligation{
amount : Double,
description : String
}
Once a class type has been defined, you can create an instance of that type using the class name along with the values for each fields:
Seminar{
name: "Law for developers",
fee: 29.99
}
Car{
id: "Batmobile4156",
range: "Infinite"
}
TechNote: When extending an existing class (e.g.,
Car extends Product
), the sub-class includes the fields from the super-class. SoCar
includes the fieldrange
which is locally declared and the fieldid
which is declared inProduct
.
You can access fields for values of a class type by using the .
operator:
Seminar{
name: "Law for developers",
fee: 29.99
}.fee // Returns 29.99
Records
Sometimes it is convenient to declare a structure without having to declare it first. You can do that using a record, which is similar to a class but without a name attached to it:
{
name : String, // A record with a name of type String
fee : Double // and a fee of type Double
}
You do not need to declare that record, and can directly write an instance of that record as follows:
{
name: "Law for developers",
fee: 29.99
}
Typing
return { name: "Law for developers", fee: 29.99 }
in the Ergo REPL, should answerResponse. {name: "Law for developers", fee: 29.99} : {fee: Double, name: String}
.
You can access the field of a record using the .
operator:
{
name: "Law for developers",
fee: 29.99
}.fee // Returns 29.99
Enums
Here is how to declare an enumerated type:
define enum ProductType {
DAIRY,
BEEF,
VEGETABLES
}
To create an instance of that enum:
DAIRY
BEEF
Optionals
An optional type can contain a value or not and is indicated with a ?
.
Integer? // An optional integer
PaymentObligation? // An optional payment obligation
Double[]? // An optional array of doubles
A an optional value can be either present, written some(v)
, or absent, written none
.
let i1 : Integer? = some(1); i1
let i2 : Integer? = none; i2
To operate on an optional type, you need to say what to do when the value is present and what to do when the value is not present. The most general way to do that is with a match expression:
This example matches a value which is present:
match some(1)
with let? x then "I found " ++ toString(x) ++ " :-)"
else "I found nothing :-("
and should return "I found 1 :-)"
.
While this example matches against a value which is absent:
match none
with let? x then "I found " ++ toString(x) ++ " :-)"
else "I found nothing :-("
and should return "I found nothing :-("
.
More details on match expressions can be found in Advanced Expressions.
For conciseness, a few operators are also available on optional values. One can give a default value when the optional is none
using the operator ??
. For instance:
some(1) ?? 0 // Returns the integer 1
none ?? 0 // Returns the integer 0
You can also access the field inside an optional concept or an optional record using the operator ?.
. For instance:
some({a:1})?.a // Returns the optional value: some(1)
none?.a // Returns the optional value: none