Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I encapsulate object constructors and destructors in haskell

Tags:

c++

c

haskell

I have Haskell code which needs to interface with a C library somewhat like this:

// MyObject.h
typedef struct MyObject *MyObject;
MyObject newMyObject(void);
void myObjectDoStuff(MyObject myObject);
//...
void freeMyObject(MyObject myObject);

The original FFI code wraps all of these functions as pure functions using unsafePerformIO. This has caused bugs and inconsistencies because the sequencing of the operations is undefined.

What I am looking for is a general way of dealing with objects in Haskell without resorting to doing everything in IO. What would be nice is something where I can do something like:

myPureFunction :: String -> Int
-- create object, call methods, call destructor, return results

Is there a nice way to achieve this?

like image 332
grasevski Avatar asked Nov 19 '13 04:11

grasevski


People also ask

Is destructor and deconstructor same?

Constructor helps to initialize the object of a class. Whereas destructor is used to destroy the instances.

What is deconstructor in OOP?

A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() .

Can destructors be parameterized?

It never takes any parameters, and it never returns anything. You can't pass parameters to the destructor anyway, since you never explicitly call a destructor (well, almost never).

What are destructor when they are called and what is their utility?

A destructor is called for a class object when that object passes out of scope or is explicitly deleted. A destructor is a member function with the same name as its class prefixed by a ~ (tilde). For example: class X { public: // Constructor for class X X(); // Destructor for class X ~X(); };


2 Answers

The idea is to keep passing a baton from each component to force each component to be evaluated in sequence. This is basically what the state monad is (IO is really a weird state monad. Kinda).

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.State

data Baton = Baton -- Hide the constructor!

newtype CLib a = CLib {runCLib :: State Baton a} deriving Monad

And then you just string operations together. Injecting them into the CLib monad will mean they're sequenced. Essentially, you're faking your own IO, in a more unsafe way since you can escape.

Then you must ensure that you add construct and destruct to the end of all CLib chains. This is easily done by exporting a function like

clib :: CLib a -> a
clib m = runCLib $ construct >> m >> destruct

The last big hoop to jump through is to make sure that when you unsafePerformIO whatever's in construct, it actually gets evaluated.


Frankly, this is all kinda pointless since it already exists, battle proven in IO. Instead of this whole elaborate process, how about just

construct :: IO Object
destruct  :: IO ()
runClib :: (Object -> IO a) -> a
runClib = unsafePerformIO $ construct >>= m >> destruct

If you don't want to use the name IO:

newtype CLib a = {runCLib :: IO a} deriving (Functor, Applicative, Monad)
like image 75
Daniel Gratzer Avatar answered Nov 14 '22 21:11

Daniel Gratzer


My final solution. It probably has subtle bugs that I haven't considered, but it is the only solution so far which has met all of the original criteria:

  • Strict - all operations are sequenced correctly
  • Abstract - the library is exported as a stateful monad rather than a leaky set of IO operations
  • Safe - the user can embed this code in pure code without using unsafePerformIO and they can expect the result to be pure

Unfortunately the implementation is a bit complicated.

E.g.

// Stack.h
typedef struct Stack *Stack;
Stack newStack(void);
void pushStack(Stack, int);
int popStack(Stack);
void freeStack(Stack);

c2hs file:

{-# LANGUAGE ForeignFunctionInterface, GeneralizedNewtypeDeriving #-}
module CStack(StackEnv(), runStack, pushStack, popStack) where
import Foreign.C.Types
import Foreign.Ptr
import Foreign.ForeignPtr
import qualified Foreign.Marshal.Unsafe
import qualified Control.Monad.Reader
#include "Stack.h"
{#pointer Stack foreign newtype#}

newtype StackEnv a = StackEnv
 (Control.Monad.Reader.ReaderT (Ptr Stack) IO a)
 deriving (Functor, Monad)

runStack :: StackEnv a -> a
runStack (StackEnv (Control.Monad.Reader.ReaderT m))
 = Foreign.Marshal.Unsafe.unsafeLocalState $ do
  s <- {#call unsafe newStack#}
  result <- m s
  {#call unsafe freeStack#} s
  return result

pushStack :: Int -> StackEnv ()
pushStack x = StackEnv . Control.Monad.Reader.ReaderT $
 flip {#call unsafe pushStack as _pushStack#} (fromIntegral x)

popStack :: StackEnv Int
popStack = StackEnv . Control.Monad.Reader.ReaderT $
 fmap fromIntegral . {#call unsafe popStack as _popStack#}

test program:

-- Main.hs
module Main where
import qualified CStack
main :: IO ()
main = print $ CStack.runStack x where
 x :: CStack.StackEnv Int
 x = pushStack 42 >> popStack

build:

$ gcc -Wall -Werror -c Stack.c
$ c2hs CStack.chs
$ ghc --make -Wall -Werror Main.hs Stack.o
$ ./Main
42
like image 29
grasevski Avatar answered Nov 14 '22 23:11

grasevski