Difference between revisions of "User:JRV"

From HaskellWiki
Jump to navigation Jump to search
(Got rid of draft tutorial)
 
Line 1: Line 1:
I'm developing a tutorial on this page (slowly). I'm keeping it here so I can check out
 
format, figures, etc. before putting the whole think in the Tutorial area.
 
 
Very rough draft:
 
--------------------------------------------------------------
 
In the interest of full disclosure—I'm not an old time Mac person. I've
 
had my Mac for two years. I only decided to learn Objective-C and Cocoa
 
after I explored doing an application using Python and Qt, and someone in
 
the Python community said “Objective-C is easy. Why don't you write a real
 
Mac application”. Neither am I a Haskell expert.
 
 
== What not, what, and why? ==
 
This is not the [[Missing_tutorials|missing tutorial]]
 
''Haskell for MacOS fans''. It is a description of how to add a Haskell module (callable from C)
 
to an Xcode/Cocoa/Interface builder project on your Mac.
 
 
Why? Well, to paraphrase [[Missing_tutorials|Haskell for MacOs fans]],
 
this provides a way to use Haskell to create the ''Models'' in the Apple
 
Model–View–Controller design pattern while using Xcode/Interface
 
Builder/Cocoa to build “insanely great Mac applications”. Modeling that
 
immediately comes to mind includes Parsing, and information visualization
 
