Dynamic Classes in EK9
In a similar manner to the dynamic functions; the dynamic class can serve a similar purpose. But unlike dynamic functions; dynamic classes can be 'named' and can be 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; the example below shows it is very useful. It is important to note that the name of the dynamic class is 'pulled up' to module scope level. So unlike a 'Java inner class' it becomes visible in the module.
Dynamic Classes cannot extend an existing class but can use traits. While this might feel like a shortcoming (it has been limited deliberately). This is to promote composition rather than inheritance. Extract a Trait signature from your 'base class' that your class would have extended. Now use traits and delegation to utilise that functionality, also provide/override 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 be best to use an alternative to EK9.
Examples
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 an object reference ('terminationHandler' and 'debugHandler').The dynamic class definitions themselves are not reusable; they could have been; if they were 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 linked to above, 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 are; 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. If you are not from a dynamic language background; you may find this syntax and arrangement quite strange at first. It does provide for an alternative approach to class/tuple definition. This gives the code a more 'interpreted/dynamic' feel.
#!ek9 defines module introduction 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 //Just access a function that returns 'ATuple' - even though 'ATuple' is not defined yet! tuple1 <- getATuple() //Lets have an inferred List of 'ATuple' and put the first entry in. tuples <- [tuple1] //This is both a declaration of a new type 'ATuple' and the creation of an object of type 'ATuple' //AND also indented is the implementation of 'ATuple'. //Plus we can put this in a loop if we need to 'ATuple' is only defined once! for i in 1 ... 10 //Make the data up from base information and dynamic content. firstName <- name + "-" + $i dob <- dateOfBirth + Duration(`P${i}M`) //define, populate and get an object of that type tuple <- ATuple(name: firstName, dob: dob, language: "English") as class //We can also create new properties in the class directly if necessary //Rather than just capture. message as String: "Dynamic: " //You can even define a range of different constructors if you wish. //These are not automatically created, developer needs to define the 'interface' //That way if the names of variables captured are refactored/renamed, the rest of the code //using ATuple can stay the same. ATuple() -> name as String dob as Date language as String this.name = name this.dob = dob this.language = language //Add in methods, hiding internal structure of object or meeting a trait type interface. name() <- rtn as String: firstName operator $ as pure <- rtn as String: `${message} ${firstName} ${dob} ${language}` operator #^ as pure <- rtn as String: $this //OK now just add that new tuple into the List of ATuple tuples += tuple //Display all those tuples. cat tuples > stdout //just to demonstrate calling a function that has 'ATuple' as a parameter. 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 statically typed and checked at compile time. The creation of classes with EK9 is quite fluid and flexible.
If you want to use fixed values when capturing and defining the 'tuple', then the names of the parameters being captured but be given. Those names are then the same names that are used as properties in the dynamic class.
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) trait 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) trait 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) validator1 <- (lenValidator, checkContent) trait of LenValidator by lenValidator, CharValidator by checkContent assert validator1.validateCharacters(val1) and validator1.validateLength(val1) assert validator1.validateCharacters(val2) and not validator1.validateLength(val2) assert validator1.validateCharacters(val3) and not validator1.validateLength(val3) assert not validator1.validateCharacters(val4) and validator1.validateLength(val4) validator2 <- (validator1, checkContent) trait of Validator, LenValidator by validator1, CharValidator by checkContent assert validator2.validate(val1) assert not validator2.validate(val2) assert not validator2.validate(val3) assert not validator2.validate(val4) val5 <- "steve" validator3 <- (lenValidator) trait 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) trait 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.
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 Composition
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.
Summary
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/flexible in EK9. It reduces the need to use Inheritance as the only mechanism of behavioural change. It means smaller more well defined functions, traits and classes can be defined (with a single responsibility) 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 both inheritance/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.
Next Steps
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.