Dynamic programming in ELENA

Alex Rakov edited this page Aug 13, 2018 · 14 revisions

ELENA is an experimental dynamic programming language. In this article I would like to show some of its dynamic features.

Let's start with a multi-methods. We may declare a several methods with the same name but with different signatures. It is also possible to declare an explicit multi-method dispatcher.

class MyClass
{
    // accepts the integer
    test(IntNumber n)
    [
            console writeLine:"It is a number".
    ]

    // accepts the string
    test(LiteralValue n)
    [
        console writeLine:"It is a literal".
    ]

    // default dispatcher
    test:n
    [
        console writeLine:"Unsupported parameter".
    ]
}

public program
[
    object o := MyClass new.

    o test(2).
    o test("s").
    o test(3l).
]

The output is:

It is a number
It is a literal
Unsupported parameter

In some cases opposite can be done as well, we may declare generic handlers which will accept any incoming messages:

import extensions.
 
class Example
{
    generic
    [
        // received is an built-in variable containing the incoming message name
        console printLine(received," was invoked").
    ]
 
    generic(x)
    [
        console printLine(received,"(",x,") was invoked").
    ]
 
    generic(x,y)
    [
        console printLine(received,"(",x,",",y,") was invoked").
    ]
}
 
public program
[
    var o := Example new.
 
    o foo.
    o bar(1).
    o someMethod(1,2).
]

Output:

foo was invoked
bar(1) was invoked
someMethod(1,2) was invoked

We may declare a custom dispatcher which will redirect all unmapped incoming messages to another object effectively overriding it (some kind of dynamic mutation / code injection).

import extensions.

class Extender
{
    // our object to extend
    object theObject.
    
    // the injected property
    object prop foo :: theField.
    
    constructor new : anObject
    [
        theObject := anObject.
    ]
    
    // redirect Object.literal method to theObject one
    T<literal> literal => theObject.

    // custom dispatcher
    dispatch => theObject.
}

public program
[
    var anObject := 234.
  
    // adding a field
    anObject := Extender new:anObject.

    // setting a field value
    anObject foo := "bar".

    console printLine(anObject,".foo=",anObject foo).
]

The output is:

234.foo=bar

The message may be dynamically dispatched.

class MyClass
{
    eval
    [
       console writeLine:"eval method"
    ]                                                    

    state0
    [
       console writeLine:"state0 method"
    ]
}  

public program
[
   var o := MyClass new.

   var subj := %state0.  // a message name constant

   o eval.              

   o~subj eval           // dynamically dispatching the message
]

The output is:

eval method
state0 method

Though ELENA does not support multiple inheritance, using custom dispatcher we may simulate it:

class CameraFeature =
{
    cameraMsg
        = "camera".
}.
 
class MobilePhone
{
    mobileMsg
        = "phone".
}
 
class CameraPhone :: MobilePhone
{
    dispatch => CameraFeature.
}
 
public program
[
   var cp := CameraPhone new.
 
   console writeLine(cp cameraMsg).
   console writeLine(cp mobileMsg).
]

The output is:

camera
phone

Now let's create a mixin object:

import system'dynamic.
import extensions.
 
class Member1
{
    literal theField := "member1 content".

    field = theField.
}

class Member2
{
    eval
    [
        // NOTE : target is built-in variable referring to the mixin
        console printLine(
           "printing the content of the second object:",
           target field).
    ]
}

public program
[ 
    var g := Group::(Member1 new, Member2 new).
    g eval.
]

Output is:

printing the content of the second object:member1 content

Using ClosureTape we may dynamically build the code:

import extensions.
import system'dynamic.

public program
[
    var t := ClosureTape new(MessageClosure new(%"writeLine[1]")).

    t(console,"Hello again").
]

The output is:

Hello again

The code may be simplified if we will build an expression tree:

import system'dynamic'expressions.

public program
[
    var c := Expression MessageCall(
                            %"writeLine[1]", 
                            Expression Constant(console), 
                            Expression Constant("Hello")).
    
    var t1 := c compiled.
    
    t1().   
]

The output is:

Hello

