Friday, 5 March 2010

A Cocoa application (almost) completely implemented in Haskell


This is the third part of a small series of articles1 on how to use Haskell for the development of Cocoa applications. In this post I describe a simple way how to implement a Cocoa controller in Haskell. This way one can define the application’s model layer completely in Haskell without any reference to the underlying Cocoa framework (or Objective-C classes).

Recap: OBJC Instances

Since the last blog entry I created a few new instances of OBJC and changed some of the old ones. Here is an overview of the current implementation:

Data StructureHaskell RepresentationObjective-C RepresentationRemarks
Opaque value 1StableIdNSObjectValue created in Objective-C
Opaque value 2StableValueHSValueValue created in Haskell
“No Value”()void
Scalar numberInt, Double,…C types int, double, …Conversion by FFI
1st class numberInt, DoubleNSNumber
BooleanBoolNSNumber
StringString, Text, ByteStringNSString
ContainerList, tupleNSArray
DictionaryMap k aNSDictionary

Opaque values are treated differently based on the runtime in which the values have been created: If an opaque NSObject value was created in the Objective-C runtime it will be available in Haskell as a value of type StableId. Analogically, an arbitrary Haskell value can be wrapped as a StableValue and exported to Objective-C. In Objective-C this Haskell value is then available as an object of the HSValue class. This is just a proxy for the FFI StablePtr: the implementation of the HSValue class deals with all the creating and freeing of the StablePtr such that the user does not need to bother with this.

This special treatment of opaque values is necessary exactly because they are opaque, meaning that neither the Haskell nor the Objective-C runtime can build native representations of values that were initially created in the foreign runtime. Therefore they have to be wrapped in the target runtime. For all the other data structures the OBJC typeclass has enough information to construct new native representations from the original values in the target runtime.

Function or method return types of void are now translated to the () Haskell type. This has not been tested that much, but right now it works and it is used for the target actions of Cocoa’s NSControl view elements.

I removed the instance declaration for the String Haskell type. This is just because I was getting all kinds of problems as String is just a type synonym for [Char] and this means that the instance definition for String overlaps with the instance definition for lists [a]. This problem is well known in Haskell and one can deal with it via GHC switches, like OverlappingInstances. But in the process of extending the OBJC typeclass I ran into more and more issues because of this. So I decided to drop the instance definition for String again. After all, converting Strings from and to Text values is quite easy.

A very similar problem is the Haskell representation of dictionaries. The most fundamental way would be to just translate NSDictionarys to Haskell key-value lists [(k, a)], but this once again would collide with the instance for lists. So I decided to use Map as the Haskell representation for an NSDictionary.

Next up: functions

The way to export Haskell functions to Objective-C as presented in the last posts was quite tedious:

  1. Wrap a Haskell function for accepting Ids by usage of fromId and toId.

  2. Export each function individually to C via the FFI.

  3. Provide a dummy C-implementation of each function in the Objective-C project.

But as functions are first-class values in Haskell there must be a better way to export them to Objective-C!

To make the exporting of Haskell functions easy we will define them as instances of the OBJC typeclass. As Haskell functions have no direct counterpart in Objective-C they will be similar to StableValues. Accordingly a Haskell function of one argument will be represented in Objective-C by an object of the class HSFunc1, which is a subclass of HSValue.2

The Haskell part is given as:

instance (OBJC a, OBJC b) => OBJC (a -> IOOBJC b) where
    toId f = newHSValue "HSFunc1" f'
        where 
            f' :: Id -> IOOBJC Id
            f' x' = toId =<< f =<< fromId x'

    fromId = undefined

Instances of OBJC are defined for a function that has a “wrappable” argument OBJC a and a wrappable result type OBJC b. These functions shall be executed in the IOOBJC monad.

Firstly we wrap the complete function yielding a new function f' that has the type Id -> IOOBJC Id. Then we export f' to Objective-C via newHSValue which is defined as:

newHSValue :: String -> a -> IOOBJC Id
newHSValue className val = checkNullPtr ("Could not create " ++ className ++ " object") $
                            BS.useAsCString (BS8.pack className) $ \cstr ->
                                c_newHSValue cstr =<< newStablePtr val

the corresponding C-part is:

id newHSValue(const char *name, HsStablePtr value)
{
    Class aClass = (Class)objc_getClass(name);
    id funcObj = [[aClass alloc] initWithHaskellValue:value];
    [funcObj autorelease];
    return funcObj;
}

As we see the toId function creates an (autoreleased) HSFunc1 object that handles the new StablePtr which was created by newHSValue.

HSFunc1 now provides a simple way for Objective-C to call the original Haskell function. This is done by the method callWithArg::

-(id)callWithArg:(id)arg1;
{
    return callFunc1(hsValue, arg1);
}

and the Haskell implementation of callFunc1:

callFunc1 :: (StablePtr (Id -> IOOBJC Id)) -> Id -> IO Id
callFunc1 fPtr arg = catchOBJC $ 
        do f <- liftIO $ deRefStablePtr fPtr
           f arg

With these definitions we can retrieve Haskell functions from Objective-C very easily and call them with Objective-C arguments getting back Objective-C values as results, for example:

NSArray *results = [getMethod(controller, @"lengthOfStrings") callWithArg:inputArray];

