Current issue

Vol.26 No.4

Vol.26 No.4

Volumes

© 1984-2024
British APL Association
All rights reserved.

Archive articles posted online on request: ask the archivist.

archive/22/4

Volume 22, No.4

 

Objects for APLers (Part 3 of 3)

by Morten Kromberg

Editors Note on Sections 1 - 8

The first four chapters of the Introduction to Object Oriented Programming For APL Programmers were published in Vector 22.1, and the next 4 in Vector 22.2. As Dyalog Version 11 has matured though the pre-release phase, there have been plenty of minor revisions, and some significant new capabilities introduced. Please use the latest download of the full document from the Dyalog website as your reference material for the earlier sections.

This series now concludes with the final 4 chapters. These are fully in line with the released version of the software, and no revisions are expected.

9. Deriving from Dyalog GUI Classes

In addition to the classes which you can write in APL, version 11.0 allows you to work with:

  • Dyalog GUI classes, including OLE Servers and and OLE Controls (ActiveX components)
  • Microsoft.Net classes

In version 11.0, these classes can be used in exactly the same way as instances of classes which have been implemented in APL. You create instances of them with ⎕NEW, and you can manipulate the public properties, methods and events which they expose (but unlike APL classes, you cannot see or modify the implementation). For example:

      MyForm←⎕NEW 'Form' (('Caption' 'Hello World')('Size'(10 10)))

      XL←⎕NEW'OleClient' (⊂'ClassName' 'Excel.Application')
      XL.ActiveWorkbook.Sheets[⊂'Sheet2'].Index
2
      ⎕using←'' ⍝ Set the .Net search path
      now←System.DateTime.Parse ⊂'2006-06-06 06:06:06.666'
      ∪now.(Year Month Day Hour Minute Second Millisecond)
2006 6 666

Note that the class name for built-in GUI objects is provided as a string rather than as a reference to a name in the workspace. The above objects were all accessible in version 10.1 of Dyalog APL, using slightly different mechanisms (which are still available in 11.0, in order to allow existing applications to continue working). The big difference is that:

  • Indexing of default properties is supported (see the Sheets example above)
  • Most importantly: you can write APL classes which derive from built-in and external classes, which is what this chapter is about.

In version 11.0, it is not possible to create instances of OLEControl objects using ⎕NEW (or classes which derive from OLEControls). This limitation will be lifted in a subsequent release.

A Standard Dialog Box

Dyalog GUI Classes provide basic building blocks for GUI applications. Each class, be it a Form, Button, List- or Combo-box, Edit, Grid, Treeview (etc.), is a very general component with a large number of properties which can be used to provide a variety of different behaviours depending on your application requirements. In any given application, you are likely to use certain behavioural patterns frequently. If you write an APL class which uses a GUI class as its base class, you can create “custom controls” which are easier to use in your particular application context, than the original Dyalog classes.

Imagine that your company has a corporate standard for the appearance of dialog boxes:

  • The form will have standard dialog box look (EdgeStyle Dialog, Border 2)
  • Each form will have a “Quit” and a “Save” button in the bottom left corner (“attached” to corner if the form is resized).
  • The form cannot be closed except by clicking on one of these buttons
  • The form Caption and its Size must always be provided when an instance is created.

We can create a suitable class which derives from 'Form': (you can find this class in the workspace DerivedGui in the OO4APL folder):

:Class Dialog : 'Form'
⍝ Implement Company Standard Dialog Form

⍝ Has 'Save' and 'Quit' buttons at bottom left
⍝ Coord Pixel, Cannot be Closed except by pressing Quit or Save

