Examples Home About us Contact info Vector graphics SVG Fills & Filters Products Price list CSharp translator User review Compiling APL Examples Limitations Seminars Dyalog APL2000 Support CUSP CausewayPro RainPro Newleaf Helpstuf Leafhtm Tutorials Demos Climate Charts VML graphics SVG examples Free Stuff CSS Editor Publications Seminars Articles |
Causeway Graphical Systems Compiling APL - some Examples The best way to get a feel for what the code-translation can do is to pull a function off the shelf and try it! This article does just that - for a more structured summary of the code-translation process, see Adrians Vector article which takes you through line-by-line and step-by-step. The Sieve of ErastosthenesI really like this benchmark (I once wrote a PL/1 version just to prove how fast VS APL was) so here we go with some fairly modern structured code. There are lots of ways to make this faster, but the point here was to try a 'natural' array approach to the classic algorithm rather than hand-tune it in any way. Here is the APL original ... primesEratos max;candidates;check
[1] © Returns primes up to n by the sieve of Eratosthenes
[2] primes1
[3] candidates1¼max
[4]
[5] :While 0<½candidates
[6] primesprimes,checkcandidates[1]
[7] candidates(0<check|candidates)/candidates
[8] :End
½Eratos 100000 9593 ... which runs in around 10sec in Dyalog APL on my old 400MHz laptop. This is a fairly hard target to beat, as it loops 9593 times, but must repeatedly compress a reasonably long vector of possible primes each time around the loop. Have a quick look down the APL code and see if you can spot anything we may need to change (or add) to make it compile ... Most people who come to the seminars pick up the need to declare (at least) the argument, and they tend to be concerned about the embedded assignment on line[6]. Very few people notice that the very first assignment to the result sets it to a scalar 1, but a little lower down it becomes a vector. In APL, this is the sort of thing we do all the time without being aware of it, and it is little things like this that can take quite a while to tidy up. Here is what happens if I try to compile this function as it stands: try'Eratos'
TYPE CLASH - int primes <- int[] at #.#.Eratos[5]
// ============ Eratos ============
// Returns primes up to n by the sieve of Erastosthenes
int Eratos(int max)
{
int check,primes;
int[] candidates;
primes = 1;
candidates = AE.Drop(1,AE.Range(max));
while (0 < candidates.Length) {
primes = AE.Join(primes,check = candidates[0]); //! TYPE CLASH - int primes <- int[]
candidates = AE.Compress(AE.LT(0,AE.Remainder(check,candidates)),candidates);
}
return primes;
}
As it happens, if you dont declare the argument type, this just assumes int so it got away with it, but we probably should tell it before going live! So what went wrong? Notice the error text reported in the session and appended to line[5] as a comment ... //! TYPE CLASH - int primes <- int[] The problem (as usual) is not where the diagnostic is - primes is supposed to be a vector of integers, but the very first time the translator sees it, it is set to a simple integer. For want of any better information, this then gets nailed in as the data type, hence the error when we actually try to make it into a vector. One way to fix this is to add an explicit declaration: primes„Eratos max;candidates;check © Returns primes up to n by the sieve of Erastosthenes ©:Public ©: int[] primes primes„1 candidates„1‡¼max :While 0<½candidates primes„primes,check„candidates[1] candidates„(0<check|candidates)/candidates :End I added two lines beginning ©: to the beginning of the function. The first simply tells C# that this function is public (so I will be able to call it from another class) and the second tells the translator that the result variable is an array of integers. Running the translator on this amended version yields: // ============ Eratos ============
// Returns primes up to n by the sieve of Erastosthenes
public int[] Eratos(int max)
{
int check;
int[] candidates,primes;
primes = new int[] {1};
candidates = AE.Drop(1,AE.Range(max));
while (0 < candidates.Length) {
primes = AE.Join(primes,check = candidates[0]);
candidates = AE.Compress(AE.LT(0,AE.Remainder(check,candidates)),candidates);
}
return primes;
}
You can see that we now create our primes variable as a 1-element array, and that the function is flagged public in the (corrected) header line. This is all completely valid C#, and you can wrap it in a little testbed, and compile and run it. It will be a little faster than the APL original, but really there is nothing much in it. On to something a little more challenging! The Monty Hall SimulatorThis is an example of old-style APL code which is about as unlike RainPro as you can be. It was published in Vector as part of a nice article on the Monty Hall paradox by Devon McCormick. ’ CARS„N GOATDOOR C;ŒIO;DOORS;PICK;GOAT;NG
[1] © Let's make a deal! We can choose 1 of 3 doors: 1 hides a car,
[2] © 2 hide goats. We pick one but Monty gives us a chance to re-choose
[3] © after he opens one of the remaining 2 doors to reveal a goat.
[4] © How many cars do we get by
[5] © C: 0„…keeping 1st choice, 1„…changing to other door?
[6] © N: number of trials.
[7] ŒIO„1 ª CARS„0
[8]
[9] L_MAKEADEAL:DOORS„(?3)²0 0 1 © Randomize choices: 1=car
[10] PICK„(?3)²0 0 1 © We pick a door: 1 is choice
[11] NG„0+.=GOAT„DOORSŸPICK © Number of unpicked goat doors
[12] GOAT[((~GOAT)/1 2 3)[?NG]]„NG¬1 © Show a goat at random (=0)
[13] DOORS„GOAT/DOORS ª PICK„GOAT/PICK © Eliminate the shown door
[14] PICK„C¬PICK © Change pick or don't
[15] CARS„CARS+PICK/DOORS © Add car if we got it
[16] …(×N„N-1)½L_MAKEADEAL
’
Again, have a quick look through the APL original and see what you can spot that may be a problem ... qq„try'GOATDOOR' ******* #.GOATDOOR: Failed to parse ŒIO„1 TYPES - (int[],int[]) not supported by AE.Or at #.GOATDOOR[11] TYPE CLASH - int[] PICK <- bool[] at #.GOATDOOR[14] TYPE CLASH - int CARS <- int[] at #.GOATDOOR[15] There is the expected error with the index origin, but three more possible errors need looking at too. Why does the translator claim that it has been asked to OR two integer arrays on line[11]? This is one of those unfortunate places where you cant tell the type of an APL constant (such as 0 0 1) by simple lexical analysis. This could be boolean, it could be integer, or it could be floating point. There are a number of ways around this one (such as creating another variable with this value and declaring it) but what I chose in the RainPro conversion was to define a couple of trivial globals true and false and to recognise these as effective keywords when compiling. So if we change the original APL to ... ’ CARS„N GOATDOOR C;ŒIO;DOORS;PICK;GOAT;NG
[1] © Let's make a deal! We can choose 1 of 3 doors: 1 hides a car,
[2] © 2 hide goats. We pick one but Monty gives us a chance to re-choose
[3] © after he opens one of the remaining 2 doors to reveal a goat.
[4] © How many cars do we get by
[5] © C: 0„…keeping 1st choice, 1„…changing to other door?
[6] © N: number of trials.
[7]
[8] CARS„0
[9] L_MAKEADEAL:DOORS„(?3)²false false true © Randomize choices: 1=car
[10] PICK„(?3)²false false true © We pick a door: 1 is choice
[11] NG„0+.=GOAT„DOORSŸPICK © Number of unpicked goat doors
[12] GOAT[((~GOAT)/1 2 3)[?NG]]„NG¬1 © Show a goat at random (=0)
[13] DOORS„GOAT/DOORS ª PICK„GOAT/PICK © Eliminate the shown door
[14] PICK„C¬PICK © Change pick or don't
[15] CARS„CARS+PICK/DOORS © Add car if we got it
[16] …(×N„N-1)½L_MAKEADEAL
’
... and run this past the convertor, we can get rid of 2 out of the 3 unexpected messages ... qq„try'GOATDOOR1' TYPE CLASH - int CARS <- int[] at #.GOATDOOR1[15] So just what is going on on line[15]? The CARS variable is (correctly) assumed to be a simple integer scalar, and the result of the compression must be single-valued as there is only one 1 in that boolean door-picker! The problem is that the original APL coder was just a little sloppy and that the function actually returns a singleton vector which (in almost all cases) APL treats as if it were scalar. The translator cannot know that an integer vector is really a singleton, so the only thing to do here is to tidy the original code: [15] CARS„CARS+†PICK/DOORS © Add car if we got it
[16] …(×N„N-1)½L_MAKEADEAL
’
This now generates to error-free C#, and the result looks like this (less a few long comments): // ============ GOATDOOR ============
// Let's make a deal! We can choose 1 of 3 doors: 1 hides a car,
// 2 hide goats. We pick one but Monty gives us a chance to re-choose
// after he opens one of the remaining 2 doors to reveal a goat.
// How many cars do we get by
// C: 0„…keeping 1st choice, 1„…changing to other door?
// N: number of trials.
int GOATDOOR(int N,int C)
{
bool[] DOORS,PICK,GOAT;
int NG,CARS;
CARS = 0;
L_MAKEADEAL: DOORS = AE.Rotate(AE.Roll(3),new bool[]{false,false,true});
PICK = AE.Rotate(AE.Roll(3),new bool[]{false,false,true}); // We pick a door
NG = PlusDotEQ(0,GOAT = AE.Or(DOORS,PICK)); // Number of unpicked goat doors
GOAT[AE.Compress(AE.Not(GOAT),new int[] {1,2,3})[AE.Roll(NG)-1]-1] = NG != 1;
DOORS = AE.Compress(GOAT,DOORS);
PICK = AE.Compress(GOAT,PICK); // Eliminate the shown door
PICK = AE.NE(C,PICK); // Change pick or don't
CARS = AE.Plus(CARS,AE.First(AE.Compress(PICK,DOORS)));
if (0 != AE.Signum(N = N - 1)) goto L_MAKEADEAL;
return CARS;
}
There is one really ugly line which you may like to fix! The final test uses the APL signum (monadic times) to check for a non-zero counter. This returns an integer result, so the translator has had to add the unnecessary comparison with zero. Also, calling an APL engine just to see if a scalar is zero-valued seems pretty dumb! Changing that test to: [16] …(0<N„N-1)½L_MAKEADEAL
’
... and re-running the conversion gives a much nicer C# equivalent: if (0 < (N = N - 1)) goto L_MAKEADEAL; return CARS; } Wrap upThis exercise in walking through a typical little APL function actually illustrates most of what you need to do to make code-conversion work for you. The way to approach each function is:
Oh, just for fun, lets try compiling it and running some timings. We would expect a bit of a speed-up here as the APL code is very scalar and the resulting C# is rather simple. The original APL code runs 300000 simulations in around 8 seconds (on my fancy new Sony) 300000 GoatDoor 0
Started 2005 9 28 11 17 52 0 for 300000 iterations
Ended 2005 9 28 11 18 0 0
100340
300000 GoatDoor 1
Started 2005 9 28 11 18 3 0 for 300000 iterations
Ended 2005 9 28 11 18 11 0
199973
So a comparative test from the DOS commandline would be: Console.WriteLine("No switch was " + test.GoatDoor(300000,0));
Console.WriteLine("Switched was " + test.GoatDoor(300000,1));
Started 2005 9 28 11 20 45 423 for 300000 iterations
Ended 2005 9 28 11 20 46 94
No switch was 99537
Started 2005 9 28 11 20 46 94 for 300000 iterations
Ended 2005 9 28 11 20 46 685
Switched was 200034
So around 0.6 sec for the same set of trials, giving a factor of 12 improvement. This is at the high end of the scale, but may be quite typical of simulations which are normally very hard to write as clean one-shot array code. Reading on ...Of course there are some things it will never do, and lots of things it cant do just now (APL is a rich and varied language, and every APLer has a personal style). For a complete list of the current limitations, read on ... Website maintained by adrian@causeway.co.uk Telephone: +44 (0) 1439 788413 |
|