Write or rewrite a tutorial that covers building a project that interfaces haskell backend code with Swift GUI code in an XCode 8.2 project. Bonus points if the entire thing can be built from the command line
stack buildrather than inside XCode.
Integrating Haskell with Swift Mac Apps
To start, let's create a new Xcode project:
Then cd into the directory with the .xcodeproj and create a
new stack project:
$ cd SwiftHaskell
$ stack new SwiftHaskellLibrary simple-library
Let's move these files up to the top directory, so we can
run both stack and xcodebuild from the same directory:
$ mv -vn SwiftHaskellLibrary/* .
$ rmdir SwiftHaskellLibrary
Exporting Haskell Functions to Swift
Add a foreign export function to the Haskell library.
TODO: Explain exporting using Haskell's C FFI
module Lib where
import Foreign.C
foreign export ccall square :: CInt -> CInt
square :: CInt -> CInt
square x = x * x
If we stack build now, in addition to building the library,
GHC will generate a C header file for us to include. Because
it's a build artifact, it's buried somewhat deep in the file
hierarchy, but we can ask stack where it is:
$ find "$(stack path --dist-dir)" -name Lib_stub.h
.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/Lib_stub.h
The stub header includes HsFFI.h from GHC, so we'll also need
to find the current compiler's version of that header.
$ find "$(stack path --compiler-bin)/.." -name HsFFI.h
/Users/nanotech/.stack/programs/x86_64-osx/ghc-8.0.1/bin/../lib/ghc-8.0.1/include/HsFFI.h
Let's make a script to symlink to these from a more convenient location so we don't need to change the Xcode project when the compiler or Cabal version changes:
#!/bin/sh
set -eux
DIST_DIR="$(stack path --dist-dir)"
ln -sf ../"$DIST_DIR"/build/Lib_stub.h build/
ln -sf "$GHC_LIB_DIR"/include build/
Run it, then add $(PROJECT_DIR)/build and
$(PROJECT_DIR)/build/include to the target's User Header
Search Paths in Xcode:
Now that we have the exported definitions in a header file, we
need to import them into Swift. Create a new header file in
Xcode named SwiftHaskell-Bridging-Header.h and save it in the
same directory as AppDelegate.swift. Then set it as the
Objective-C Bridging Header in Xcode:
And include the Haskell stub header:
#ifndef SwiftHaskell_Bridging_Header_h
#define SwiftHaskell_Bridging_Header_h
#include "Lib_stub.h"
#endif /* SwiftHaskell_Bridging_Header_h */
Linking
To link our Haskell library into our Swift app, we'll build it as a dynamic library and add it to our Xcode project. Building a static library is also possible, but currently requires rebuilding the GHC standard libraries.
Add a ghc-options line to the library section in the
.cabal file:
library
# ... other options
ghc-options: -threaded -dynamic -shared -fPIC -o build/libswifthaskell.dylib
-threadedenables the multithreaded GHC runtime, which is usually what you want.-dynamictells GHC to link to the dynamic versions of Haskell libraries. This is required when using-shared.-sharedbuilds a shared library.-fPICenables position-independent code, which is needed for shared libraries.
Run stack build, then drag build/libswifthaskell.dylib into
the Xcode project and add it to the SwiftHaskell target.
We'll also need to link to the RTS so we can initialize it
from Swift, and copy all shared library dependencies into the
app bundle so it's self-contained. otool -L will show what
libraries libswifthaskell.dylib depends on:
$ otool -L build/libswifthaskell.dylib
build/libswifthaskell.dylib:
@rpath/libswifthaskell.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libHSbase-4.9.0.0-ghc8.0.1.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libHSinteger-gmp-1.0.0.1-ghc8.0.1.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libHSghc-prim-0.5.0.0-ghc8.0.1.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
Let's update our previous script to locate them and symlink them into the build directory:
#!/bin/sh
set -eux
DIST_DIR="$(stack path --dist-dir)"
GHC_VERSION="$(stack exec -- ghc --numeric-version)"
GHC_LIB_DIR="$(stack path --compiler-bin)/../lib/ghc-$GHC_VERSION"
ln -sf ../"$DIST_DIR"/build/Lib_stub.h build/
ln -sf "$GHC_LIB_DIR"/include build/
ln -sf "$GHC_LIB_DIR"/rts/libHSrts_thr-ghc"$GHC_VERSION".dylib build/
# FIXME: Link the other Haskell libraries
ln -sf "$GHC_LIB_DIR"/rts/libffi.dylib build/
This is the symlink_deps.sh script in the repository.
TODO: Find shared library dependencies automatically, like
cabal-macosx.
Drag these libraries into Xcode too,
Then go to the target's Build Phases panel and add a new phase to copy the libraries into the app bundle:
Runtime Setup
Before using functions from Haskell, we'll need to start its
runtime. hs_init takes pointers to C's argc and argv. In
Swift, these are available as CommandLine.argc and
CommandLine.unsafeArgv.
Add this to your app delegate's applicationDidFinishLaunching
method:
func applicationDidFinishLaunching(_ aNotification: Notification) {
var argc = CommandLine.argc
var argv = Optional.some(CommandLine.unsafeArgv)
hs_init(&argc, &argv)
}
And the corresponding hs_exit to applicationWillTerminate:
func applicationWillTerminate(_ aNotification: Notification) {
hs_exit()
}
Calling Haskell from Swift
Add a new label to the window in MainMenu.xib for us to write
the result of our Haskell function square into, and add it as
an @IBOutlet to the AppDelegate:
@IBOutlet weak var label: NSTextField!
We already have our Haskell library's header imported, so we
can just call the exported square function. Add this to
applicationDidFinishLaunching:
label.stringValue = "\(square(5))"
And run the app:
Calling Swift from Haskell
TODO: FunPtr callbacks