⍝ Usage: ⎕NEW Dialog (Caption Size FormProps)
⍝ Set Save.onSelect to take control of Save button behaviour

    :Field Public Save
    :Field Public Quit

    ∇ Create(cap size formprops);z
      :Access Public
      z←('Coord' 'Pixel')('EdgeStyle' 'Dialog')('Border' 2)
      z←z,('Caption'cap)('Size'size),formprops
      :Implements Constructor :Base z

      onClose←¯1 ⍝ Disable Close event
      z←('Size'(22 100))('Attach'(4⍴'Bottom' 'Left'))

      Save←⎕NEW'Button'(('Caption' 'Save')('Posn'((Size[1]-30),10)),z)

      Quit←⎕NEW'Button'(('Caption' 'Quit')('Posn'((Size[1]-30),120)),z)
      Quit.onSelect←'doQuit'
    ∇

    ∇ doQuit args
      :Access Public
      Close
    ∇

:EndClass ⍝ Dialog

Which allows:

      f1←⎕NEW Dialog ('Hello World' (50 250) ⍬)
      f1.BCol←192 192 255 ⍝
Pale Blue

Note that the name of the GUI class is quoted when it is used as a base class, in the same way as it would be in the argument to ⎕NEW. Apart from that, all the principles we have discussed so far about the use of the public members of the base class apply in the same way as if the base class had been written in APL. For example, we can set the BCol property to change the background colour.

In the class code, note that we can refer to the onClose property of the form directly in our constructor: it is a public property exposed by our base class and thus immediately available to derived class code – and to any user of the derived class. Our doQuit function can call the Close method of the form directly to close the form.

With the above class in our arsenal, we can start each dialog from a slightly higher level than a raw Dyalog Form object.

A Labelled Edit Field

One pattern which is often repeated in applications is an edit field with an attached label. It would be nice for application code to be able to treat such a pair as a unit, rather than have to position, size and track them separately. We can achieve this with a class which derives from the built-in Edit class and adds some right-justified text just to the left of the edit box:

:Class EditField : 'Edit'
⍝ An Edit field with an associated Label on the left
⍝ Usage example: ⎕NEW EditField
      ('Price:' '10.5' (10 30) (⍬ 50) (⊂'FieldType' 'Numeric'))

    :Field Private Label ⍝ Ref to a Text object

    ∇ Make(label text posn size editprops)
      :Access Public
      :Implements Constructor :Base ('Text' text)('Posn' posn)('Size' size),editprops

       Label←##.⎕NEW'Text'(('Text' label)('Points'(posn+3 ¯3))('Halign' 2)) ⍝ Created in container (##)
    ∇

:EndClass ⍝ EditField

Of course, a more sophisticated implementation might have options for the positioning and alignment of the label, but this class illustrates the principle. Creating “company standard” dialog boxes for data entry is now a bit easier than before:

      f1←⎕NEW Dialog ('Edit Contact'(200 300) ⍬)
      f1.First←f1.⎕NEW EditField ('First name:' '' (10 60) (⍬ 100) ⍬)
      f1.Last←f1.⎕NEW EditField ('Last name:' '' (38 60) (⍬ 100) ⍬)
      f1.Address←f1.⎕NEW EditField ('Address:' '' (66 60) (90 230)(⊂'Style' 'Multi'))
      f1.(First Last Address).Text←'D…' 'Duck' ('Box 555' 'Duckburg')

Our Dialog class exposes the Save button as a public field, so we can collect the data by defining a function and assigning it to the Select event:

     ∇ Update(button event);form

[1]    form←button.## ⍝ Find the form
[2]    ContactInfo←form.(First Last Address).Text
[3]    form.Close
     ∇
     f1.Save.onSelect←'Update'

Finally, we could collect our notes, tidy up and put the whole thing up into a little Contact class with an Edit method:

:Class Contact
    :Field Public FirstName←''
    :Field Public LastName←''
    :Field Public Address←0 ⍴⊂''

    ∇ Edit;f1
      ⍝ Uses #.Dialog and #.EditField
      :Access Public
      f1←⎕NEW #.Dialog('Edit Contact'(200 300)⍬)
      f1.First←f1.⎕NEW #.EditField('First name:' ''(10 60)(⍬ 100)⍬)
      f1.Last←f1.⎕NEW #.EditField('Last name:' ''(38 60)(⍬ 100)⍬)
      f1.Address←f1.⎕NEW #.EditField('Address:' ''(66 60)(90 230)(⊂'Style' 'Multi'))
      f1.(First Last Address).Text←FirstName LastName Address
      f1.Save.onSelect←'Update'
      ⎕DQ'f1'
    ∇
    ∇ Update(button event);form
      ⍝ Private method used by Edit as callback on Save button
      form←button.##
      FirstName LastName Address←form.(First Last Address).Text
      form.Close
    ∇