Here controller is a Haskell controller (wrapped in an HSValue) and the function getMethod returns the Haskell function lengthOfStrings wrapped as an HSFunc1 value which now can be called with callWithArg:.

I have not yet implemented the fromId part of the function instance of OBJC. This is just because there was no need for this backward conversion, but the implementation should be quite straightforward.

Towards a Haskell Controller

With the new instances of OBJC for wrapping arbitrary Haskell values and functions it is now possible to build a real Haskell controller.

The connection of the Haskell controller to the view part of a Cocoa application will be managed by a Objective-C controller proxy layer. This controller proxy will interact with the Haskell controller via a small C interface:

  • HSValue *initController(NSDictionary *ivars): This creates the Haskell controller value and returns it as an HSValue to Objective-C. The dictionary ivars contains the data members of the calling Objective-C controller proxy by name. It can be used by the Haskell controller to access other Objective-C objects, like e.g. the view elements (see below!).

  • NSArray *getMethodNames(HSValue *controller): Returns the names of all exposed “methods” of the Haskell controller.

  • id getMethod(HSValue *controller, NSString *methodName): Returns the controller method for a given name. This will be an object of the class HSFunc1, HSFunc2, …

These are the only functions the Haskell controller has to provide in order to communicate with the Objective-C part of the application.

With this interface a typical way to implement a Cocoa controller in Haskell would be as follows:

  1. Define a proxy Objective-C controller and bind all necessary view elements (and other needed Objective-C objects) to it by using IBOutlets in the “Interface Builder”.

  2. Initialize the Haskell controller in the awakeFromNib method of the Objective-C controller proxy. The bound IBOutlets are provided to the Haskell controller by the ivars dictionary.3

  3. Bind Haskell functions to the view elements.

The last step now looks something like this:

"numberSlider" `connect` presentNumbers

This connects the target and action of the numberSlider view element to a Haskell function presentNumbers. It is of type Action4 and given as:

presentNumbers :: Action
presentNumbers sender =
    do val <- sender # objectValue     -- luckily "objectValue" delivers a NSNumber!
       let x = sqrt val       :: Double
       let y = (round val)^2  :: Int
       outlet "number_doubleValue"  >>= setObjectValue x
       outlet "number_integerValue" >>= setObjectValue y

This corresponds to the original Objective-C version from the last blog entry:

- (IBAction)newNumberFromSlider:(id)sender;
{
    NSNumber *value = [NSNumber numberWithDouble:[(NSSlider*)sender doubleValue]];

    // double
    NSNumber *doubleRes = doubleTest(value);
    [number_doubleValue setDoubleValue:[doubleRes doubleValue]];

    // integer
    NSNumber *intRes = integerTest(value);
    [number_integerValue setIntValue:[intRes intValue]];
}

By defining the complete action-target of the numberSlider in Haskell we do not have to export the functions doubleTest and integerTest anymore. All the Haskell calculation can be done directly in the Haskell function!

The controller itself is just a plain Haskell data type, given for example as:

data Controller = Controller { 
                    ctrOutlets :: OutletTable,
                    ctrMethods :: MethodTable,

                    ctrModel   :: Model
                  }

Connecting a Model

In the example application I have created a very simple model:

data Model = Model {
                mdSimpleName    :: IORef T.Text,
                mdStringsLength :: IORef Answer  -- type Answer = [(Int, T.Text)]
             }

I use IORefs for storing mutable data of the application. This data has to be stored somewhere as in the test application the user can e.g. enter some text and then later retrieve this text again by pressing a button. So we are dealing with two separate events that are triggered by the user and the application has to store this data between the two user-driven events.

Dealing with IORefs seems to be not the classic Haskell solution, but somewhere we have to store this information. We could pass a new data structure back to the controller and avoid dealing with mutable data in the model, but then the controller has to keep this information. Now the controller could pass this new information back to the Objective-C controller proxy and leave it to this object to store a new HSValue value. This way we would avoid dealing with mutable data in the Haskell part at all, but this is only some kind of passing the buck. I think it’s best to handle mutable data in the model directly, because after all the model should manage the data (and state!) of the application. Another nice side effect of this is that this way the controller is a non-mutable data structure.

The model itself is completely separated from the view and only interacts with the controller. This means that the model only operates on Haskell values: there is no reference to Objective-C objects in the model implementation. All the conversion is done by the controller or implicitly by the OBJC typeclass. This is a quite nice abstraction and means that we could use the very same model and plug it for example to a GTK+ GUI.

Conclusion

By mixing Haskell and Objective-C in a Cocoa application we have a very pure implementation of the MVC pattern: a stateful view, a stateless controller layer, and a stateful model. Because of being forced to make clear cuts between the individual parts the underlying pattern is visible much more clearly. For example the model only handles Haskell values like Text and does not know about NSStrings.

The test application can be found on github.




  1. Previous articles: “Curry’n’C Converter — Using Haskell with Objective-C in the Classic Cocoa Tutorial” and “A small Haskell / Objective-C Interface”.

  2. In this post I will only discuss functions of one argument. In the source however, you can find also the definition of HSFunc2 which is for two-argument functions.

  3. An implementation of a method - (NSDictionary*)ivarDictionary to get the ivars of the controller is provided in the example project HSOBJC_Test

  4. Action is defined as type Action = StableId -> IOOBJC (): For a given sender (StableId) it performs some IOOBJC actions and returns nothing, because the Objective-C actions return void.