fpco / inline-c

284 stars 49 forks source link

Improve C++ support #13

Open bitonic opened 9 years ago

bitonic commented 9 years ago

The main things to do are:

I have some ideas for both, currently experimenting.

mboes commented 9 years ago

Writing down those ideas would help others pick this up if they want to.

chpatrick commented 9 years ago

An idea for template support:

Anything with template polymorphism would be generated with an explicit function. This function would get a templated C++ implementation and a Haskell typeclass. You could then request specializations of this function for given Haskell types, which would produce typeclass instances.

Sketch:

data Vector a

[temp| {class PushBack a}
template <class $a>
void pushBack(const vector<$a> *foo), $a val) {
  foo->push_back(val);
} |]

[tempInst| pushBack<int> |]

[tempInst| pushBack<char*> |]

... generates Haskell

class PushBack a where
  pushBack :: Ptr (Vector a) -> a -> IO ()

foreign import ccall inline_c_pushBack_cInt ...

instance PushBack CInt where
  pushBack = inline_c_pushBack_cInt

foreign import ccall inline_c_pushBack_cChar_ptr ...

instance PushBack (Ptr CChar) where
  pushBack = inline_c_pushBack_cChar_ptr

and C++

template <class inline_c_a>
void pushBack(vector<inline_c_a> *foo), inline_c_a val) {
  foo->push_back(val);
}

extern "C" {
  static void inline_c_pushBack_cInt(vector<int> *foo, int val) {
    pushBack<int>(foo, val);
  }

}

etc..
chpatrick commented 9 years ago

Actually you could just use a regular block instead of a function, and you could share classes:

pushBack :: VectorImpl a => Ptr (Vector a) -> a -> IO ()
pushBack vec val =
  [block| <class VectorImpl a> void { 
    $(vector<$type:a> *vec) ->push_back($($type:a val));
  |]

resize :: VectorImpl a => Ptr (Vector a) -> Int -> IO ()
resize vec size =
  [block| <class VectorImpl a> void { 
    $(vector<$type:a> *vec) ->resize($(int val));
  |]

destruct :: VectorImpl a => Ptr (Vector a) -> IO ()
destruct vec =
  [block| <class VectorImpl a> void { 
    delete $(vector<$type:a> *vec);
  |]

[tempImpl| VectorImpl CInt |]

produces:

class VectorImpl a where
  inline_c_pushBack_32193293 :: Ptr (Vector a) -> a -> IO ()
  inline_c_resize_3232112131 :: Ptr (Vector a) -> Int -> IO ()
  inline_c_destruct_43244234 :: Ptr (Vector a) -> IO ()

instance VectorImpl CInt where
  ...
bitonic commented 9 years ago

Yes, that makes a lot of sense, thanks a lot for the input. I especially like the second mockup of how it would look like.

There is the slight disadvantage of an upfront setup, but I don't think we can do much better than that.

I fear that the hardest part of this will be parsing C++ types...

chpatrick commented 9 years ago

Some further thoughts:

It would be good if external modules could generate implementations of templating classes (eg. VectorImpl above). This means they need access to the source of the templated C++ functions. It would be quite a pain to include the generated code from another module, so as a (slightly out there) alternative I propose that we encode the necessary information as an instance of a type family on these template typeclasses. This could then be recovered in TH in the external module and included with the template specializations.

data ClassImpl str = ClassImpl { templateSources :: [ ( str, str ) ] }
type family Template (cls :: k) :: ClassImpl Symbol
type instance Template VectorImpl
  = 'ClassImpl
    '[ '( "inline_c_pushBack_0", "template<class T>..." )
     ]

Or perhaps when we produce a templating class, eg. VectorImpl, we could also produce a function createVectorImpl :: Name -> DecsQ that could be called in other modules when specialization is required. Or have an intermediate step and have functions:

defVectorImpl :: Name -> TemplateClassInfo -- generated with VectorImpl
tempImpl :: TemplateClassInfo -> DecsQ -- in inline-c

and then it's tempImpl $ defVectorImpl ''CInt at the use site.