:EndClass ⍝ Contact

We have not created a constructor for this class, so new instances will have the values declared at the start of the class definition. Register your first three friends as follows:

      Friends←3↑⎕NEW Contact
      Friends.Edit

This pops us three modal dialog boxes in a row, one after the other – which is hardly ideal. We’ll improve on that in the next chapter.

10. Interfaces

As we have seen, a base class provides core functionality which other classes can build upon. The core functionality is available from each derived class, unless the derived class intentionally overrides all or part of it. This makes it possible for programs which “know about” the base class behaviour to use most, if not all, classes derived from the same base. If necessary, a user of a derived class can cast the instance to the base class using dyadic ⎕CLASS. The following expressions call the List method of ExcelWorkBook via an instance of PlanBook.

      w←⎕NEW PlanBook 'c:\temp\widgets.xls'
      (ExcelWorkBook ⎕CLASS w).List 'c:\temp'

There are situations where it is also useful for classes which do not derive from a common base class to expose common behaviour – an interface. In the same way as with classes which derive from a common base class, a program written to use a particular interface should be able to use any class which implements the interface.

For example, we can define an interface called iEditable which requires an object to be able to:

  • Paint itself inside a subform at a given location on a GUI Form and return a reference to the subform which it created for itself
  • Pick up new values for its properties when asked to do so

We can then write an editor which was able to edit any instance of any class which supported this interface. By convention, the names of interfaces begin with a lowercase i. Our iEditable interface definition might look like this:

:Interface iEditable

   ∇ SubForm←Paint(Container Position)
   ⍝ Paint instance in Container at Posn, return ref to SubForm used
   ∇

   ∇ Update
   ⍝ Update properties of instance from Painted controls
   ∇

   ∇ UnPaint
   ⍝ Remove any references to resources created by Paint
   ∇

:EndInterface ⍝ iEditable

The interface definition contains no code, which will leave many APL programmers wondering what it is for! In most other languages, an interface definition would contain a little more: declarations of the types of all the arguments and results. APL interface definitions can also include type information, if we want to export them for other languages to use – see the final sections on Microsoft.Net for more about this. There are other reasons why an interface definition is useful, which we will investigate in a moment.

Let us modify the Contacts class we created in the previous chapter, and replace the existing Edit and Update methods with an implementation of iEditable:

:Class EditableContact : iEditable
    :Field Public FirstName←''
    :Field Public LastName←''
    :Field Public Address←0 ⍴⊂''
  ⍝ --- iEditable implementation
    :Field Private idSubForm ⍝ ref to subform is stored

    ∇ {SubForm}←Paint(container position)
      :Signature SubForm←iEditable.Paint Container,Position
      SubForm←idSubForm←container.⎕NEW'SubForm'(('Posn'position)('Size'(120 200))('BCol'container.BCol))
      SubForm.First←SubForm.⎕NEW #.EditField ('First name:'FirstName(10 60)(⍬ 100)⍬)
      SubForm.Last←SubForm.⎕NEW #.EditField ('Last name:'LastName(38 60)(⍬ 100)⍬)
      SubForm.Address←SubForm.⎕NEW #.EditField('Address:'Address(66 60)(48 130)(⊂'Style' 'Multi'))
    ∇
    ∇ Update
      :Signature iEditable.Update
      FirstName LastName Address←idSubForm.(First Last Address).Text
    ∇
    ∇ UnPaint
      :Signature iEditable.UnPaint
      idSubForm←⍬
    ∇
:EndClass ⍝ Contact