(see [http://themonadreader.wordpress.com The Monad Reader, 14]).
 
 
== Overview of the process ==
 
The “how to” consists of the following steps.
 
 
# Develop and test Haskell module, callable from C.
 
# Build an Xcode project with a dummy c file for the Haskell file.
 
# Add Haskell .h and .o files to your Xcode project.
 
# Add dependency required Haskell libraries to your Xcode project.
 
# Resolve naming conflicts between Haskell libraries and other libraries.
 
 
Steps 1 and 2 can be done in either order. Step 4 should be easy, using
 
facilities of GHC and/or Cabal, but I haven't been able to make these work.
 
I show how to do this iteratively. This is a bit tedious, but not bad if
 
you don't plan on changing your Haskell code frequently.
 
 
[[image:JRV_CocoaHaFib_after.png|thumb|350px|right]]
 
Here is a screen shot of the upper left corner of the Mac screen, including
 
the app menu (generated by Interface builder) and the application window
 
itself. When the user types a number in the entry box, the line below changes to
 
give the corresponding Fibonacci Number.
 
 
== The Haskell module ==
 
For this test I used the same code as used in
 
[[Calling_Haskell_from_C|Calling Haskell from C]], with some slight
 
modifications. Here is the Haskell code, in a file called ''FibTest.hs''.
 
 
<haskell>
 
 
{-# LANGUAGE ForeignFunctionInterface #-}
 
 
module FibTest where
 
 
import Foreign.C.Types
 
 
fibonacci :: Int -> Int
 
fibonacci n = fibs !! n
 
where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
 
 
fibonacci_hs :: CInt -> CInt
 
fibonacci_hs = fromIntegral . fibonacci . fromIntegral
 
 
foreign export ccall fibonacci_hs :: CInt -> CInt
 
 
</haskell>
 
 
Compile this with ghc, viz:
 
 
$ ghc -c -O FibTest.hs
 
 
This produces the following files that we will import into Xcode:
 
 
* ''FibTest.o''
 
* ''FibTest_stub.h''
 
* ''FibTest_stub.o''
 
 
and the following files that we won't need:
 
 
* ''FibTest_stub.c''
 
* ''FibTest.hi''
 
 
== Import into Xcode project ==
 
First we start an Xcode project in the usual way. I started this as a
 
plain Cocoa application. I called my application CocoaHaskellFib.
 
 
Several steps can be done in any order:
 
 
=== Copy Files ===
 
FibTest.o, FibTest_stub.h, and FibTest_stub.o into the folder where
 
you have saved your CocoaHaskellFib project.
 
 
Then add them to the project by Project ➝ Add To Project .
 
 
=== Create application class ===
 
Next, create a Cocoa Objective-C class using the Xcode menu, File→New.
 
I named mine CocoaFib. Xcode will create (bare bones )both an interface
 
file (.h) and an implementation file (.m) for you to add code to.
 
 
Here is my interface file (.h file), after adding outlets and a method
 
declaration:
 
 
<pre-c>
 
#import <Cocoa/Cocoa.h>
 
 
@interface CocoaFib : NSObject {
 
IBOutlet NSTextField *integerInput;
 
IBOutlet NSTextField *fibOutput;
 
IBOutlet NSTextField *forNis;
 
 
}
 
 
-(IBAction)generate:(id)sender;
 
 
@end
 
</pre-c>
 
 
The three outlets are for the user input, fibonacci number output, and the
 
number following “n =” in the label.
 
 
It's worth noting that the three outlets are connected to their respective
 
parts of the user interface by dragging in Interface Builder.
 
 
Here is my implementation code (.m file), after adding some includes,
 
the ''generate'' method, and a call to hs_exit in the ''dealloc'' method:
 
 
<pre-c>
 
 
#import "CocoaFib.h"
 
#include "HsFFI.h"
 
#include "FibTest_stub.h"
 
 
@implementation CocoaFib
 
 
-(IBAction)generate:(id)sender{
 
int i = [intValue integerInput];
 
int j = fibonacci_hs(i);
 
[setIntValue:i forNis];
 
[setIntValue:j fibOutput];
 
}
 
 
-(void)dealloc{
 
hs_exit();
 
[dealloc super];
 
}
 
 
@end
 
</pre-c>
 
 
=== Modify main.m ===
 
When creating a Cocoa project, Xcode automatically generates a file
 
''main.m''. Normally, one never touches this file. However, when using
 
a Haskell module, one needs to initialize the Haskell run-time by calling
 
''hs_init''. Normally, I would do this in an ''init'' call of
 
''CocoaFib.m'', rather than in ''main.m'', but since ''hs_init'' needs an
 
''argc'' and ''argv'', and since ''main.m'' already has them hanging
 
around, I took the easy way out.
 
 
Here is my ''main.m''
 
 
<pre-c>
 
#import <Cocoa/Cocoa.h>
 
#include "FibTest_stub.h"
 
 
int main(int argc, char *argv[])
 
{
 
hs_init(&argc, &argv);
 
return NSApplicationMain(argc, (const char **) argv);
 
}
 
 
</pre-c>
 
 
The only changes to the main.m provided by Xcode are the addition of
 
''include "FibTest_stub.h"'' and the call to ''hs_init''.
 
 
== Add Haskell libraries, compile and run ==
 
This is the easy part. Just kidding. It should be easy, if it were easy
 
to make a library or executable containing ''FibTest.o'',
 
''FibTest_stub.o'' and all their dependencies. Unfortunately I've been
 
unable to do that, in spite of spending most of my spare time for a week
 
trying, and posting questions on ''Haskell-cafe''.
 
 
Anyway, here is how I did it. If you have a better way, here is a good
 
place to edit this tutorial!
 
 
=== Build and go, the first time ===
 
First, I added ''libffi.a'' to my project for good measure.
 
 
Then select ''Build and go'' from the menu or the toolbar. This will result
 
something like 26 failures, all due to undefined symbols.
 
 
What to do?
 
 
What I did was go to the ''…/usr/lib/ghc-6.10.4'' directory of my installation,
 
and run:
 
 
<pre>
 
find . -name "lib*.a" | xargs nm > ~/develop/haskellLibInfo/libInfo
 
</pre>
 
This results in a file with a list of all the available symbols from all
 
the libraries in the Haskell installation.
 
 
Entries look something like this:
 
 
<pre>
 
./array-0.2.0.0/libHSarray-0.2.0.0.a(Base__1.o):
 
U _arrayzm0zi2zi0zi0_DataziArrayziBase_STUArray_con_info
 
000000b0 D _arrayzm0zi2zi0zi0_DataziArrayziBase_zdWSTUArray_closure
 
0000009c T _arrayzm0zi2zi0zi0_DataziArrayziBase_zdWSTUArray_info
 
00000090 t _arrayzm0zi2zi0zi0_DataziArrayziBase_zdWSTUArray_info_dsp
 
00000078 t _s7RK_info
 
00000070 t _s7RK_info_dsp
 
 
 
</pre>
 
 
Entries with a flag T (for text) are code. U of course indicates
 
undefined, and is no help to us. D indicates data, and is used (if I
 
understood correctly) for defined global constants.
 
 
OK. So what do you do with this?
 
 
Your Xcode failure output will say something like:
 
 
<pre>
 
"_newCAF", referenced from:
 
_FibTest_a3_info in FibTest.o
 
"_base_GHCziBase_plusInt_closure", referenced from:
 
_FibTest_a3_info in FibTest.o
 
 
 
</pre>
 
 
Now go look in your ''libInfo'' file (or whatever you called it)
 
and search for ''T _newCAF''. It is in
 
''libHSrts.a''.
 
 
Now go back to Xcode. In a Finder window, locate the file ''libHSrts.a''.
 
Drag ''libHSrts.a'' into the ''Groups and Files'' pane of the Xcode window.
 
 
In Xcode, this doesn't move the file, it provides a reference to it for the
 
linker. You will get a dialog asking if you want to add the file to the
 
project, and you'll be given an opportunity to copy the file into the
 
project. Add the file to the project, but it is not necessary to copy it.
 
 
Now when you redo ''Build and go'', you will no doubt get even more
 
failures due to undefined symbols.
 
 
But don't despair, iterate.
 
 
=== Iterate ===
 
After a few interations of adding a library containing missing
 
symbols followed by ''Build and go'' you'll get a successful build.
 
 
Well, except I had one further problem.
 
 
=== Resolve name conflicts of Haskell libraries ===
 
I have a bunch of libgmp.a, libgmp.so, and libgmp.dylib on my machine, in
 
addition to the one in ''…/usr/lib/ghc-6.10.4''.
 
I don't know where they all came from. Xcode linking tries to
 
use the ones early in its search path, and they don't work with Haskell.
 
 
To get around this, I made a symbolic link in the ''usr/lib/ghc-6.10.4''
 
directory as follows:
 
 
<pre>
 
ln -s libgmp.a lib-h-gmp.a
 
</pre>
 
and added lib-h-gmp.a to my project.
 
 
== That's all there is to it ==
 
Well, its a bit tedious, but consider the following:
 
 
Once you import this into your Xcode project, with all the attendant adding
 
of .a files, you shouldn't have to change any of the .a file additions
 
again. Now you can concentrate on the View and Control part of your
 
project.
 
 
== To do ==
 
The biggest ''to do'' is to generate a single library or .o file that
 
includes all the dependencies. There should be a way to do this, but I
 
haven't been able to get it to work. I'll continue to try. Any help will
 
be appreciated.
 
 
One might want to write an Objective-C wrapper for your haskell module.
 
This would encapsulate the module as an Objective-C class, and each
 
function call would be a method. I'm not sure why you would want to do
 
this generally. If your Haskell module was managing some persistent data
 
store it might make sense.
 

Latest revision as of 16:22, 2 November 2009