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 Structure | Haskell Representation | Objective-C Representation | Remarks |
---|---|---|---|
Opaque value 1 | StableId | NSObject | Value created in Objective-C |
Opaque value 2 | StableValue | HSValue | Value created in Haskell |
“No Value” | () | void | |
Scalar number | Int , Double ,… | C types int , double , … | Conversion by FFI |
1st class number | Int , Double | NSNumber | |
Boolean | Bool | NSNumber | |
String | String , Text , ByteString | NSString | |
Container | List, tuple | NSArray | |
Dictionary | Map k a | NSDictionary |
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 String
s 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 NSDictionary
s 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:
Wrap a Haskell function for accepting
Id
s by usage offromId
andtoId
.Export each function individually to C via the FFI.
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 StableValue
s. 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 anHSValue
to Objective-C. The dictionaryivars
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 classHSFunc1
,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:
Define a proxy Objective-C controller and bind all necessary view elements (and other needed Objective-C objects) to it by using
IBOutlet
s in the “Interface Builder”.Initialize the Haskell controller in the
awakeFromNib
method of the Objective-C controller proxy. The boundIBOutlet
s are provided to the Haskell controller by theivars
dictionary.3Bind 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 Action
4 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 IORef
s 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 IORef
s 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 NSString
s.
The test application can be found on github.
Previous articles: “Curry’n’C Converter — Using Haskell with Objective-C in the Classic Cocoa Tutorial” and “A small Haskell / Objective-C Interface”. ↩
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. ↩An implementation of a method
- (NSDictionary*)ivarDictionary
to get theivars
of the controller is provided in the example project HSOBJC_Test ↩Action
is defined astype Action = StableId -> IOOBJC ()
: For a given sender (StableId
) it performs someIOOBJC
actions and returns nothing, because the Objective-C actions returnvoid
. ↩