With this class defined, we can create a form and arrange our contacts on it. Note that, in order to access the interface, we have to cast each instance of EditableContact to iEditable , in the same way that we might cast to a base clase if we wanted to access that. This is in order to make it possible for a class to add an interface without the risk of name conflicts between interface member names and members of the class itself:

      contacts←3↑⎕NEW EditableContact
      e_contacts←iEditable ⎕CLASS¨contacts ⍝ Cast each to iEditable
      e_contacts[1].⎕nl -3 ⍝ Each one exposes 3 methods  Paint UnPaint Update

      f1←⎕NEW Dialog ('My Contacts' (480 300) ⍬)
      e_contacts[1].Paint f1 (0 0)
      e_contacts[2].Paint f1 (150 0)
      e_contacts[3].Paint f1 (300 0)
      ⍝ or:       e_contacts {⍺.Paint f1 ⍵}¨(0 0)(150 0)(300 0)

At this point, pause to fill in the form:

When we are ready, we run the update functions:

      e_contacts.Update ⍝ Runs iEditable.Update on each instance
      contacts.(FirstName LastName)
  Donald  Duck    Dolly  Duck   Scrooge  McDuck  

We could also have defined a function to do the update and connected it to the Save button:

      upd←{e_contacts.Update}
      f1.Save.onSelect←'upd'

The reason we need an UnPaint method can be illustrated by the following sequence. Note that the form does not disappear when expunged. This is due to the references (in the private fields called idSubForm) inside each contact:

      ⎕ex 'f1'
      e_contacts.UnPaint

Of course, if contacts and e_contacts were local to the function doing the above work, or if we expunged these, the references would also disappear.

Avoiding Name Conflicts

Through the ages, many APL developers have “independently discovered” the need for something like interfaces, and written code which (for example) checks the class of the name 'Paint', and if the function is present deduces that “the interface is available”. However, this is dangerous: A class might have a method called Paint which has nothing to do with “being iEditable”. Even if we are rigorous and check all the names in the interface, there is a risk of confusion with similar interfaces. In addition to this: What it if our class already has a method called Paint and we want to add the interface to it?

The important reason for having an interface definition is the avoidance of these name conflicts between interfaces, or between an interface and names used by the class. In the EditableContact class, each of the interface functions has a :Signature statement, for example:

    ∇ {SubForm}←Paint(container position)

   ... stuff snipped ...

      :Signature SubForm←iEditable.Paint Container,Position

It is the name iEditable.Paint in the :Signature statement which declares that the function implements the Paint method of the iEditable interface. The name of the actual function is not actually used. It makes sense to use the same name as the interface function, but if we needed to avoid name conflicts we could call it ie_Paint or foo – anything we like.

Conclusion

The chapter on interfaces concludes the introduction to object oriented concepts as implemented in Dyalog APL version 11.0. The following chapters show how object orientation can be used to integrate APL with other tools which inhabit the Microsoft.Net Framework.

11. Using The Microsoft.Net Framework

Operating systems are designed to hide the details of the machine hardware from developers, making it possible to write applications without understanding the details of how the chips which manage memory, disk, keyboard, screen and other peripherals are controlled. As operating systems have evolved, they have tended to provided ever higher levels of abstraction. Environments like Microsoft.Net provide object-oriented encapsulations of everything from low level hardware interfaces to high level GUI and Databases.

