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

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.

Boolean

Variables of type Boolean can have the values of:

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:

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:

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.

It also adds a few additional methods.

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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)

Relative Lengths (really only applicable for screen rendering)

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.

#!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:

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:

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

Quantifiers

Boundary Matchers

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.