| HaskellScript | |
| 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 CalculatorA 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:
Automation objectsThe Haskell component will be an automation component. This is just a
COM component that supports the Writing an interface specificationAllthough 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:
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 ( The IDL description (
[ 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(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 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 > midl proxycalculator.idlThe resulting type library ( proxycalculator.tlb) can be viewed
with the Oleview
utility.
Using Automation objectsBefore 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 Visual BasicIn 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 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.
JavaJust 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 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 componentBefore the above clients can work, the actual component needs to be
implemented. A Haskell implementation of a component
consists of two modules, a proxy ( Since the calculator uses infinite integers, the
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 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 :: 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 codeNow, 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
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.
Next, the methods of the interface are defined. The
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 :: 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 proxyThe glue code between Haskell and the outside world is put in the
proxy module ( The Proxy module starts by importing the
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 objectThe last thing that is needed before the component can be found by other
applications is to register the component.
The example utility Start up ScriptReg and type the class name (
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". | |
|   | | |