Or we may use TapeAssembly class to create a class in run-time:

import system'dynamic.
import extensions'dynamic.
 
public program
[
    var a1 := ClosureTape::(
        openSingletonClosure,
        openMethodClosure,
        newMessageClosure new:"eval",
        newParamTokenClosure new:"n",
        newParamTokenClosure new:"self",
        openCodeClosure,        
        openExpressionClosure,
        newReferenceClosure new:"system'console",
        newMessageClosure new:"writeLine",
        newIdentifierClosure new:"n",
        closeClosure,        
        closeClosure,
        closeClosure,
        closeClosure).
  
    var o := TapeAssembly new(a1); eval.
    o eval("Hello again").
]

Output is :

Hello  again

We may use TapeExpression to simplify the code. Let's create a simple class with a single method which counts down the parameter and prints the values:

import extensions'dynamic'expressions.

public program
[
    // singleton Class
    var c := TapeExpression Singleton(
    // method eval(object m)
                TapeExpression Method(
                   "eval",
                   TapeExpression Code(
   // var m := n.
                      TapeExpression Declaring("m"),
                      TapeExpression Assigning(
                        "m",
                        TapeExpression Variable("n")
                      ),
   // while (m > 0)
                      TapeExpression Loop(
                          TapeExpression MessageCall(
                             TapeExpression Variable("m"), 
                             "greater",
                             TapeExpression Constant(0)),
                          TapeExpression Code(
   // console writeLine(m)
                              TapeExpression MessageCall(
                                 TapeExpression Constant(console), 
                                 "writeLine",
                                 TapeExpression Variable("m")),
   // m := m - 1
                              TapeExpression Assigning(
                                "m",
                                TapeExpression MessageCall(
                                    TapeExpression Variable("m"),
                                    "subtract",
                                    TapeExpression Constant(1)))))),
                      TapeExpression Parameter("n"))).

    var o := (c compiled)().   
    
    o eval(5).
]

The output is:

5
4
3
2
1

Let's implement the following code dynamically:

{ test : n = if(n == 2)[^ 2]. ^ 0 }

To implement the condition we will use TapeExpression.If(condition, truePart) method :

import extensions.
import extensions'dynamic'expressions.

public program
[
    var c := TapeExpression Singleton(
                TapeExpression Method(
                   "test",
                   TapeExpression Code(
                      TapeExpression If(
                        TapeExpression MessageCall(
                            TapeExpression Variable("n"),
                            "equal",
                            TapeExpression Constant(2)
                        ),
                        TapeExpression Code(
                            TapeExpression Returning(
                                TapeExpression Variable("n")
                            ))),
                      TapeExpression Returning(
                        TapeExpression Constant(0)
                      )),
                      TapeExpression Parameter("n"))).

    var t1 := c compiled.

    var o := t1().   

    console printLine("test(2)=",o test(2)).
    console printLine("test(3)=",o test(3)).
]

The output is :

test(2)=2
test(3)=0     

Now let's evaluate the code in run-time using ELENA Script Engine :

import extensions.
import extensions'scripting.

public program
[
    escript eval("system'console writeLine(""Hello World"").").
    
    var o := escript eval(
       "^ { eval(x) [ ^extensions'math'mathControl power(x, 2 ) ]}").

    console printLine(o eval(2)).
]

The output is :

ELENA VM 3.3.1 (C)2005-2017 by Alex Rakov
Initializing...
Debug mode...
Done...
Hello World
4

Now we will invoke our code from IDE.

Let's write a simple class :

import extensions.

public singleton MyClassToTest
{
    foo
    [
        console printLine("foo fired!").
    ]
}

public program
[
]

Now let's compile the code : Project - Compile and open Interactive window : View - ELENA Interactive and type in the window (presuming test is the project root namespace):

>test'MyClassToTest foo.

The output is :

ELENA command line VM terminal 3.3.2 (C)2011-2018 by Alexei Rakov
ELENA VM 3.3.1 (C)2005-2017 by Alex Rakov
Initializing...
Debug mode...
Done...

>test'MyClassToTest foo.
foo fired!
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.