If you are running Dyalog APL on a machine where the Microsoft.Net Framework is available, you have access to a vast collection of tools for application building, which are supplied by Microsoft. All of these classes, any classes you have acquired from third parties, plus the classes you write yourself in a language which supports .Net (Visual C#, Dyalog APL, and dozens of other languages), can be used in the same way as you would use the APL classes described in the preceding chapters.

All of the “.Net classes” mentioned above, whether they are something you have written yourself or they were provided by Microsoft, reside in files called Assemblies. Microsoft has decided to reuse the extension “.DLL” for these files, so they have the same extension as traditional Dynamic Link Libraries. There can be dozens, or hundreds of assemblies on your machine, so a mechanism is required to name the assemblies that an application would like to use.

⎕USING contains a list of assembly names (see the Dyalog APL documentation for details). If one of the elements is an empty vector, this is taken to mean the Microsoft.Net Framework classes contained in the assembly called mscorlib.dll. This core library contains the most commonly used classes. To give an impression of the types of services provided by .Net, the remainder of this chapter will explore a (very) small selection.

System.Environment

System.Environment is a class which contains a number of useful shared properties and methods which provide information about the platform on which the application is running:

      ⎕USING←'' ⍝ This is interpreted as ⎕USING←,⊂''

      System.Environment.⎕nl -2
CommandLine  CurrentDirectory  ExitCode  HasShutdownStarted
    MachineName  NewLine  OSVersion  ProcessorCount  StackTrace
    SystemDirectory  TickCount  UserDomainName  UserInteractive
    UserName  Version  WorkingSet

      se←System.Environment
      se.(Version OSVersion MachineName)
 2.0.50727.42  Microsoft Windows NT 5.1.2600 Service Pack 2  GOLLUM

      se.SpecialFolder.⎕NL-2
 ApplicationData  CommonApplicationData  CommonProgramFiles  Cookies
    ... etc ...

      se.(GetFolderPath SpecialFolder.ProgramFiles)
C:\Program Files

      se.(↑{⍵ (GetFolderPath SpecialFolder⍎⍵)}¨SpecialFolder.⎕NL-2)

ApplicationData      C:\Documents and Settings\mkrom.INSIGHT\...
CommonApplicationData  C:\Documents and Settings\All Users\...
  ... etc ...

      ⎕av⍳se.NewLine
4 3

We could have named the System namespace in ⎕USING. This would have allowed us to leave the System prefix out of our references to classes:

      ⎕USING←'System' ⍝ Equivalent to ⎕USING←,⊂'System'
      Environment.Version
 2.0.50727.42

The author’s personal preference is still to use the fully qualified names – I suspect this may change as I (and the rest of the APL community) start using these classes more frequently, and start to recognize Environment as meaning System.Environment. However: note that APL will only search for .Net classes if the name which is used would give a VALUE ERROR in the APL workspace. The use of so-called “namespace prefixes” in ⎕USING increases the likelihood of a name conflict between your own names and those in an assembly which you are trying to use (for example, Version is more likely to conflict with a name in the workspace than is System.Version). We’ll return to this topic in the next chapter.

System.Globalization.CultureInfo/DateTimeFormatInfo

The DateTimeFormatInfo class contains information about the date and time formats for a given culture. You can get hold of an instance of DateTimeFormatInfo for the current culture, or for a specific one:

      current←System.Globalization.CultureInfo.CurrentCulture
      current.(Name EnglishName)
  da-DK  Danish (Denmark)

      dtf←current.DateTimeFormat
      dtf.⎕nl -2

 AbbreviatedDayNames  AbbreviatedMonthGenitiveNames
    AbbreviatedMonthNames  AMDesignator  Calendar  CalendarWeekRule
    CurrentInfo  DateSeparator  DayNames  FirstDayOfWeek  ... etc ...

     dtf.MonthNames
 januar  februar  marts  april  maj  juni  juli  august  ...

     dtf.FullDateTimePattern
ddd, dd MMMM yyyy HH:mm:ss

     dtf.GetShortestDayName¨0 1 2 3 4 5 6
 sø  ma  ti  on  to  fr  lø

     dtf.NativeCalendarName   ⍝ SO much valuable information!
Den gregorianske kalender

     de←(⎕NEW System.Globalization.CultureInfo(⊂'de-DE')).DateTimeFormat
     de.DayNames
 Sonntag  Montag  Dienstag  Mittwoch  Donnerstag  Freitag  Samstag

de-DE means German as spoken (or rather, written...) in Germany.

System.DateTime and System.TimeSpan

DateTime and TimeSpan provide tools for working with timestamps, including doing arithmetic on them:

      ⎕←may29←⎕NEW System.DateTime (2006 5 29)
29-05-2006 00:00:00
      ⎕←aday←⎕new System.TimeSpan (24 0 0) ⍝ 24-hour timespan
1.00:00:00
      ⎕←may28←may29-aday
28-05-2006 00:00:00
      +\5⍴ aday
 1.00:00:00  2.00:00:00  3.00:00:00  4.00:00:00  5.00:00:00
      (+\5⍴aday).Days
1 2 3 4 5
      nextweek←may28++\5⍴ aday
      ,[1.5]nextweek
 29-05-2006 00:00:00
 30-05-2006 00:00:00
 31-05-2006 00:00:00
 01-06-2006 00:00:00
 02-06-2006 00:00:00
      nextweek.(Month Day)
 5 29  5 30  5 31  6 1  6 2
      nextweek>may29+aday
0 0 1 1 1
      (nextweek-System.DateTime.MinValue).Days
732459 732460 732461 732462 732463

System.IO.DirectoryInfo

The DirectoryInfo class contains methods for inspecting the contents of file system folders:

     temp←⎕NEW System.IO.DirectoryInfo(⊂'c:\temp')
     temp.CreateSubdirectory⊂'subdir'
c:\temp\subdir

     ↑(temp.GetDirectories⊂'*.*').(Name CreationTime)
 DyalogWebSite  30-05-2006 15:35:40
 js             24-02-2006 22:30:06
 subdir         12-06-2006 14:17:14

(temp.GetDirectories⊂'subdir').Delete 1

      files←temp.GetFiles⊂'a*.dws'
      ↑files.(FullName CreationTime LastAccessTime)
 c:\temp\a.DWS         18-01-2006 17:17:01  05-06-2006 21:57:06
 c:\temp\ab.DWS        12-04-2006 15:54:36  05-06-2006 21:57:06
 c:\temp\ado.DWS       17-06-2005 13:22:08  05-06-2006 21:57:06

      ,[1.5] ⎕CLASS files[1]
  System.IO.FileInfo
  System.IO.FileSystemInfo    System.Runtime.Serialization.ISerializable
  System.MarshalByRefObject
  System.Object

The final result above shows that each element of the result is an instance of System.IO.FileInfo, which derives from System.IO.FileSystemInfo ,which implements the interface System.Runtime.Serialization.ISerializable derives from System.MarshalByRefObject. At the end of the day, everything derives from System.Object.

System.IO.FileInfo

As we have seen above, the Getfiles method of DirectoryInfo returns instances of System.IO.FileInfo, which is a companion class for DirectoryInfo:

      (a←files[1]).⎕nl -2
 Attributes  CreationTime  CreationTimeUtc  Directory  DirectoryName
      Exists  Extension  FullName  IsReadOnly  LastAccessTime
      LastAccessTimeUtc  LastWriteTime  LastWriteTimeUtc  Length  Name

      a.(Name Exists IsReadOnly)
 a.DWS  1 0

      z←a.(CopyTo⊂'c:\temp\z.dws')
      z.(FullName CreationTime)
 c:\temp\z.dws  14-06-2006 16:28:30
      z.Delete

Summary

The above examples represent a very small subset of the classes provided by the Microsoft.net framework. There are classes for handling web requests in HTTP and FTP format, for compressing files, sending and receiving e-mail, GUI, database access, graphics and printing, the list is almost endless. It is our intention that, following the release of version 11.0, Dyalog will produce sample code for many of the most useful classes, to demonstrate how applications written in version 11.0 can tap into this vast resource.

In addition to providing the framework itself, the .Net environment specifies calling conventions which mean that classes implemented in all .Net languages – including Dyalog APL version 11.0 – are fully interoperable. A class written in any language can use, derive from, be called, and be used as a base class by any other .Net language. This allows us to integrate APL with other languages and tools more easily than ever before.

12. Using APL Classes from .Net

In the workspace called DotNet in the OO4APL folder, there is a class with two “mathematical” methods in it:

      )copy DotNet
