In a similar manner to the dynamic functions, the dynamic class can serve a similar purpose in some ways. But unlike dynamic functions, dynamic classes can be 'named' and are elevated to be fully usable as a class as if it were declared in the class block (when named).
If you are familiar with Java you may initially think these are just like anonymous classes; they can be, but they can also be used in a different way (when named).
Initially it may seem a little strange that it is possible to dynamically define a class deep in a function or method body; and that class can then be used as if it were defined at module level (when named); but as the example below shows it is very useful.
Dynamic Classes cannot extend an existing class but can use traits. While this might feel like a shortcoming (it has been limited deliberately) - remember you can and should use composition where possible! Extract a Trait signature from your 'base class' that your class would have extended. Now use traits and delegation to utilise that functionality, also providing/overriding the methods in your dynamic class. Remember try and go with the flow of this to make the most of the language, if you find it 'too painful' it would probably best to use an alternative to EK9.
The function setupSignalHandling and specifically 'terminationHandler' and 'debugHandler' both define dynamic classes, they capture variables and override methods. But take note that the dynamic classes they create are not named. They are held via object reference ('terminationHandler' and 'debugHandler'), so the dynamic class definitions themselves are not reusable; they could have been if named. The TCP Server example shows two dynamic classes with a trait TCPHandler. Both of these classes override the method connected to be able to process the incoming traffic. Again these dynamic classes are un-named. The example later shows a named and reusable class.
The Mutex example also shows how a dynamic class can be employed. It shows the full syntax when creating the dynamic class.
Tuple like example
All of the examples so far have demonstrated a fairly simple use of dynamic classes, this next inline example demonstrates the EK9 version of a Tuple. In fact EK9 just uses a dynamic class for this but without any traits. The data captured into the Tuple becomes private and only exposed via public methods defined on the class
The interesting points to note in the example, is the use of 'ATuple' in functions defined both before and after 'ATuple' is declared, and that 'ATuple' is declared inside a for loop. The other main point is that function 'getATuple' can just declare it returned the 'ATuple' type and then can also create a new instance of 'ATuple' with suitable data (Constructor must still be declared in 'ATuple').
This provides a way to define classes in a very dynamic, but type safe manner. You may find this syntax and arrangement quite strange at first if you are not from a dynamic language background. It does provide for an alternative approach to class/tuple definition. In general it gives the code a more 'interpreted/dynamic' feel.
#!ek9 defines module introduction defines type List of ATuple defines function printer2() -> data as ATuple Stdout().println("[" + data + "] but I can access [" + data.name() +"] directly if I wish") //Note how we can just use 'ATuple' in a normal way by creating an instance of it. getATuple() <- rtn as ATuple: ATuple("John", 1994-01-01, "English") defines program ShowTupleClass() stdout <- Stdout() name <- "Steve" dateOfBirth <- 1970-01-01 language <- "English" //Just access a function that returns 'ATuple' - even though 'ATuple' is not defined yet! tuple1 <- getATuple() tuples <- [tuple1] for i in 1 ... 10 firstName <- name + "-" + $i dob <- dateOfBirth + Duration("P"+$i+"M") //Here is where the tuple is actually defined. tuple <- ATuple(firstName, dob, language) as class message as String: "Dynamic: " ATuple() -> name as String dob as Date language as String this.firstName = name this.dob = dob this.language = language name() <- rtn as String: firstName operator $ <- rtn as String: message + firstName + " " + $dob + " " + language operator #^ <- rtn as String: $this tuples += tuple cat tuples > stdout printer1(tuple1) printer2(tuple1) printer2(getATuple()) defines function printer1() -> data as ATuple stdout <- Stdout() stdout.println("[" + data + "] but I can access [" + data.name() +"] directly if I wish") //EOF
The example above demonstrates that it is possible to define all your classes dynamically like this if you wanted to. Coming from a C++, Java or C# background this will probably seem strange, normally classes are a big deal and even need to be in their own files.
The nice thing about the approach above, is that data captured is hidden and users of the dynamic class can only use the public methods defined. The methods are all type checked and resolved as you would expect. So refactoring dynamic classes altering method names is safe.
This gives the impression of a more dynamic typed language, but is in fact strongly typed and checked at compile time. The creation of classes with EK9 is quite fluid and flexible.
There is one major restriction when creating dynamic classes and that is the variables that are to be captured but must be variables. They cannot be literals or function/method calls. This is because when the dynamic class is created with the captured data, each data has to be given a field name. The idea of using '_1' or something like that is possible but not acceptable. Meaningful names provide much more value in terms of readability. But at the cost of having to declare variables with names to capture.
Composition Dynamic Class Example
The section on composition shows how classes and traits can be used in composition. This power is magnified when used with dynamic classes as the following example demonstrates.
In fact this example shows how dynamic functions and dynamic classes can be used together.
#!ek9 defines module introduction defines trait LenValidator validateLength() abstract -> p as String <- r as Boolean CharValidator validateCharacters() abstract -> p as String <- r as Boolean Validator with trait of LenValidator, CharValidator validate() -> p as String <- r as Boolean: validateLength(p) and validateCharacters(p) defines class NoDollarValidator trait of CharValidator override validateCharacters() -> p as String <- r as Boolean: p? and not p.contains("$") defines function discriminator as abstract -> s as String <- rtn as Boolean defines program Example min <- 9 max <- 40 prohibited <- "@" gt <- (min) is discriminator as function rtn: length s > min lt <- (max) is discriminator rtn: length s < max val1 <- "Some Value" assert gt(val1) and lt(val1) val2 <- "Short" assert not gt(val2) and lt(val2) val3 <- "1234567890123456789012345678901234567890Long" assert gt(val3) and not lt(val3) lenValidator <- CheckLength(gt, max) of LenValidator as class override validateLength() -> p as String <- r as Boolean: false if p? r := gt(p) and length p < max //So I can just use gt and lt or mix and match assert lenValidator.validateLength(val1) assert not lenValidator.validateLength(val2) assert not lenValidator.validateLength(val3) checkContent <- CheckCharacter(prohibited) of CharValidator override validateCharacters() -> p as String <- r as Boolean: true if p? r: ~p.contains(this.prohibited) val4 <- "1234567890@not allowed" assert checkContent.validateCharacters(val1) assert checkContent.validateCharacters(val2) assert checkContent.validateCharacters(val3) assert not checkContent.validateCharacters(val4) validate1 <- (lenValidator, checkContent) of LenValidator by lenValidator, CharValidator by checkContent assert validate1.validateCharacters(val1) and validate1.validateLength(val1) assert validate1.validateCharacters(val2) and not validate1.validateLength(val2) assert validate1.validateCharacters(val3) and not validate1.validateLength(val3) assert not validate1.validateCharacters(val4) and validate1.validateLength(val4) validate2 <- (validate1, checkContent) of Validator, LenValidator by validate1, CharValidator by checkContent assert validate2.validate(val1) assert not validate2.validate(val2) assert not validate2.validate(val3) assert not validate2.validate(val4) val5 <- "steve" validator3 <- (lenValidator) of Validator, LenValidator by lenValidator, CharValidator by charValidator charValidator as CharValidator: NoDollarValidator() override validate() -> p as String <- r as Boolean: true if p not matches /[S|s]te(?:ven?|phen)/ r: validateLength(p) and validateCharacters(p) assert validator3.validate(val1) assert not validator3.validate(val2) assert not validator3.validate(val3) assert validator3.validate(val4) assert validator3.validate(val5) val6 <- "steve#host" validator4 <- (lenValidator) of Validator, LenValidator by lenValidator, CharValidator notAllowed String: "#" override validateCharacters() -> p as String <- r as Boolean: false if p? r:= p not contains notAllowed assert validator4.validate(val1) assert not validator4.validate(val2) assert not validator4.validate(val3) assert validator4.validate(val4) assert not validator4.validate(val5) assert not validator4.validate(val6) //EOF
There is quite a bit going on in the above example, the first part of the 'Example' program defines two dynamic functions 'gt' and 'lt', these are both functions that extend abstract function 'discriminator'. These are tested and then captured and used in dynamic classes.
The 'CheckLength' dynamic class has a trait of 'LenValidator' and overrides method validateLength. This method uses the captured function 'gt', but just does the 'max' variable as-is. This variable object is held as a class property called 'lenValidator'. This too is used later in composition.
A second dynamic class 'CheckCharacter' is used to check the content does not contain a specific prohibited character. This has the trait of 'CharValidator' and overrides method validateCharacters.
As both 'CheckLength' and 'CheckCharacter' are named dynamic classes they can be used in other contexts if required (which will include the captured variable they hold).
The objects 'validate1' and 'validate2' define new dynamic classes that have traits of 'LenValidator' and 'CharValidator'. The important point to note here that the implementation of methods validateLength and validateCharacters are not reimplemented but are automatically delegated. Note that they are delegated to the variable objects they capture.
Object 'validate3' does reimplement method validate, but in this scenario only 'lenValidator' is captured for delegation the 'charValidator' is created as a new property on the dynamic class and uses class 'NoDollarValidator'.
Finally object 'validate4' only delegates the length validation to a captured variable objects and then implements method validateCharacters using a property defined in the dynamic class itself.
The examples above clearly demonstrate that dynamic classes can be very short, flexible and can also be re-used either directly, through high order functions or through composition.
The idea of the 'Single Responsibility Principle' and composition is very powerful and flexible in EK9. It reduces the need to use Inheritance as the only mechanism of extensibility. It means smaller more well defined functions, traits and classes can be defined and then composed in a range of different ways.
By adopting polymorphism in functions as well as classes and traits; EK9
strongly promotes re-use and composition of small well rounded functions/classes. With the syntax
to support delegation and dynamic class definition; simple one liners like:
validate1 <- (lenValidator, checkContent) of LenValidator by lenValidator, CharValidator by checkContent
provide a really lightweight declarative mechanism of creating a new class composed of a mix of functions and other classes that implement traits.
The next section is again more functional and details Stream Pipelines this makes the most of collection and functions to accomplish a range of processing. If you want to stay with class type simple aggregates; take a look at Text Properties. These provide a very simple mechanisms to collect all the output text you might want to use in a program or toolkit in one logical place.