Monday, February 28, 2011

Rosetta Code: Arrays

Today, let's discuss the work with array (In this post I combined several tasks).
First, lets' show the basic array syntax
There are several ways how to declare the array in ELENA. The simplest way looks like this
#var anArray := (1, 2, 3).
Note that the array is an object containing the references to the integer objects rather than values itself. By default the array object inherits std'basic'Array class.
The syntax to retrieve the element is quite simple:
#var anItem := anArray@1.
Note that @ message returns the indexer proxy rather than the element itself. If you wish to retrieve the object itself the code should be a bit more complex:
#var anItem := (anArray@1) content.
To find out the difference between these variants let's examine the concept of an indexer. The indexer is a special adapter allowing to access the array members. To create indexer it is enough to send indexer message
#var anIndexer := anArray indexer.
The indexer can retrieve or assign the current array element with content'get / content'set methods (note that set method works only for dynamic arrays) and navigate the array (it is similar to the C array pointer). So to retrieve the element we have to create an array, move to the required position and return the content:
anArray indexer write &index:1.
But it is possible to use more simple way with the help of "refer" message (@ operand).
anArray @ 1.
Let's consider the following sample:
ctrl'It::anArray run : anItem =>
[
   'program'Output << anItem << "%n".
]
In this code we print every member of the array. Note that the object is extended with its index.
ctrl'It::anArray run : anItem =>
[
'program'Output << "a[" << anItem index << "]=" << anItem << "%n".
]
We could use an enumerator as well. In this case the object rather then the indexer is used:
ctrl'Scan::anArray run : anItem =>
[
   program'Output << anItem << "%n".
]
If we would like to get the original object from the proxy (for example if it should be assigned to the class field, in this case it is not optimal to store the proxy) we should use content'get message:
anArray@1 content.
Now let's see how could we create a dynamic array:
#var anArray := basic'NewArray::3.
In most cases there is no difference between the constant and the dynamic one except assigning the value:
anArray@0 set &content:2.
I hope it is clear now why we have to do it this way. After a dynamic array is created all its members are nil. Though it is possible to create and fill the array at the same time
anArray := factory'NewArray::{ &factory'array_size:3 &factory'pattern:(Integer::0) }.
And finally let's consider the code to calculate the mean(arithmetic average) of a numeric vector.
#define std'basic'*.
#define std'patterns'*.
#define std'dictionary'*.

// --- Sum ---

#class MeanAction
{
    #field theValue.
    #field theCount.
    
    #role Empty
    {
        #method numeric'get = 0.
        
        #method evaluate : aValue
        [
            theValue := Real::0.
            theCount := Integer::0.
            
            #shift.
            
            self evaluate:aValue.
        ]
    }
    
    #method new
    [
        #shift Empty.
    ]
    
    #method numeric'get = theValue / theCount.
    
    #method evaluate : aValue
    [
        theCount += 1.
        
        theValue += aValue.
    ]
    
    #method start : aPattern
    [
        aPattern run:self.
        
        ^ self numeric.
    ]
}

// --- Program ---

#symbol Program =>
[
    'program'Output << MeanAction start:Scan::(1, 2, 3, 4, 5, 6, 7, 8).
].
In short we execute the special action object for every member of the collection (or an array) and then print the result. The program code is quite simple (once again we use an enumeration, but this type with implicit action object instead of anonymous one), so let's look more precisely at MeanAction. Enumeration code pattern executes the action for every member of the collection by sending "evaluate" message (=> operator) with the collection member. So we have to declare this method and calculate the total sum simultaneously counting the numbers. "numeric'get" method returns the array mean. In the object constructor we initialize the action fields. Till this moment all is quite straightforward. But there is a special case - zero vector (an array with zero members). If we try to return the mean for it the program will crash with divide by zero exception. So we are creating a special set of methods (Empty role) to deal with it and switch the object to it (#shift Role). If evaluate method is called at least once than the vector is not zero and we have to use the standard set of methods (statement #shift).

Friday, February 25, 2011

Rosseta code tutorials:Amb operator

Practically from the very start I had a problem with finding interesting samples to be implemented on ELENA. And only recently I found a place where I could find them

So I'm continuing implementing these tasks. My next sample is Amb operator.

Let's consider several tasks - one (with literals) from rosetta code (i.e. it is a failure if the last character of word 1 is not equal to the first character of word 2, and similarly with word 2 and word 3, as well as word 3 and word 4) and another (with numeric) from here. We will call them Program1 and Program2.

#symbol Program1 =>
[
#var A := AmbValue::(1, 2, 3).
#var B := AmbValue::(4, 5, 6).

AmbOperator::(A, B) seek: => (A * B== 8).

'program'Output << A << "*" << B << "=8".
].

#symbol Join =
{
if &first:aFirst &second:aSecond
[
^ aFirst@(aFirst length - 1) == aSecond@0.
]
}.