Dotnet saved   ... etc
      Maths.Round (○1) 2
3.14
      Maths.Fib 10
55

The definition of the class is:

:Class Maths

    ∇ r←Round(n decimals);base
      :Access Public Shared
      base←10*decimals
      r←(⌊0.5+n×base)÷base
    ∇

      fibonacci←{⍝ Tail-recursive Fibonacci from ws "dfns"
          ⍺←0 1
          ⍵=0:⍬⍴⍺
          (1↓⍺,+/⍺)∇ ⍵-1
      }

    ∇ r←Fib n
      :Access Public Shared
      r←fibonacci n
    ∇

:EndClass ⍝ Math

We can make this class available to all Microsoft.Net applications if we copy it into a workspace and subsequently export the workspace as as .Net assembly. As a service to users of typed languages like C#, we probably want to add type declarations of the .Net types of the parameters and results of the public methods before we do the export. This is not strictly necessary, but without it all types will default to System.Object. The result of this will be that most users have to cast data to or from System.Object in order to use it. Our assembly will be more pleasant to use if we can declare everything using the simplest or closest .Net type.

To make the declarations, we must first add a :Using statement which allows us to find .Net data types (this is usually done at the top of the class script):

:Using System

Next, we must add one line to each of our methods (a class containing these declarations exists in the workspace DotNet under the name MathsX). Note that dynamic functions can not be used as public methods in a class, but must be “covered” by a traditional function which supports declarative statements.

    ∇ r←Round(n decimals);base
      :Signature Double←Round Double N, Int32 Decimals
    ∇ r←Fib n
      :Signature Double←Fib Int32 N

