Causeway->Products->CSharp translator->Examples

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 Adrian’s Vector article which takes you through line-by-line and step-by-step.

The Sieve of Erastosthenes

I 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 ...

     ’ primes„Eratos max;candidates;check
[1]     © Returns primes up to n by the sieve of Eratosthenes
[2]      primes„1
[3]      candidates„1‡¼max
[4]
[5]      :While 0<½candidates
[6]        primes„primes,check„candidates[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 ...





...then scroll down to see what I needed to do to fix it ...


....OK!

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 don’t 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 Simulator

This 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 ...



... things that often get noted are the inner product on line[11], the complex indexing expression [13] and the branch on [16]. The one thing I will definitely need to do is remove the assignment to ŒIO which is (currently) fixed at 1 in the SharpArrays DLL. Anyway, let’s give the conversion a try ...

      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 can’t 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 up

This 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:

  1. Give it a quick visual check, remove localised index-origin or ML settings, and declare anything obvious (like array arguments, and probably the result type). You might like to declare quite a few local variables (which will save the translator working them out for you) but this is not strictly necessary.
  2. Run your function through the convertor (probably ignoring the result for now) and double-click on the error messages that you see in the session to get straight to each problem line in turn. Hopefully, the message is clear enough to allow you to fix the problem! Usually you will get a quite a few errors on ‘scratch locals’ where the old habits of symbol-table economy often led to much reuse of short meaningless names.
  3. When the convertor stops barking about errors, have a good look at the generated C# code with a view to making your APL more ‘translator-friendly’ - often by replacing old-style branch conventions with control structures. This generally improves readability, and should make the compiled code run faster.
  4. If you are serious about making a conversion that will work in the long-term, go through and replace all your short UPPERCASE variable names with more meaningful biCapitalised ones. Try to avoid any re-use of local names to mean different things in different places in the code. The C# will look a lot better, and your APL code-quality will improve.

Oh, just for fun, let’s 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 can’t 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