#symbol Joinable : aPair = Join if:ctrl'Args::aPair.

#symbol Program2 =>
[
#var A := AmbValue::("the","that","a").
#var B := AmbValue::("frog", "elephant", "thing").
#var C := AmbValue::("walked", "treaded", "grows").
#var D := AmbValue::("slowly", "quickly").

AmbOperator::(A, B, C, D) seek:
=> (Joinable::(A,B) and:Joinable::(B,C) and:Joinable::(C,D)).

'program'Output << A << " " << B << " " << C << " " << D.
].


How does Amb operator work? At first we have to define set of possible values for amb operator to choose from (AmbValue). Then we ask AmbOperator to find a correct subset. Joinable symbol is used for a tricky condition of the rosetta code sample (a@(a length)==b@0). ctrl'Args symbol is used to simplify the symbol argument list (i.e. Join if:ctrl'Args::(a, b) is similar to Join if:{ first'get = A. second'get = B. } or Join if:(&first:A &second:B)).

So let's consider the code to implement Amb operator.
We start with a multi-list enumerator. I would like to remind that an enumerator is used in ELENA to execute an action for every member of a collection (similar to foreach statement in C#). Why we need an enumerator? To find a correct solution AmbOperator has to go over all possible combinations of amb operands. So let's implement it (it is declared in ext'patterns module):
#class MultiEnumerator
{
#field theEnumerators.

#role BOF
{
#method proceed
[
#shift.

ctrl'Control run &list:theEnumerators &foreach:anEnumerator => (anEnumerator proceed).

^ basic'True.
]
}

#method enumerator = $self.

#method proceed
[
#var aRetVal := ctrl'Control run &list:theEnumerators &foreach:anEnumerator =>
[
#if(anEnumerator proceed)?
[ ^ basic'False. ].

anEnumerator clear proceed.
].

^ (basic'nilValue != aRetVal).
]

#method new : aCollection
[
#shift BOF.

theEnumerators := basic'ArrayType evaluate &__array &count:(aCollection count) &filling: aCurrent => (aCollection@(aCurrent indexer) enumerator).
]

#method get = nil.
}

There is nothing special there. In the construct we create a list of enumerators for every member of the parameter. There is a special use case at the beginning (role BOF) where all enumerators are initialized (first call of proceed method). With each next call of proceed method the first enumeration goes on until it reaches the end, then it is repeated for the next member of the second enumeration and so on until the last enumeration is finished.

Now we need AmbValue enumerator:
#class AmbEnumerator
{
#field theValues.

#role BOF
{
#method proceed
[
theValues $reset.

#shift.

^ basic'True.
]
}

#method new : Values
[
theValues := Values.

#shift BOF.
]

#method proceed
[
^ theValues $next.
]

#method clear
[
#shift BOF.
]

#method get = theValues.
}

AmbEnumerator is actually an adapter which dynamically modify (mutate) AmbValue by calling private methods - $reset (AmbValue is wrapped around the first member of its collection) and $next (AmbValue is wrapped around the next one).
#class AmbValue
{
#field theValues.
#field theCurrent.

#method enumerator = AmbEnumerator::self.

#method $reset
[
theCurrent := theValues@0.
]

#method $next
[
#var anIndexer := theCurrent indexer.

anIndexer += 1.

^ anIndexer eof'is back:basic'False | back:basic'True.
]

#method new : Values
[
theValues := Values.
theCurrent := nil.
]

#union (theCurrent primary).
}

As I said before AmbValue is a proxy class (dynamic extension) over the collection of possible values. Only one value at the time can be accessible (statement #union). "Primary" message is used to return the actual object (note that theCurrent is an indexer).

And finally our AmbOperator
#symbol AmbOperator : Values =
{
seek : anExpression
[
ctrl'Control run &enumerator:exctrl'MultiEnumerator::Values &foreach: aCurrent =>
[
^ anExpression evaluate inverted.
].
]
}.

So how does it work? The key is AmbValue. With a help of ELENA magic AmbValue could be wrapped around one of its members.
    #var A := AmbValue::(1, 2, 3).
#var B := AmbValue::(4, 5, 6).

At the beginning A and B points to nil objects. But if we will call $reset method they will be equal to 1 and 4 integer constants (actually are wrapped around them, bit for other parts of the program they ARE integer numbers). After we call $next method they will be 2 and 4. Others are quite simple. AmbOperator with the help of MultiEnumerator executes the required expression for every possible combination of A and B values (i.e. 1 and 4, 1 and 5, 1 and 6, 2 and 4 and so on) until the expression is true (A * B = 8). Et voila!

P.S. The code works for ELENA API starting from 1.6.0.1 (I'm going to release it very soon). I added exctrl'MultiEnumerator and fixed several bugs in indexer implementation (without them the code for Join symbol should be like this: (aFirst literal)@(aFirst literal length - 1) == (aSecond literal)@0.).