The first :Signature declares that Round returns a result of type Double (which means a double-precision floating-point number), and takes two parameters. The first is a Double which is called N (most .Net development tools will make this name visible to a developer using our class), and the second parameter is a 32-bit integer called Decimals .

After adding the signatures, we are ready to export our class:

      )clear
clear ws
      )NS MyCorp
#.MyCorp
      )CS MyCorp
#.MyCorp
      )copy dotnet Maths
... dotnet saved Mon Jun 19 14:01:18 2006

In the above example, we created a namespace MyCorp and copied Maths into it. This will export our class inside a .Net namespace called MyCorp, so that the class can be referred to as MyCorp.Maths. It is customary to embed classes within at least one level of namespaces – often two levels, typically a company name followed by a product name – in order to organize classes in applications which use classes from a number of different sources.

Select File|Export from the session menu, select “Microsoft .Net Assembly” as the file type and probably uncheck “Runtime Application”:

When you press Save, the following text should appear in the Status window:

The output allows us to check that the two methods Fib & Round were exported, with the expected parameter and result types. We can now write a C# programme which uses our class, for example:

public class Test
{
    static void Main()
    {
       System.Console.Write("Round(2/3,2) = ");
       System.Console.WriteLine(MyCorp.Maths.Round(0.6666,2));
       System.Console.Write("Fib(10) = ");
       System.Console.WriteLine(MyCorp.Maths.Fib(10));
    }
}

The workspace DotNet in the OO4APL folder contains a programme called CSC, which can be used to call the C# compiler. If we save the above programme in a file called c:\apl_assys\testmaths.cs (or copy the file by this name from the OO4APL folder), we can compile it by calling:

    'winexe' CSC 'c:\apl_assys\testmaths' 'c:\apl_assys\mycorpmaths.dll'
0  Microsoft (R) Visual C# .NET Compiler version 7.10.6001.4
   for Microsoft (R) .NET Framework version 1.1.4322
   Copyright (C) Microsoft Corporation 2001-2002. All rights reserved. 

The left argument of 'winexe' instructs the C# compiler to build a Windows Executable rather than an assembly. The right argument contains the name of the source file (without the .cs extension), optionally followed by any assemblies which it needs. The result is a file with the same name as the source file, but with an extension of .exe. We call it from APL using ⎕CMD:

      ⎕CMD 'c:\apl_assys\testmaths.exe'
