HaskellScript previous home next  
lambda ObjectScript Example
HaskellScript
  Documentation
  Download
  Questions
  History
  Acknowledgements

HaskellAgent
HaskellDirect
HaskellObject
  Example
  Documentation
ScriptServer
  Interface

The following example will show how to develop a COM component in Haskell. The example is shipped with HaskellScript and can be found in the demos\hscript\objectscript\calculator directory. Clients of the examples are written in Visual Basic 6.0 and Visual J++ 6.0, but any other COM compliant language can be used (like Haskell, Smalltalk, Perl, Python or C++).

A Calculator

A calculator will be developed that knows about precedence (ie. 1+2*3 = 7, not 9) and uses infinite integers for its calculation. Haskell will be used to implement the semantics of the calculator, Java and VB will be used to conveniently create the visual interface. The final calculator will look like:

calculator interface

Automation objects

The Haskell component will be an automation component. This is just a COM component that supports the IDispatch interface. This interface allows methods to be called by name instead of a static offset, ie. it implements dynamic binding. The dynamic nature of those interfaces allow the ObjectScript software to act as a host component for Haskell objects, delegating all method calls to the appropiate Haskell function.

Writing an interface specification

Allthough ObjectScript doesn't need it, it is recommended to describe the interface to the component in IDL, the Interface Description Language. An IDL description has the following advantages:

  • Normally, all calls to an Automation object are checked at run-time. With an IDL description, much of these checks can be done at compile-time.
  • Visual environments can use the IDL description to aid the programmer.
  • The HaskellDirect compiler can generate the proxy code needed for a Haskell component from such an IDL description (allthough it can be easily written by hand).
  • It documents your interface.

ObjectScript and Visual tools like VB and J++ can not directly use IDL descriptions but use type libraries. Type libraries are binary descriptions of the IDL source. The MS IDL compiler (midl.exe) is used to generate type libraries from the IDL source.

The IDL description (proxycalculator.idl) starts with a type library declaration:

