This tutorial shows how to perform transitive member access in a simple program.
To run this tutorial, you may use the following project and source files:
These files are located in the \samples\Basics\Foreach subdirectory under the path where you installed Cω, which by default is C:\Program Files\Comega.
This tutorial is divided into the following sections:
Normal member access such as b.Text or hello.Body
selects only the direct members of a singleton or stream of object instances.
On the other hand transitive member access selects all recursively
reachable accessible members (where access also lifts over streams). For
example using transitive member access, we can rewrite the expression
hello.Body.P as hello...P, which selects all
accessible P members from hello,
no matter where in the object graph of hello they
appear.
Every once in a while we receive urgent messages that promise to make us rich quick and risk-free, and we definitively do not want to miss the opportunity to make a quick 9.5 million dollars:
<Email>
<Header>
<From>assdgsdgsdg@hotmail.com</From>
<To>undisclosed recipients</To>
<Subject>URGENT ASSISTANCE NEEDED</Subject>
</Header>
<Body>
...
<P>
WITH OUR POSITIONS, WE HAVE SUCCESSFULLY SECURED
FOR OURSELVES THE SUM OF THIRTY ONE MILLION,
FIVE HUNDRED THOUSAND UNITED STATES DOLLARS (US$31.5M).
THIS AMOUNT WAS CAREFULLY MANIPULATED
BY OVER-INVOICING OF AN OLD CONTRACT.
</P>
...
<P>
IT HAS BEEN AGREED THAT THE OWNER OF THE ACCOUNT
WILL BE COMPENSATED WITH 30% OF THE REMITTED FUNDS,
WHILE WE KEEP 60% AS THE INITIATORS AND 10% WILL BE
SET ASIDE TO OFFSET EXPENSES AND PAY THE NECESSARY TAXES.
</P>
...
<P>THIS TRANSACTION IS 100% RISK FREE.</P>
...
</Body>
</Email>;
The code below uses Cω filters, and type-based transitive member access to
define the MustRead virtual method on the type
Email that we can use to filter interesting messages from our
mailbox.
A message is interesting if it contains certain trigger words. The function
IsInteresting checks if a given string contains one of those
interesting words:
bool IsInteresting (string s){
return
( s.IndexOf("URGENT") >= 0
|| s.IndexOf("YOUR ASSISTANCE") >= 0
|| s.IndexOf("MILLION") >= 0
|| s.IndexOf("100% RISK FREE") >= 0
);
}
Given a message msg, the transitive member access
expression msg...string::* selects all recursively
accessible members of type string in msg.
In this case, the stream of strings returned by ms...string::*
is the stream consisting of the strings this.Header.From,
this.Header.To, this.Header.Subject, and this.Body.P.
Using the IsInteresting predicate we can filter out all
interesting words from a message:
public virtual string* MustRead () {
bool IsInteresting (string s){
return
( s.IndexOf("URGENT") >= 0
|| s.IndexOf("YOUR ASSISTANCE") >= 0
|| s.IndexOf("MILLION") >= 0
|| s.IndexOf("100% RISK FREE") >= 0
);
}
return this...string::*[IsInteresting(it)];
}
This code is noteworthy in two aspects. First, IsInteresting
is defined as a local function declaration inside the MustRead
method. Second, square brackets that are usually used for indexing are here
used for filtering. Within the filter the string variable it is
successively bound to all values of the transitive query.
Filters are expressions and thus compose easily, i.e. we can have one filter
after another: the result is still an expression. However sometimes we do not
only want to filter information but we also want to apply a method or a
code block to all stream elements. For instance, you might want to lower
all strings contained in an Email msg.
Normally we would write a foreach loop. But loops
aren't expressions and thus don't compose. Therefore, Cω provides a
foreach loop on the expression level (as we have already seen being
used several times before):
string* lowers = msg...string::*.{ it.ToLower() };
An alternative way to write this as a block is:
string* lowers = msg...string::*.{ s = it.ToLower(); return s; };
The expression or block is applied to all elements of the stream; within them it
is successively bound to the elements of the stream.
The following is a complete Cω program that demonstrates the concepts discussed above.
using System;
public class Test {
static void Main() {
xs = GetStrings();
// Implicitly convert stream of structs to stream of strings
string* ys = xs;
// print all strings in UPPERCASE
ys.{ Console.WriteLine("it = {0}.",it); };
// Convert all strings to lowercase
lowers = xs...string::*.{ it.ToLower() };
// Print all strings in lowercase
lowers.{ Console.WriteLine("it = {0}.",it); };
System.Console.ReadLine();
}
static struct{string Text;}* GetStrings() {
yield return new{Text = "THE"};
yield return new{Text = "QUICK"};
yield return new{Text = "BROWN"};
yield return new{Text = "FOX"};
yield return new{Text = "JUMPED"};
yield return new{Text = "OVER"};
yield return new{Text = "THE"};
yield return new{Text = "LAZY"};
yield return new{Text = "DOG!"};
}
}
it = THE. it = QUICK. it = BROWN. it = FOX. it = JUMPED. it = OVER. it = THE. it = LAZY. it = DOG!. it = the. it = quick. it = brown. it = fox. it = jumped. it = over. it = the. it = lazy. it = dog!.