Round(2/3,2) = 0,67
Fib(10) = 55

It is important to note that our assembly (mycorpmaths.dll) can be used from ANY language which can use Microsoft.Net. The list includes APL – see the web page http://www.gotdotnet.com/team/lang/. In fact, we can use the assembly from APL, in the same way that we would use any other .Net assembly:

      )clear
clear ws
       ⎕using←',c:\apl_assys\mycorpmaths.dll'
       MyCorp.Maths.Round (○1) 3
3.142

13. Inheriting from a .Net Base Class

In the same way that you can inherit from one of the built-in Dyalog GUI classes, you can write a class which derives from a .Net base class. You can derive from classes which you write yourself in a .Net language (including Dyalog APL J), classes purchased from a 3rd party tool vendor, or from Microsoft.Net Framework classes. Very many of the Framework classes are sealed for security and performance reasons, which means that they can not be used as base classes, but quite a few are intended for use as base classes.

We will now look at writing our own class in C#, as an optimization of our fibonacci function. The Fibonacci series is generated using a recursive algorithm which does a VERY small amount of processing for each level of recursion. Even though tail-recursive dynamic functions are highly efficient, a compiled, strongly typed language like C# is going to be able to execute this particular type of algorithm significantly faster than APL can do it.

If we build our Maths class on a base class written in C#, we can easily substitute some of the methods by methods written in C#. For example, we can write the following (ugly but fast) C# class:

using System;
namespace OO4APL

{ public class Maths
    { public static double Fib(int n)
      {   double n1 = 0, n2 = 1, r = 0;
          int i=1;
                  while (i<n)
                  {   i++; r = n1 + n2;
                      n1 = n2; n2 = r;   }
                  return n2;
      }
    }
}

If we save this code in a file called fibonacci.cs (which can be copied from the OO4APL folder), and compile this class using our CSC function:

      CSC 'c:\apl_assys\fibonacci'

0  Microsoft (R) Visual C# .NET Compiler version 7.10.6001.4
   for Microsoft (R) .NET Framework version 1.1.4322
   Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.

We can now write a new class FastMaths , which derives from OO4APL.Maths. It contains the methods which are still written in APL, the rest will be inherited from the base class:

:Class FastMaths : OO4APL.Maths

:Using ,c:\apl_assys\fibonacci.dll

    ∇ r←Round(n decimals);base
      :Access Public Shared
      base←10*decimals
      r←(⌊0.5+n×base)÷base
    ∇

:EndClass ⍝ Math

In version 11.0, a class which derives from a .Net class must be exported as a .Net class before it can be used. This restriction may be relaxed in a future release. If you want to test the class quickly, without creating a .Net assembly as a .dll, it is sufficient to export the class to memory using the menu item File|Export to memory, after which it can be called:

      FastMaths.Round(○1)3
3.142
      FastMaths.Fib 10
55

The first method runs in APL, and the second in C#.

Conclusion

This concludes the Introduction to Object Oriented Programming for APL Programmers. If you want to learn more, it is time to read the Release Notes and the Language Reference for Version 11.0, both of which contain a number of additional examples.


script began 5:22:14
caching off
debug mode off
cache time 3600 sec
indmtime not found in cache
cached index is fresh
recompiling index.xml
index compiled in 0.1809 secs
read index
read issues/index.xml
identified 26 volumes, 101 issues
array (
  'id' => '10011350',
)
regenerated static HTML
article source is 'HTML'
source file encoding is ''
read as 'Windows-1252'
URL: ./oop224_files/image002.jpg => trad/v224/./oop224_files/image002.jpg
URL: ./oop224_files/image004.jpg => trad/v224/./oop224_files/image004.jpg
URL: ./oop224_files/image006.jpg => trad/v224/./oop224_files/image006.jpg
URL: ./oop224_files/image008.jpg => trad/v224/./oop224_files/image008.jpg
URL: ./oop224_files/image010.jpg => trad/v224/./oop224_files/image010.jpg
completed in 0.2082 secs