[ uuid(C0EA2BF4-AFD1-11D2-B378-006008D1BF8C) 
, helpstring("Calculator sample type library")
, version(1.0)
]
library CalculatorLib {
importlib("stdole.tlb");

All code between brackets are attributes. The uuid attribute is a Universal Unique IDentifier. These numbers are used in COM to identify components. You can generate such numbers using the guidgen.exe utility. (At the time of writing, the version attribute is required by ObjectScript and should always be 1.0 due to tiresome reasons.) The library statement creates a type library and the importlib statement imports the default COM type declarations. The interface for the calculator is defined next:

[ uuid(C0EA2BF5-AFD1-11D2-B378-006008D1BF8C) 
, helpstring("Calculator interface")
]
dispinterface ICalculator
{  
   properties:
      [id(1),readonly] BSTR DisplayString;
   methods:
      [id(2)] void Assign(void);
      [id(3)] void Clear(void);
      [id(4)] void Digit( [in] int digit );
      [id(5)] void Sign(void);
      [id(6)] void ParenOpen(void);
      [id(7)] void ParenClose(void);
      [id(8)] void Plus(void);
      [id(9)] void Minus(void);
      [id(10)] void Mul(void);
      [id(11)] void Divide(void);
      [id(12)] void Modulo(void);
      [id(13)] void Factorial(void);
      [id(14)] void Power(void);
};

The dispinterface statement is used to define a pure IDispatch interface (instead of a dual interface, which ObjectScript doesn't support). The id attribute identifies methods and properties by number and is arbitrary. The interface has one (readonly) property called DisplayString which retrieves a textual representation of the current result of calculator. The BSTR type is used in automation to represent unicode strings with length information. It returns a string since a normal integer would overflow with the infinite integers used internally. All the methods simply correspond to the buttons on a calculator. The Digit method takes a integer between 0 and 9 as an argument.

The last item is the actual Haskell component that implements this interface:

[ uuid(C0EA2BF3-AFD1-11D2-B378-006008D1BF8C)
, helpstring("Haskell Calculator class")
]
coclass Calculator
{
  [default] dispinterface ICalculator;
};
};

The coclass statement defines the COM component. It only implements the ICalculator interface. (ObjectScript requires this interface to be the default). The uuid attribute specifies the class id (CLSID) of the component. After the IDL specification is written, the MIDL compiler is used to generate the type library:

> midl proxycalculator.idl
The resulting type library (proxycalculator.tlb) can be viewed with the Oleview utility.
Using Automation objects

Before I explain how to write the Haskell component, I will first show how a client would use the calculator component. (Normally, you need to write the component first before it can be used). Both a Visual Basic and Visual J++ example is given. Both examples are in the demos\hscript\objectscript\calculator directory with full source code.

Visual Basic

In Visual Basic, a new project is started and the interface for the calculator is drawn. The display is a text label and the digit buttons are created as a control array. Before we write code, the type library is imported via the menu "project/references". The script starts with declaring and creating an instance of the calculator.

Dim Calc As Calculator

Private Sub Form_Load()
Set Calc = CreateObject("Haskell.Calculator")
End Sub

The CreateObject call creates a new COM object. Normally a CLSID is provided but in VB you can also use Programmatic IDentifiers (ProgID's) to reference an object. Later we will see how to register the Haskell component with the ProgID "Haskell.Calculator".

Next we define event handlers for each button by clicking on it and we define a procedure to refresh the display after each action. Note how Visual Basic can help the programmer by using the information from the type library. Simply click "file/make exe" and we have a calculator running.

Calculator design in Visual Basic Calculator interface in Visual Basic

Java

Just as in Visual Basic, the interface of the calculator can be drawn in Visual J++. After the interface design is done, the type library is transformed to a Java package via the menu "Project/Add COM Wrapper". The Haskell calculator is now available as if it were a normal Java class. The Java source starts by importing this package:

import proxycalculator.*;

public class Form1 extends Form
{
  public ICalculator calc;      
        
  public Form1()      
  {      
     // Generated by Visual J++ Form Designer support
     initForm();                        

     // TODO: Add any constructor code after initForm call
     calc = new Calculator();           
     refreshDisplay();           
  }      

  public void dispose()      
  {      
     // Generated by Visual J++ Form Designer support
     super.dispose();           
     components.dispose();           
                
     // TODO: Add any dispose code           
     calc = null;           
  }            

The Form class is generated by the visual environment. I have added the calc variable to hold a reference to an ICalculator interface. In the constructor this reference is initialized by calling new. The Calculator class implements the ICalculator interface (as defined in the IDL description) but if another implementation is wanted, perhaps a Java object, only this line needs to be changed. Due to an unknown reason, the calc variable needs to be set to null explicitly.

Now define the event handlers for the buttons by clicking on them. Choose menu "Build" and we have a nice calculator. [show design]

Writing a Haskell component

Before the above clients can work, the actual component needs to be implemented. A Haskell implementation of a component consists of two modules, a proxy (proxycalculator.hs) and the actual implementation (calculator.hs). The proxy module is the glue between Haskell and the outside world. This module can be generated with HaskellDirect from the IDL description. However, HaskellDirect doesn't support this mode of operation yet, so the next section explains how to write it by hand.

Since the calculator uses infinite integers, the Integer type is used for the numbers. To handle parenthesis and precedence, the calculator uses a stack to remember past operations. The main data types are:

data Op  = Num Integer
         | Bin Int (Integer -> Integer -> Integer)
         | Open
            
type Stack      = [Op]
type Transition = Stack -> (Stack,Maybe Integer) 

An Operation is either a number, a binary operator with some priority and functionality or a open parenthesis. The stack is a list of operations. A calculation is a Transition from a stack to a new stack and maybe a new result for display.

All permissable operations of the calculator are now put into a transition table that specifies for each kind of operation a transition function.

stateTable :: [(String,Transition)]
stateTable      = digits ++ ops

digits  = map digitOp [0..9]
        where
          digitOp d  = (("d"++show d), adigit d)

ops     = [("assign", binop 0 (flip const))
          ,("clear",  unop    (const 0))
          ,("mul",    binop 2 (*)    )  
          ,("div",    binop 2 idiv   )  
          ,("plus",   binop 1 (+)    )  
          ,("minus",  binop 1 (-)    )  
          ,("open",   openop         )  
          ,("close",  closeop        )  
          ,("sign",   unop negate  )    
          ,("fac",    unop fac     )    
          ,("mod",    binop 2 mod   )   
          ,("pow",    binop 3 power'   )
          ]        
        where        
          idiv x 0      = 0        
          idiv x y      = x `div` y
An example of a transition is the binop function:
binop :: Int -> (Integer -> Integer -> Integer) -> Transition
binop prec f ss   = (Bin prec f : ys, y)
                  where
                    (y,ys)  = eval prec ss

eval is the heart of the calculator. It uses pattern matching on the stack to evaluate the stack as far as possible:

eval :: Int -> Stack -> (Maybe Integer,Stack)

eval prec (Num x : Bin p f : Num y : xs)    
     | prec <= p  = eval prec (Num (f y x) : xs)  --apply bin

eval prec (Num x : Open : xs)   
     | prec <= 0  = (Just x, Num x : xs)
     
eval prec (Open : xs)           = (Nothing, xs)
eval prec (Num x : xs)          = (Just x, Num x : xs)
eval prec (Bin p f: xs)         = eval prec xs       
eval prec []                    = (Just 0,[])        
Higher-order functions, polymorphism, infinite integers and algebraic data types make Haskell ideal to implement a non-trivial calculator in just a few lines of code (exercise: Create the same functionality in C++ or Java and evaluate the size and ability to extend and maintain). The above program can be loaded in Hugs where all functions can be evaluated and inspected.
Writing the component code
Now, the above code is used to implement an object based interface to the calculator. First we define a record to maintain the state of the calculator between method calls.
module Calculator where

import Com
import LibRef

data Calculator = Calculator { stack :: Ref Stack, 
                               display :: Ref Integer }

The calculator has two fields, a stack and the current display result. Next some primitive functions are defined to access the state and to construct an initial calculator state:

calculatorConstruct :: IO Calculator
calculatorConstruct     = do stack   <- newRef []
                             display <- newRef 0
                             return Calculator { stack, display }

getStack calc           = getRef (stack calc)
setStack xs calc        = setRef (stack calc) xs

getDisplay calc         = getRef (display calc)
setDisplay x calc       = setRef (display calc) x

Normally, these functions are automatically generated by HaskellDirect. calculatorConstruct is automatically called when an object is constructed. The name of this function should always be the object name followed with Construct.

Next, the methods of the interface are defined. The (#) operator swaps its arguments: x # f = f x. All the methods receive the current state as their last argument. Note that the property DisplayString is called getDisplayString. setDisplayString should have been defined too, if this wasn't a readonly property.

assign :: Calculator -> IO ()
assign          = transition "assign"
clear           = transition "clear"
sign            = transition "sign"
power           = transition "power"
...
digit :: Int -> Calculator -> IO ()
digit n calc    
  | n >= 0 && n <= 9  = calc # transition ("d"++show n) 
  | otherwise         = coFail "invalid digit"
          
getDisplayString :: Calculator -> IO String
getDisplayString calc
  = do x <- calc # getDisplay
       return (show x)

The transition function, looks up the operation in the state table and performs the transition on the current stack. If the operation is unknown, it fails with a COM error (coFail)

transition :: String -> Calculator -> IO ()      
transition name calc
  = case (lookup name stateTable) of
      Nothing     
        -> coFail "unknown operation"
      Just trans  
        -> do xs <- calc # getStack 
             let (ys,y) = trans xs
             calc # setStack ys
             case (y) of 
               Nothing -> return ()
               Just d  -> calc # setDisplay d
Generating the proxy

The glue code between Haskell and the outside world is put in the proxy module (proxycalculator.hs). This module can be generated by HaskellDirect but this is not supported in the current release of HaskellDirect.

The Proxy module starts by importing the Calculator module followed with the definitions of the proxy methods:

module ProxyCalculator where

import ScriptEvents
import Calculator

proxyCalculatorConstruct
        = constructor calculatorConstruct
        
proxyDisplayString = property readonly getDisplayString

proxyClear      = public_0_0 clear
proxyAssign     = public_0_0 assign
proxyDigit      = public_1_0 digit
proxySign       = public_0_0 sign
proxyParenOpen  = public_0_0 parenOpen
proxyParenClose = public_0_0 parenClose
proxyPlus       = public_0_0 plus
proxyMinus      = public_0_0 minus
proxyMul        = public_0_0 mul
proxyDivide     = public_0_0 divide
proxyModulo     = public_0_0 modulo
proxyFactorial  = public_0_0 factorial
proxyPower      = public_0_0 power
This is all! The proxy methods should have the name of the method/property with the proxy prefix. The constructor is defined using the constructor function. A property with the property function which takes a set and get function as arguments. The set function of a readonly property is readonly. The public_n_m functions define methods with n arguments and m results. Note that every argument should be in the Variant class (Automation objects can only pass VARIANT types).
Registering the object

The last thing that is needed before the component can be found by other applications is to register the component. The example utility scriptreg.ext can be used to register Haskell components. It can be found in the demos\hscript\objectscript\scriptreg directory. The utility itself is written using Visual Basic for the interface and a (stateless) Haskell component for manipulating the registry.

Start up ScriptReg and type the class name (Calculator) and the source of the proxy module. Normally this is enough information but since we have already defined a CLSID in the type library, we need to be carefull and set the correct CLSID also.

Script registration of the calculator

Since the calculator is already registered when installing HaskellScript, all the information of the component will popup as soon as the class name is typed. After the registration, the component can be used by any other COM compatible language.

Daan Leijen, "Nov 22 1999".

 
previous home next