EK9 Built in Types
EK9 naturally comes with a number of different built-in types, but also a set of collection types; these have been designed in from the outset and form the core of the language and its features.
There is a blog post here on strong typing and why it is important. In addition this is another post on modern types for languages. This makes the case for adding more sophisticated types to a programming language.
The Types
- Any
- Boolean
- Character
- String
- Integer
- Float
- Bits
- Time
- Duration
- Millisecond
- Date
- DateTime
- Money
- Locale
- Colour
- Dimension
- Path
- JSON (JavaScript Object Notation)
- RegEx (Regular Expression)
- Collection Types
There are several predefined classes and types available; these are detailed in sections collection types, standard types and network types as they are used for specific purposes; these are more like a standard library/API. Whereas the types above are the basic building block types you will need. Most of the names above will be quite obvious and a number of examples of their use are give below.
Both String and Bits are also a sort of collection, String can also be viewed as an ordered list of Characters and Bits and order list of Booleans.
All of the types above are Objects, they are not primitive types, and they are all passed by reference and not by value. Please also note that as discussed at length in the operators and basics section a variable of any type can also be allocated space to hold a value (or not), but can also be un set. This means it does have space to hold a value but the value it holds is meaningless. Please also read the declarations section as this section will be using that terminology.
EK9 does not have static methods that are attached to classes; but has a number of mechanisms that facilitate that sort of functionality. The first is the use of functions to return an Object in a specific state, the second is the use of methods on classes that do not alter/mutate the Object they are called on but do return some value. This might seem a little strange, but it removes the fixed and binding nature of static methods on a fixed class, it reduces the need for specific Factory classes to some degree.
Any
Variables of type Any can have 'any' value and be of 'any' type, including being a function:
- Unknown not set
- Any value that the real type can hold
EK9 has the type of 'Any' to enable full polymorphic passing of data items. All types and function have the eventual 'super' of 'Any'. However there are no call semantics/methods or operators on 'Any' other than the 'is-set' operator.
When working with the 'Any' type, there comes a time when you will need to 'Know' the type of the object in hand. At this point to make used of those objects/functions employ a 'dispatcher'. See Advanced Class Methods for more details on this.
Boolean
Variables of type Boolean can have the values of:
- Unknown not set
- True
- False
This is very different from most other languages in the sense that most other languages only support true and false.
- //Declare a Boolean - using type inference but with no value yet known.
- reviewAccess ← Boolean()
- //Declare a Boolean - with type inference with a known initial value
- allowModification ← false
- //Declare a Boolean - but with no space allocated to hold the value.
- provideAccess as Boolean
- //Declare a Boolean - but with no value yet known.
- denyAccess as Boolean: Boolean()
- //Declare a Boolean - without type inference
- allowViewing as Boolean := true
To understand what you can do with the boolean values; review the operators section. In general, it is best to employ type inference whenever possible. The Boolean is the simplest type.
Character
Variables of type Character can have the values of:
- Unknown not set
- Any Single character value
- Unicode escape sequence (four value only) ie '\u00E9' for (é)
The above represents a single character, to represent a word or a sentence then use the String type.
- //Some examples of using Character, some with type inference
- c1 ← Character()
- c2 ← 's'
- c3 as Character := '\u00E9'
- //Comparisons
- c1 < c2
- c1 <= c2
- c1 > c2
- c1 >= c2
- c1 == c2
- c1 != c2
- c1 <> c2
- //Other operators
- length c1 //results in not set
- length c2 //results in 1
- length c3 //results in 1
- c3.length() //same result (1) - but written in object form
- #? c3 //results in 233 (hashcode method)
- c3.#?() //same result (233) - but written in object form
- //Promotion to String
- str as String := #^c2 //results String of "s"
- str ← #^c2 //also results String of "s" - uses type inference
- str ← c2.#^() //also results String of "s" - uses type inference and object form
- //Standard methods
- str ← c2.upperCase() //results in 'S'
- str ← c2.upperCase().lowerCase() //results in 's'
String
Variables of type String can have the values of:
- Unknown not set
- Empty value of ""
- Any number of Characters
The String is used to represent an order sequence of characters. It can be altered and can grow and unlike some languages does not have 'termination' character. It also implements the comparison and functional operators.
But note the important distinction between not set and "" an empty String. It also implements the following methods and operators.
- empty - is the String empty ie equal to ""
- length - how many characters does the String have
- first - get the first character ('#<')
- last - get the last character ('#>')
- contains - See comparison for details
- matches - See comparison for details
- '<=>' - See comparison for details
- '<~>' - See comparison for details
It also adds a few additional methods.
- count(Character) - how many times does Character appear in the String
- clear - Set the String to un set
- '+=' - Add the String, Character or List of Character to the current String
- '|' - Used with a Pipeline to add a String (even if it is currently un set)
- trim - Remove white space either side of a String
- rightPadded- add space to right of string to a length
- leftPadded - add space to left of string to a length
- upperCase - uppercase the String
- lowerCase - lowercase the String
String Literals
The String literal can take two forms in EK9; this is to support fixed Strings and also Interpolated Strings.
- //Declare a fixed String literal
- language ← "EK9"
- //Declare a variable to be used in an interpolated String
- age ← 30
- //Use the variable in an interpolated String
- message ← `Your age is ${age}`
The interpolated String starts and end with a back tick '`'. The sequence '${' indicates that the compiler should now look for a variable/function/method/expression in the scope to execute. This is the same sort of syntax as used in Javascript. This must return a variable of a type that either is a String, could be coerced to a String or has a '$' String operator on it.
Escapes in String
In a normal String i.e. "A Test" the " character must be escaped: i.e. "A \"Test\"", but back tick '`' and '$' do not need to be escaped.
If you have a handful of Strings you need to use in code; then directly defining them in the source code might be done. But for very large text elements or a large number of text elements in different spoken languages consider using the text construct. This enables you to separate and format plain text Strings and interpolated Strings in a specific construct away from source code.
In an interpolated String the converse of escaping characters is true: i.e. `Your \`salary\` is \$${salary}`.
Interpolation Example
See text properties for defining a large number of String text items.
#!ek9
defines module introduction
defines record
Person
firstName String: String()
lastName String: String()
Person()
->
firstName String
lastName String
assert firstName? and lastName?
this.firstName :=: firstName
this.lastName :=: lastName
operator $ as pure
<- rtn String: `${firstName} ${lastName}`
defines function
getAuthor()
<- rtn Person: Person("Steve", "Limb")
defines program
ShowStringInterpolation()
stdout <- Stdout()
normal <- "With normal double quote \" text"
stdout.println("[" + normal + "]")
var <- 90.9
interpolated <- `A tab\t ${var} a " but a back tick \` the dollar have to be escaped \$\nOK?`
stdout.println("[" + interpolated + "]")
me <- Person("Steve", "Limb")
stdout.println(`Author: ${me} or via function: ${getAuthor()}, direct access "${me.firstName}"`)
//EOF
This above program produces the following output:
[With normal double quote " text]
[A tab 90.9 a " but a back tick ` the dollar have to be escaped $
OK?]
Author: Steve Limb or via function: Steve Limb, direct access "Steve"
The two specific types of String literal enable a choice of functionality for creating output Strings. They have been designed to be explicitly different: using either " or `. More importantly the variables in interpolated string must always be used with ${...} and never just $ by itself.
Accessing Characters in a String
If you want to access specific a specific Character or a range of Characters in a String, pipeline processing as shown below.
#!ek9
defines module introduction
defines function
isO() as pure
-> c as Character
<- o as Integer: c == 'o' or c == 'O' <- 1 else 0
defines program
ShowStringType()
i1 <- "The Quick Brown Fox"
i2 <- "Jumps Over The Lazy Dog"
space <- " "
i3 <- String()
hashCode <- #? i1
//Hashcode is -732416445
sentence1 <- i1 + " " + i2
//sentence1 is "The Quick Brown Fox Jumps Over The Lazy Dog"
l1 <- length sentence1 //will be 43
l2 <- sentence1.length() //will also be 43 but in object form.
oCount <- sentence1.count('o') //will be three as three 'o' in sentence
oOCount <- cat sentence1 | map with isO | collect as Integer //will be four as 1 'O' and 3 'o'
//Alternative way of joining values
parts <- [i1, space, i2]
sentence2 <- cat parts | collect as String
//sentence2 is "The Quick Brown Fox Jumps Over The Lazy Dog"
//OR
sentence3 <- cat [i1, space, i2] | collect as String
//sentence3 is "The Quick Brown Fox Jumps Over The Lazy Dog"
//Mechanism to extract parts of a String
jumpingBrownFox <- cat sentence2 | skip 10 | head 15 | collect as String
//jumpingBrownFox is "Brown Fox Jumps"
//Note that there is no issue with extending beyond the length of the sentence
dog <- cat sentence2 | skip 40 | head 15 | collect as String
//dog is "Dog"
//Even this is 'safe' and the result is just not set
nonSuch <- cat sentence2 | skip 50 | head 5 | collect as String
//nonSuch is not set
//EOF
There are few new and different ideas going on in the example above, the first thing to note are the standard operators and the type inference and also the length operator in 'object form'.
But importantly the two mechanisms that can be used to join String together; the standard '+' addition operator, but also the use of a List of String being collected into a single String (with the '|' operator).
There is the use of the Stream/Pipeline commands map, skip and head. These are used as there is no way to index Characters on a String. This is by design, this approach is safe in terms of indexing past the end of the actual length as shown in the example above nonSuch just is not set to any value (ie un set).
As an aside most Object Oriented languages tend to add more methods to Object types for tasks like getting substrings. EK9 does provide some methods on Objects, but mainly focuses on providing tooling to enable you to write your own functions to provide this type of functionality.
Clearly very frequent operations such as converting to lower case or upper case should be provided via the Object; but other operations that are less frequent should be developed as a standard library of functions by the developer. This keeps the EK9 standard library smaller, over time the EK9 standard library will increase but in a controlled manner.
Integer
Variables of type Integer can have the values of:
- Unknown not set
- Negative value up to -9,223,372,036,854,775,808
- Positive value up to 9,223,372,036,854,775,807
The comparison, mathematical , modification and ternary operators have been covered in other sections. But here are a couple of examples of operations on Integers for clarity. Note that idea of plus/minus infinity (or NaN) does not exist in EK9, you can make the argument infinity is not known; in EK9 that is represented by un set.
- //Some examples of using Integer
- i1 ← -9223372036854775808
- hashCode ← #? i1 //results in -2147483648
- asFloat ← #^ i1 //promotion to Float results in -9.223372036854776E18
- //Division operations with zero
- nonResult ← 0 / 90 //result is 0 (zero)
- nonResult := 90 / 0 //result is un set i.e not known
- nonResult := 0 / 0 //result is un set
There are no unsigned, short, long or other types; Integer is the only type that holds whole numbers.
Pipeline with Integer result
The accumulation of amounts (Integer/Float values etc.) is quite common; many languages have very distinct syntax for this specific task. EK9 does not have a specific syntax like list comprehensions, but it does have a much more general syntax that is covered in depth in the Streams/Pipelines section.
But as this section describes the use of the Integer type, a short example of how the Integer type can be used in a pipeline is given below. The objective of the code below is two-fold, first to get a list of Integers starting at 1 and incrementing by two up to and including 11; secondly to get the total of those values. The example below could have been done with a simple for loop, but it is shown here as a pipeline. The key point in the example is the collect as Integer syntax, you can use your own types here if you wish.
- theValues as List of Integer := List() //create a list to capture values in
- //Simple pipeline processing
- intSum ← for i in 1 ... 11 by 2 | tee in theValues | collect as Integer
- //theValues has the following contents [1, 3, 5, 7, 9, 11]
- //intSum has the value 36
The first couple of examples here and all the standard operators are probably what you would normally expect with and Integer type. But the last example is probably something very different from what you've seen before. This is discussed in a little more detail here but is much more detail in the Streams/Pipelines section. In general this pipeline approach enables reuse and collection of objects during the pipeline processing - but in a standard syntax.
As an example, later sections will discuss Durations as part of date/time processing, it is possible to use a pipeline like the one above but with durations rather than integers. But the concept of the processing pipeline and accumulations, mapping and teeing is the same irrespective of type.
It is accepted that the syntax is not as terse as list comprehensions.
Float
Variables of type Float can have the values of:
- Unknown not set
- Negative values -4.9E-324 to -1.7976931348623157E308
- Positive values 4.9E-324 to 1.7976931348623157E308
As with the Integer the comparison, mathematical , modification and ternary operators have been covered in other sections.
- //Some examples of using Float
- i1 ← -4.9E-324
- hashCode ← #? i1 //results in -2147483647
- //Division operations with zero
- nonResult ← 0.0 / 90.0 //result is 0.0 (zero)
- nonResult := 90.0 / 0.0 //result is un set i.e not known
- nonResult := 0.0 / 0.0 //result is un set
As you can see the Float has most of the same operators as Integer (except promotion '#^') but has variable precision (minimum and maximum values). Note it too can be used in pipeline processing and can receive Float values.
Bits
Variables of type Bits can have the values of:
- Unknown not set
- Any number of bits
The Bitwise operators have been discussed in the operators section. The '+' and '+=' operators are provided to join Bits together as is the '|' operator when used with Streams/Pipelines.
- //Bits values
- a ← 0b010011
- b ← 0b101010
- c ← 0b010011
- //Equality Operators
- result ← a == b //result would be false
- result ← a <> b //result would be true
- result ← a < b //result would be true
- result ← a <= b //result would be true
- result ← a > b //result would be false
- result ← a >= b //result would be false
- result ← a == c //result would be true
- result ← a != c //result would be false
- result ← a <> c //result would be false (alternate syntax)
- //Joining/Adding Bits
- set6 ← 0b010011
- set7 ← 0b101010
- set6set7 ← set6 + set7 //result would be 010011101010
- set6true ← set6 + true //result would be 0100111
- set6false ← set6 + false //result would be 0100110
- set6false += true //set6false would now be 01001101
- //Bitwise operations
- set6 ← 0b010011
- set7 ← 0b101010
- ored ← set6 or set7 //result would be 0b111011
- xored ← set6 xor set7 //result would be 0b111001
- anded ← set6 and set7 //result would be 0b000010
- notted ← ~set6 //result would be 0b101100
- alsonotted ← not set6 //result would be 0b101100
As you can see in the addition example above, Bits are not treated like numbers.
A Stream/Pipeline approach should be taken to access ranges of specific bits from a set of Bits as shown below.
- //Extracting ranges of Bits
- //assumes function booleanToBits has been defined
- partial ← set6false | skip 3 | map booleanToBits | collect as Bits
- //partial has the value of 0b01001
Importantly Bits are read right to left as you would expect the least significant bit is to the right and the most significant bit is to the left. So the streaming of the bits (Boolean values) starts with the least significant bit. Hence, in the example above the "101" is skipped this just leaves the "01001" left. You can use head and tail to further refine your sub-selection.
Hopefully you can now start to see that rather than defining an Object specific set of methods on classes to get the result of a sub-selection; the functional methodology here really is repeatable and consistent. So given a page it is feasible to filter and sub-select a number of paragraphs using various criteria.
This point is quite important and is in no way aimed at denigrating and Object-Oriented approach, but rather splits development between Object Orientation and a Functional Programming. You may find this difficult at first, but when you think "hey why is there no method on this class to do X", it's probably time to consider a more functional approach to the problem.
Over time you will find your classes become smaller/concise, but your library of functions grows and is much more reusable in different contexts.
Time
Variables of type Time can have the values of:
- Unknown not set
- Any valid time of the day
Time represents the concept of the time of day HH:MM or HH:MM:SS. This is in isolation to any particular date. There is a separate type that represents the time of day on a particular date in a particular time zone This is a DateTime.
The comparison and ternary operators have been covered in other sections. But here are a couple of examples of operations on Time for clarity. Time can also be used with Duration. Indeed, it only makes sense to use the '+', '-', '+=' and '-=' operators on Time with a Duration. Moreover, it makes no sense to be able to add, multiply or divide by a Time (though with a Duration this does make sense). But it is logical to be able to subtract one Time from another to get a Duration.
Some examples and also a demonstration of the assert key word, very useful for unit testing for pre-conditions / post-conditions.
#!ek9
defines module introduction
defines program
ShowTimeType()
//These declarations and operations should be obvious by now if you've read the other sections.
t1 <- 12:00
t2 <- 12:00:01
t3 <- 12:00:01
t4 <- Time()
t5 <- Time(12, 00)
t6 <- Time(12, 00, 01)
assert t1 <> t2
assert t2 == t3
assert t1 < t2
assert t1 <= t2
assert t2 <= t3
assert t2 > t1
assert t2 >= t1
assert t3 >= t2
//Check not set
assert ~t4?
assert t1 == t5
assert t2 == t6
//Just create a Time object to get the current time
t4a <- SystemClock().time() //t4a will be set to current time
t4b <- t4.startOfDay() //t4b will be set to 00:00:00
t4c <- t4.endOfDay() //t4c will be set to 23:59:59
//Now actually alter t4 itself
t4.set(SystemClock().time()) //t4 will now be set to the current time
assert t4?
t4.setStartOfDay() //Will be set to 00:00:00
assert t4?
t4.setEndOfDay() //Will be set to 23:59:59
assert t4?
//Durations
d1 <- PT1H2M3S //one hour, two minutes and three seconds
d2 <- P3DT2H59M18S //three days, two hours, fifty nine minutes and eighteen seconds
//Note how only the hours minutes and seconds of 'd2' is used.
//Addition operations
t4 := t5 + d1
assert t4 == 13:02:03
t4 += d2
assert t4 == 16:01:21
//Subtraction operations
t4 := t5 - d1
assert t4 == 10:57:57
t4 -= d2
assert t4 == 07:58:39
//Get the Duration between two times
d3 <- 12:04:09 - 06:15:12
assert d3 == PT5H48M57S
//Note the different views of a Duration
d4 <- 06:15:12 - 12:04:09
assert d4 == PT-6H11M3S
assert d4 == PT-5H-48M-57S
//Example of building a time from a number of durations
durations as List of Duration := [d1, d2]
t7 <- cat durations | collect as Time
assert t7 == 04:01:21
//t7 will default to start of the day plus each of the durations piped in
//Note that time will just roll over just after 23:59:59
//It is also possible to get the hour, minute and second from a Time
second <- t7.second()
minute <- t7.minute()
hour <- t7.hour()
assert 4 == hour
assert 1 == minute
assert 21 == second
//EOF
Duration
Variables of type Duration can have the values of:
- Unknown not set
- Any positive duration of time from 1 second to years
- Any negative duration of time from 1 second to years
The Duration has been shown in the previous example using Time. With Time the duration values of hour, minute and second are really the only relevant ones. But a Duration can also hold years, months and days. Which will be important when the types Date and DateTime are covered.
The format of the duration is as per ISO 8601 Durations, though fractional values are not supported see Millisecond on how to work with fractions of a second. Durations take the following format: P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S.
The 'P' denotes a period, then each '[n]' value is then followed by what the field is to be applied. So for example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds".
It is also possible to use 'W' for weeks in addition to months and days.
Durations are designed to be used without milliseconds and in some ways anything less than a second is really in a different category of time (as a human would understand). But clearly it is possible to multiply up milliseconds so they become significant (to a human). The Millisecond type and the Duration are designed to work together (but are different).
- //Some examples of using Duration
- d1 ← PT1H2M3S //results in 1 hour, 2 minutes and 3 seconds
- d2 ← P3DT2H59M18S //results in 3 days, 2 hours, 59 minutes and 18 seconds
- d3 ← P2Y6W3DT8H5M8S //results in 2 years, 45 days, 8 hours, 5 minutes and 8 seconds
- //Note that 6 weeks was converted to 42 days and then the additional 3 days are added to that
- d4 ← P2Y2M1W3DT8H5M8S //results in 2 years, 2 months, 10 days, 8 hours, 5 minutes and 8 seconds
- d5 ← P2W //results in 14 days
- //Mathematics with durations
- d6 ← PT1H2M1S
- d6a ← d6 * 2 //results in PT2H4M2S i.e. twice the duration in d6
- d6b ← d6 * 8.25 //results in PT8H31M38S
- d6c ← d6b / 3 //results in PT2H50M32S
- d6d ← d6 + PT20M //results in PT1H22M1S
- d6e ← d6 - PT2M6S //results in PT59M55S
- d6e += PT2H5S //results in PT3H
As you can see from the examples above; once you have a Duration of any period of time you can use normal mathematics to manipulate that Duration. The Duration has logical and rational mathematics operators; for example it makes no sense to add an Integer, Float or String to a Duration. However, it does make sense to be able to add and subtract a Duration, but also multiply and divide a Duration by an Integer or Float.
It is these specific types and your own defined types like records and classes where adding specific operators and overloading those operators with varying (but suitable) types that enable EK9 to remain readable and easy to understand.
Millisecond
Variables of type Millisecond can have the values of:
- Unknown not set
- Any valid Integer value
The format of the Millisecond literal is digits'ms' for example 250ms is two hundred and fifty milliseconds. The compiler will check the validity of millisecond literals. You can have any valid integer (positive or negative). So 6000ms is actually 6 seconds. Naturally you can add these types to Durations and if there values result in seconds, minutes or hours etc. then those will be added to the Duration. You can also convert them to a Duration by using the duration() method. The value of the milliseconds will be rounded up to the nearest second where necessary, for example 1501ms will round up to two seconds.
Naturally the Millisecond type supports many of the normal mathematical and comparison operators and can be promoted to a Duration and also constructed with the Duration type.
Date
Variables of type Date can have the values of:
- Unknown not set
- Any valid date
The format of the date literal is YYYY-MM-DD for example 2020-11-18 is the Eighteenth of November 2020. The compiler will check the validity of date literals, so for example declaring a date of '2020-11-31' would not even compile.
It is important to note that the Date is not associated with any sort of time zone. It is just a Date so this could be used to hold a date of birth for example. If you need to be exact on the Date then using a DateTime would be more appropriate.
The range of operations on Date are very similar to those offered on the Time type; comparison and ternary for example. As with Time Duration can be used to do calculations, it only makes sense to use the '+', '-', '+=' and '-=' operators on Date with a Duration. Moreover, it makes no sense to be able to add, multiply or divide a Date (though with a Duration this does make sense). But it is logical to be able to subtract one Date from another to get a Duration.
#!ek9
defines module introduction
defines program
ShowDateType()
//3rd of October 2020
date1 <- 2020-10-03
date2 <- 2020-10-04
date3 <- 2020-10-04
date4 <- Date()
date5 <- Date(2020, 10, 03)
date6 <- Date(2020, 10, 04)
assert date1 <> date2
assert date2 == date3
assert date1 < date2
assert date1 <= date2
assert date2 <= date3
assert date2 > date1
assert date2 >= date1
assert date3 >= date2
assert ~date4?
assert date1 == date5
assert date2 == date6
date4a <- Date().today()
assert date4a?
date4.setToday() //Now set to the current date
//Durations
d1 <- P1Y1M4D //one year, one month and four days
d2 <- P4W1DT2H59M18S //twenty nine days, two hours, fifty nine minutes and eighteen seconds
//Note how only the days part of 'd2' is used
date4 := date5 + d1
assert date4 == 2021-11-07
date4 += d2
assert date4 == 2021-12-06
d3 <- date4 - date5
assert d3 == P1Y2M3D
//Pipeline with durations and dates.
durations as List of Duration := [d1, d2]
//Because the epoch is 1970-01-01 accumulation starts from that date
date7 <- cat durations | collect as Date
assert date7 == 1971-03-06
//If you want to accumulate from a specific date you can do this instead
date8 <- 0000-01-01
cat durations >> date8
assert date8 == 0001-03-06
dayOfMonth <- date8.day()
monthOfYear <- date8.month()
year <- date8.year()
dayOfTheWeek <- date8.dayOfWeek()
assert dayOfMonth == 6
assert monthOfYear == 3
assert year == 1
assert dayOfTheWeek == 2
//EOF
When using Dates and Durations you must take care because transitioning and adding durations to specific points in time and subtracting points in time to recover Durations can lead to some strange effects. For example adding a Duration of a few months and days to date where the span will cover a leap year will result in a new date that takes that particular year (leap) into account.
Also the addition of durations uses 30 days per month to when summing up days. This means that adding a number of durations together and then applying that to a date will give one result. Whereas, taking an initial date and adding each duration in turn will give a different date.
If you are looking for a final date from a base date when using a number of durations, apply each duration in turn to the base date, rather than summing all the durations and applying that sum of durations to the base date.
- //Adding Durations to a Base Date
- date ← 2019-06-01
- //Definition of durations omitted
- cat durations >> date
- //The Easiest way to move a date on is by a number of durations
By using the approach above and number of positive, negative or even empty durations can be applied to a date. The date will just increment by the appropriate amount but take into account the base date it started from and include leap years and the normal variation in the days per month.
DateTime
This is the first built in type that is a sort of aggregate. Whilst this type really holds and instant in time it also has the concept of a timezone. So it relates Date, Time and a timezone together all in one type.
Variables of type DateTime can have the values of:
- Unknown not set
- Any valid date/time within a time zone
The format of the DateTime literal is YYYY-MM-DDTHH:MM:SS+/-HH:MM for example 2020-11-18T13:45:21-01:00 is the eighteenth of November 2020 @ thirteen forty-five and twenty-one seconds in a time zone offset by minus one hour from UTC. But also note that 'Z' Zulu or GMT/UTC can also be used as a shortcut for +/-00:00 as follows 2020-11-18T14:45:21Z.
Working with Time and Date as separate concepts is quite natural for most developers, but DateTimewith timezones sometimes take a bit more thought.
For example is 2020-11-18T13:45:21-01:00 less than or greater than 2020-11-18T13:45:21Z? Seeing the "-01:00" may confuse you.
It is greater; if you adjust them both to the same time zone by adding/removing the hour and making the same adjustment to the hour of the day is becomes obvious.
The range of operations on DateTime are very similar to those offered on the Date and Time types; comparison and ternary for example. As with Date; Duration can be used to do calculations.
The following example with DateTime uses timezones - doing mathematics with dates, times and timezone combinations can be a bit confusing. The main thing to think about here is the same instant in time. i.e. if you are in London what time (and date) will it be right now in New York.
#!ek9
defines module introduction
defines program
ShowDateTimeType()
//3rd of october 2020 @ 12:00 UTC
dateTime1 <- 2020-10-03T12:00:00Z
dateTime2 <- 2020-10-04T12:15:00-05:00
dateTime3 <- 2020-10-04T12:15:00-05:00
dateTime4 <- DateTime()
dateTime5 <- DateTime(2020, 10, 03, 12)
dateTime6 <- DateTime(2020, 10, 04, 12, 15)
assert dateTime1 <> dateTime2
assert dateTime2 == dateTime3
assert dateTime1 < dateTime2
assert dateTime1 <= dateTime2
assert dateTime2 <= dateTime3
assert dateTime2 > dateTime1
assert dateTime2 >= dateTime1
assert dateTime3 >= dateTime2
assert ~dateTime4?
assert dateTime1 == dateTime5
dateTime4a <- DateTime().today()
assert dateTime4a?
dateTime4.setToday() //Now set to the current dateTime
//Durations
d1 <- P1Y1M4D //one year, one month and 4 days
d2 <- P4W1DT2H59M18S //twenty nine days, two hours, fifty nine minutes and eighteen seconds
dateTime4 := dateTime5 + d1
assert dateTime4 == 2021-11-07T12:00:00Z
dateTime4 += d2
assert dateTime4 == 2021-12-06T14:59:18Z
d3 <- dateTime4 - dateTime5
assert d3 == P1Y2M9DT2H59M18S
d4 <- dateTime2 - dateTime3
assert d4 == PT0S
//Think carefully about this (focus on the instant)
//At ZULU/GMT/UTC what time will it be in the -05:00 time zone?
//2020-10-04T12:15:00-05:00
//2020-10-04T12:15:00Z
d5 <- dateTime3 - dateTime6
assert d5 == PT5H
d6 <- dateTime3.offSetFromUTC()
assert d6 == PT-5H
//processing of durations
durations as List of Duration := [d1, d2]
dateTime7 <- cat durations | collect as DateTime
//Because the epoch is 1970-01-01
assert dateTime7 == 1971-03-06T02:59:18Z
//From a specific date time add on the durations in turn
dateTime8 <- 0000-01-01T00:00:00Z
cat durations >> dateTime8
assert dateTime8 == 0001-03-06T02:59:18Z
dayOfMonth <- dateTime8.day()
monthOfYear <- dateTime8.month()
year <- dateTime8.year()
dayOfTheWeek <- dateTime8.dayOfWeek()
hourOfTheDay <- dateTime8.hour()
minuteOfTheHour <- dateTime8.minute()
secondOfTheMinute <- dateTime8.second()
timeZone <- dateTime8.zone()
offset <- dateTime8.offSetFromUTC()
assert dayOfMonth == 6
assert monthOfYear == 3
assert year == 1
assert dayOfTheWeek == 2
assert hourOfTheDay == 2
assert minuteOfTheHour == 59
assert secondOfTheMinute == 18
assert timeZone == "Z"
assert offset == PT0S
//EOF
The date time example is quite long, but as you can see the ideas are the same; in the sense of using normal mathematical operations as with just Date and Time with Duration. The idea is to try and keep the same semantics irrespective of type, this reduces the surface area of method names and ideas. It also reduces the number of classes involved.
So while the EK9 language does have a lot of types, constructs and uses a wide range of operators it does reduce the need for a wide range of method names, helper classes and third party API's.
This is a key point if you decide to adopt EK9 - 'go with the grain and flow'. Don't fight against it, it would be better to adopt a different language like C or C# for example.
Money
This is the first built-in type that models an important modern real world concept. You can make the argument that dates, times and timezones are abstract concepts, but Money really is a modern abstract business concept. Many programming languages do not have a built-in type for money, but as EK9 is aimed at a wide range of uses incorporating Money from the outset provides a more rounded language. There are some particularly tricky things to deal with when it comes to money, these are the number of fractional parts (cents, etc.) and the rounding methodology. It is this aspect that means Money is in general more difficult to work with than a DateTime as there is additional variation in Money.
Variables of type Money can have the values of:
- Unknown not set
- Any valid positive decimal value with a currency
- Any valid negative decimal value with a currency
The format of the Money literal is D+(.D+)?#SSS where D is any number, it is possible to have an optional point then the fractional part of the amount; this is then always followed by a '#' and then the three digit currency code.
So for example the Chilean Unidad de Fomento is a bit special as it has four fractional places i.e. 45.9999#CLF and the Iraqi Dinar has three places (45.000#IQD). When displaying these values it is normal to use a Locale. But when coding them use the format above. Currencies like GBP and USD only use two decimal places as do many other currencies, however some like Guinea Franc have none at all.
Please note that the mechanism of specifying and checking these values is code and it is not intended to be viewed in this way by end users. The presentation of Money is subject to the Locale you may for example decide that while a currency does have 2 decimal places because of your business application it is not appropriate to display them (for example transaction charges could be a fixed amount of £50 or $75 - so it is not necessary to display the pennies/cents).
Examples of the use of Money:
#!ek9
defines module introduction
defines program
ShowMoneyType()
tenPounds <- 10#GBP
assert tenPounds == 10.00#GBP
thirtyDollarsTwentyCents <- 30.2#USD
assert thirtyDollarsTwentyCents == 30.20#USD
//Mathematical operators
nintyNinePoundsFiftyOnePence <- tenPounds + 89.51#GBP
assert nintyNinePoundsFiftyOnePence == 99.51#GBP
assert tenPounds < nintyNinePoundsFiftyOnePence
assert nintyNinePoundsFiftyOnePence > tenPounds
assert tenPounds <> nintyNinePoundsFiftyOnePence
assert tenPounds <= 10.00#GBP
assert tenPounds >= 10.00#GBP
//As they are different currencies - operation like this are meaningless
//You will have to convert to the same currency to compare or combine them.
assert ~(tenPounds != thirtyDollarsTwentyCents)?
assert ~(tenPounds == thirtyDollarsTwentyCents)?
//The above assertions are checking if the equality test is a valid test, not the result of the equality test.
//rounding up for money values 49.755 is 49.76
fourtyEightPoundsSeventySixPence <- nintyNinePoundsFiftyOnePence/2
assert fourtyEightPoundsSeventySixPence == 49.76#GBP
minusFourHundredAndThirtyFivePoundsSixtyPence <- fourtyEightPoundsSeventySixPence * -8.754
assert minusFourHundredAndThirtyFivePoundsSixtyPence == -435.60#GBP
nineHundredAndThirtyFivePoundsSixtyPence <- 500#GBP - minusFourHundredAndThirtyFivePoundsSixtyPence
assert nineHundredAndThirtyFivePoundsSixtyPence == 935.60#GBP
variableAmount <- (nineHundredAndThirtyFivePoundsSixtyPence * 3) / 18
assert variableAmount == 155.93#GBP
multiplier <- nineHundredAndThirtyFivePoundsSixtyPence / variableAmount
assert multiplier == 6.0001
variableAmount += 4.07#GBP
assert variableAmount == 160.00#GBP
variableAmount *= 0.666
assert variableAmount == 106.56#GBP
variableAmount -= 0.56#GBP
assert variableAmount == 106.00#GBP
variableAmount /= 4
assert variableAmount == 26.50#GBP
variableAmount := -variableAmount
assert variableAmount == -26.50#GBP
variableAmount := abs variableAmount
assert variableAmount == 26.50#GBP
variableAmount := sqrt variableAmount
assert variableAmount == 5.15#GBP
variableAmount := variableAmount ^ 6
assert variableAmount == 18657.07#GBP
//Division by zero result is not set
variableAmount := variableAmount/0
assert ~variableAmount?
//Use of two difference currencies together - result is not set
variableAmount := tenPounds + thirtyDollarsTwentyCents
assert ~variableAmount?
//Example if pipeline to add amounts together
amounts as List of Money := [tenPounds, nintyNinePoundsFiftyOnePence, fourtyEightPoundsSeventySixPence]
total <- cat amounts | collect as Money
assert total == 159.27#GBP
//Provide the exchange rate and convert to USD
totalInUSD <- total.convert(1.32845, "USD")
assert totalInUSD == 211.58#USD
//EOF
Hopefully you can see from the above example, working with Money in EK9 is easy and natural. For example divide one amount of money by another, and you get a Float as a result; i.e. how many times does one amount go into another. But divide an amount of money by an Integer or Float and you get Money as a result. If you want to convert an amount of Money into another currency - then just provide the exchange rate you wish to use and the new currency code.
But importantly 'Money' is type safe, you cannot add USD values to GBP values for example; the result would be un set!
- //Pipeline could have been written like this
- total ← cat tenPounds, nintyNinePoundsFiftyOnePence, fourtyEightPoundsSeventySixPence | collect as Money
- //Rather than as shown in the example like this
- amounts as List of Money := [tenPounds, nintyNinePoundsFiftyOnePence, fourtyEightPoundsSeventySixPence]
- total ← cat amounts | collect as Money
Money like quite a few other types has an iterator() method on it, this allows it to iterate its own value (if it is set). This means that is can be used as a source in a pipeline. The cat pipeline command accepts multiple parameters (comma separated) and so can trigger iteration over all the parameters. String and Bits provide iteration over Characters and Booleans respectively and so cannot do self iteration, to accomplish that with those typesuse a List or for single values an Optional.
Locale
EK9 has a mechanism for enabling the developer to output a range of types in localised form. The general form of a locale is {language}_{country}, for example en_GB or de_DE. EK9 supports localisation of the following types out of the box. In some cases (Time, Date, DateTime and Money) there are short, medium, long and full formats available for:
- Integer
- Float
- Time
- Date
- DateTime
- Money
For additional localisation of 'Text' suitable for output to end users see Text Properties. The Locale together with the Text construct provide a very good basis to produce textual output suitable for users in a range of different languages.
An example of the formats below is given with a number of sample locales and formats so that difference in output for the same data can be clearly seen.
#!ek9
defines module introduction
defines program
ShowLocale()
enGB <- Locale("en_GB")
//Note you can use underscore or dash as the separator.
enUS <- Locale("en-US")
deutsch <- Locale("de_DE")
skSK <- Locale("sk", "SK")
i1 <- -92208
i2 <- 675807
presentation <- enGB.format(i1)
assert presentation == "-92,208"
presentation := enGB.format(i2)
assert presentation == "675,807"
presentation := deutsch.format(i1)
assert presentation == "-92.208"
presentation := deutsch.format(i2)
assert presentation == "675.807"
presentation := skSK.format(i1)
assert presentation == "-92 208"
presentation := skSK.format(i2)
assert presentation == "675 807"
f1 <- -4.9E-22
f2 <- -1.797693134862395E12
presentation := enGB.format(f1)
assert presentation == "-0.00000000000000000000049"
presentation := enGB.format(f2)
assert presentation == "-1,797,693,134,862.395"
//With control over number of decimal places displayed
presentation := enGB.format(f1, 22)
assert presentation == "-0.0000000000000000000005"
presentation := enGB.format(f2, 1)
assert presentation == "-1,797,693,134,862.4"
presentation := deutsch.format(f1)
assert presentation == "-0,00000000000000000000049"
presentation := deutsch.format(f2)
assert presentation == "-1.797.693.134.862,395"
presentation := skSK.format(f1)
assert presentation == "-0,00000000000000000000049"
presentation := skSK.format(f2)
assert presentation == "-1 797 693 134 862,395"
time1 <- 12:00:01
presentation := enGB.shortFormat(time1)
assert presentation == "12:00"
presentation := deutsch.mediumFormat(time1)
assert presentation == "12:00:01"
date1 <- 2020-10-03
presentation := enGB.shortFormat(date1)
assert presentation == "03/10/2020"
presentation := enUS.mediumFormat(date1)
assert presentation == "Oct 3, 2020"
presentation := skSK.longFormat(date1)
assert presentation == "3. októbra 2020"
presentation := deutsch.fullFormat(date1)
assert presentation == "Samstag, 3. Oktober 2020"
dateTime1 <- 2020-10-03T12:00:00Z
presentation := enUS.shortFormat(dateTime1)
assert presentation == "10/3/20, 12:00 PM"
presentation := enGB.mediumFormat(dateTime1)
assert presentation == "3 Oct 2020, 12:00:00"
presentation := deutsch.longFormat(dateTime1)
assert presentation == "3. Oktober 2020 um 12:00:00 Z"
presentation := skSK.fullFormat(dateTime1)
assert presentation == "sobota 3. októbra 2020, 12:00:00 Z"
thousandsOfChileanCurrency <- 6798.9288#CLF
tenPounds <- 10#GBP
thirtyDollarsEightyNineCents <- 30.89#USD
presentation := enGB.format(tenPounds)
assert presentation == "£10.00"
presentation := enGB.format(thirtyDollarsEightyNineCents)
assert presentation == "US$30.89"
presentation := enGB.format(thousandsOfChileanCurrency)
assert presentation == "CLF6,798.9288"
presentation := deutsch.format(tenPounds)
assert presentation == "10,00 £"
presentation := deutsch.format(thirtyDollarsEightyNineCents)
assert presentation == "30,89 $"
presentation := deutsch.format(thousandsOfChileanCurrency)
assert presentation == "6.798,9288 CLF"
//Without the currency symbol
presentation := deutsch.longFormat(tenPounds)
assert presentation == "10,00"
presentation := deutsch.longFormat(thirtyDollarsEightyNineCents)
assert presentation == "30,89"
presentation := deutsch.longFormat(thousandsOfChileanCurrency)
assert presentation == "6.798,9288"
presentation := skSK.format(tenPounds)
assert presentation == "10,00 GBP"
presentation := skSK.format(thirtyDollarsEightyNineCents)
assert presentation == "30,89 USD"
presentation := skSK.format(thousandsOfChileanCurrency)
assert presentation == "6 798,9288 CLF"
presentation := enGB.mediumFormat(tenPounds)
assert presentation == "£10"
presentation := enGB.mediumFormat(thirtyDollarsEightyNineCents)
assert presentation == "US$31"
presentation := enGB.format(thousandsOfChileanCurrency, true, false)
assert presentation == "CLF6,799"
presentation := enGB.shortFormat(tenPounds)
assert presentation == "10"
presentation := enGB.shortFormat(thirtyDollarsEightyNineCents)
assert presentation == "31"
presentation := enGB.format(thousandsOfChileanCurrency, false, false)
assert presentation == "6,799"
//EOF
The example above; albeit quite long should give you a good idea of the range and simplicity of how to localise the output of a range of types. It also shows how the format can be altered for specific types being short, medium, long of full.
Colour
The Colour type has a number of features that add quite a bit of value for any developer needing to manipulate colour. These are built right into the type itself as shown in the following example. It is possible to work with the Bits of the Colour alter them and create new colours. But also add/subtract and change transparency/saturation and lightness.
#!ek9
defines module introduction
defines program
ShowSimpleColour()
//Colour can hold RGB or ARGB (i.e with alpha channel)
//Here shown with alpha channel fully opaque
testColour <- #FF186276
testColourAsBits <- testColour.bits()
assert testColourAsBits == 0b11111111000110000110001001110110
//Bitwise manipulation and colour recreation from bits
modifiedBits <- testColourAsBits and 0b10110111000100000110001010110110
modifiedColour <- Colour(modifiedBits)
assert modifiedColour == #B7106236
assert modifiedBits == 0b10110111000100000110001000110110
//Note it is also possible to access the HSL values of the colour
assert modifiedColour.hue() == 148
assert modifiedColour.saturation() == 71.9298245614035
assert modifiedColour.lightness() == 22.35294117647059
//Alter the alpha channel to control how transparent/opaque
moreOpaqueColour <- modifiedColour.withOpaque(80)
assert moreOpaqueColour == #CC106236
//It can be made lighter - much lighter in this case
lighterColour <- moreOpaqueColour.withLightness(80)
assert lighterColour == #CCA7F1CA
//Then we can saturate it more
moreSaturatedColour <- lighterColour.withSaturation(90)
assert moreSaturatedColour == #CC9EFAC9
//Remove the Red from this colour
lessRedColour <- moreSaturatedColour - #3A0000
assert lessRedColour == #CC04FAC9
//Add in some blue
moreBlueColour <- lessRedColour + #00001D
assert moreBlueColour == #CC04FADD
//Available in different formats as Strings
assert moreBlueColour.RGB() == $#04FADD
assert moreBlueColour.RGBA() == $#04FADDCC
assert moreBlueColour.ARGB() == $#CC04FADD
//EOF
- //The actual colours from example above, shown with vertical bar background
- //To show opaque and transparency
- #186276FF - testColour
-
- #106236B7 - modifiedColour
-
- #106236CC - moreOpaqueColour
-
- #A7F1CACC - lighterColour
-
- #9EFAC9CC - moreSaturatedColour
-
- #04FAC9CC - lessRedColour
-
- #04FADDCC - moreBlueColour
-
As you can see from the example above EK9 makes working with colours easy and in general developers always need to be able to alter shades and hues of colours programmatically; including lightening and darkening. This is straight forward with the Colour type, get the current Lightness and just multiply it up or down by a factor and make the alteration to get the new Colour.
It's quite easy to imagine a pipeline process taking a stream of Colours and using a range of functions to manipulate those Colours to produce a new set that are darker, lighter or more muted.
Dimension
The Dimension type is a simple aggregate type that enforces the concept of some sort of size in combination with a unit of measurement. The main focus of this type is to make explicit the use and combinations of mathematical calculations. This could have been done by end user developers with a class hierarchy, but EK9 provides a simpler mechanism in the language itself.
Inspired by the CSS idea of dimensions this type can employ any of the following units for dimensions.
Absolute Lengths (m, km and mile added - not suitable in CSS context)
- km - Kilometers
- m - Meters
- cm - Centimeters
- mm - Millimeters
- mile - Miles
- in - Inches
- pc - Picas (1pc = 12 pt)
- pt - Points (1pt = 1/72 of 1 inch)
- px - Pixels (1px = 1/96th of 1 inch)
Relative Lengths (really only applicable for screen rendering)
- em - Relative to the font-size (2em means 2 times the size of the current font)
- ex - Relative to the x-height of the current font
- ch - Relative to the width of the "0" (zero)
- rem - Relative to font-size of the root element
- vw - Relative to 1% of the width of the viewport
- vh - Relative to 1% of the height of the viewport
- vmin - Relative to 1% of viewport's smaller dimension
- vmax - Relative to 1% of viewport's larger dimension
- % - Relative to the parent element
Example of using the Dimension type.
#!ek9
defines module introduction
defines program
ShowDimensionType()
dimension1 <- 1cm
dimension2 <- 10px
dimension3 <- 4.5em
dimension4 <- 1.5em
calc1 <- dimension1 * 2
calc2 <- dimension2 / 5
calc3 <- dimension3 + 0.6
calc4 <- dimension3 + dimension4
assert calc1 == 2cm
assert calc2 == 2px
assert calc3 == 5.1em
assert calc4 == 6em
//But if we divide a dimension by another dimension we get just a number
calc5 <- dimension3 / dimension4
assert calc5 == 3
//Normal comparison operators as you would expect
assert calc3 < calc4
//But also the coalescing operators
lesser <- calc3 <? calc4
assert lesser == calc3
calc3 += 0.9
assert calc3 == calc4
calc3++
assert calc3 > calc4
//Checking the calculation is not actually valid
//different types of dimension calc1 is cm and calc2 is px
assert not (calc1 <> calc2)?
//This is a test that the comparison was valid, not the result of the comparison
assert (calc3 < calc4)?
calc6 <- sqrt calc4
assert calc6 == 2.449489742783178em
numberOfKmInMiles <- 1.609344km
eightMiles <- 8mile
inKM <- eightMiles.convert(numberOfKmInMiles)
assert inKM == 12.874752km
assert length inKM == 12.874752
returnJourney <- -eightMiles
assert abs returnJourney == eightMiles
squared <- inKM ^ 2
assert squared == 165.75923906150402km
result <- eightMiles + inKM
assert not result?
//EOF
The Dimension type is designed to help with strong typing and to ensure that only compatible types are used together or are converted through an explicit method call. See Mars Climate Orbiter as to why this is 'quite important'.
Path
Like JSON (next) the concept of an object/array path is built into the EK9 language. This is because that concept of an object graph with a mix of Maps and Arrays is a very common one.
Here is an example of how to define a path to be used with an object graph. The literal definition of a path starts with $?, it is then followed by combinations of property or array addressing.
- Properties - .property-name
- Arrays - [index]
#!ek9
defines module com.customer.just
defines program
ASimpleSetOfPaths()
path1 <- $?.some.path.inc[0].array
path2 <- $?.another[2][1].multi-dimensional.array.access_value
simplePath <- $?.aKey
firstArrayElement <- $?[0]
propertyFromFirstElement <- $?[0].a-field
//Not to be confused with string conversion
anInt <- 90
stringRepresentation <- $anInt
//Or json conversion of a record.
me <- CustomerDetail(firstName: "Steve", lastName: "Limb")
jsonOfMe <- $$me
defines record
CustomerDetail
firstName <- String()
lastName <- String()
//EOF
JSON
The JSON type is built right into the EK9 language. Is has been designed to support all the normal types that JSON supports, those being:
- String - EK9 String
- Number - EK9 Integer/Float
- Boolean - EK9 Boolean
- null - EK9 unset value
- object - EK9 aggregates/Dict
- array - EK9 List
Other EK9 types; such as Character, Date, Time, DateTime, Duration, Millisecond, Money, Colour, Dimension and Bits are all converted to the JSON String type.
Importantly developers can convert their own data types of records and classes to JSON using the $$ JSON operator. But EK9 can provide a default $$ operator for records without the developer needing to write any code in most cases.
It is also possible convert your records and classes back from JSON by providing:
- A constructor that takes a single JSON argument
- OR, by providing a default constructor and a pipe '|' operator that takes a single JSON argument
Regular Expression
The RegEx type is the last of the built-in non-collection types. It is built into the language for the simple reason that regular expressions are so widely used and are so expressive (albeit quite complex).
Standard shorthand sequences
- . - Any character (may or may not match line terminators)
- \d - A digit: [0-9]
- \D - A non-digit: [^0-9]
- \s - A whitespace character: [ \t\n\x0B\f\r]
- \S - A non-whitespace character: [^\s]
- \w - A word character: [a-zA-Z_0-9]
- \W - A non-word character: [^\w]
Quantifiers
-
X, once or not at all
- X? - Greedy
- X?? - Reluctant
- X?+ - Possessive
-
X, zero or more times
- X* - Greedy
- X*? - Reluctant
- X*+ - Possessive
-
X, one or more times
- X+ - Greedy
- X+? - Reluctant
- X++ - Possessive
-
X, exactly n times
- X{n} - Greedy
- X{n}? - Reluctant
- X{n}+ - Possessive
-
X, at least n times
- X{n,} - Greedy
- X{n,}? - Reluctant
- X{n,}+ - Possessive
-
X, at least n times but not more than m times
- X{n,m} - Greedy
- X{n,m}? - Reluctant
- X{n,m}+ - Possessive
Boundary Matchers
- ^ - The beginning of a line
- $ - The end of a line
- \b - A word boundary
- \B - A non-word boundary
- \A - The beginning of the input
- \G - The end of the previous match
- \Z - The end of the input
- \z - The end of the file
Here is an example of the use of RegEx with match, split and group. The operations can be RegEx or String focussed.
#!ek9
defines module introduction
defines program
ShowRegExType()
sixEx <- /[a-zA-Z0-9]{6}/
assert sixEx matches "arun32"
assert sixEx not matches "kkvarun32"
assert sixEx matches "JA2Uk2"
assert sixEx not matches "arun$2"
regEx <- /[S|s]te(?:ven?|phen)/
Steve <- "Steve"
steve <- "steve"
Stephen <- "Stephen"
stephen <- "stephen"
Steven <- "Steven"
steven <- "steven"
Stephene <- "Stephene"
stephene <- "stephene"
assert Steve matches regEx and steve matches regEx
assert Stephen matches regEx and stephen matches regEx
assert Steven matches regEx and steven matches regEx
assert Stephene not matches regEx
assert stephene not matches regEx
//String and Regex can work either way around
assert regEx matches Stephen
//Examples using escape of '\'
stockExample <- "This order was placed for QT3000! OK?"
groupEx <- /(.*?)(\d+)(.*)/
assert groupEx matches stockExample
fractionExample <- "3/4"
fractionEx <- /.*\/.*/
assert fractionEx matches fractionExample
slashEx1 <- /^Some\\Thing$/
assert slashEx1 matches "Some\Thing"
slashEx2 <- /^Some\/Thing$/
assert slashEx2 matches "Some/Thing"
colonDelimited <- "one:two:three:four:five"
colonRegEx <- /:/
//You can work with RegEx first or String first
whenSplit <- colonDelimited.split(colonRegEx)
alsoSplit <- colonRegEx.split(colonDelimited)
assert $whenSplit == "one,two,three,four,five"
assert $alsoSplit == "one,two,three,four,five"
assert whenSplit == alsoSplit
//Check on finding groups
extractionCheck <- "This is a sample Text 1234 with numbers in between."
extractNumbersEx <- /(.*?)(\d+)(.*)/
matchedGroups <- extractionCheck.group(extractNumbersEx)
//Note the comma separated items
assert $matchedGroups == "This is a sample Text ,1234, with numbers in between."
justTheNumber <- cat matchedGroups | skip 1 | head 1 | collect as String
//Convert to an Integer - if possible
asAnInteger <- Integer(justTheNumber)
assert $justTheNumber == "1234"
assert asAnInteger == 1234
//Check just the last two (as an example)
lastTwo <- cat matchedGroups | tail 2 | collect as List of String
assert length lastTwo == 2
nonExtractionCheck <- "Another sample but with no numbers in it."
matchedGroups := nonExtractionCheck.group(extractNumbersEx)
//Expecting this to be unset
assert not matchedGroups?
//Safe way of accessing some result that might not be set (in this case it is not set)
justNotTheNumber <- cat matchedGroups | skip 1 | head 1 | collect as String
assert not justNotTheNumber?
//EOF
Another reasonably long example (only touches on how powerful regular expression are though), but hopefully you can see how the direct use of regular expressions in the code reduces the number of escape sequences required.
Summary
EK9 provides a wide range of types built right into the language. By providing types like Time, Date, DateTime, Money, Dimension and Colour EK9 enables strong typing and provides consistent and simple semantics.
Next Steps
Many of the examples above have used some form of collection type. These are discussed in more detail in the collection types section itself.