Using higher-order Haskell types in C# -
how can use , call haskell functions higher-order type signatures c# (dllimport), like...
double :: (int -> int) -> int -> int -- higher order function typeclassfunc :: ... -> maybe int -- type classes data mydata = foo | bar -- user data type datafunc :: ... -> mydata what corresponding type signature in c#?
[dllimport ("libhsdlltest")] private static extern ??? foo( ??? ); additionally (because may easier): how can use "unknown" haskell types within c#, can @ least pass them around, without c# knowing specific type? important functionality need right know pass around type class (like monad or arrow).
i know how compile haskell library dll , use within c#, first-order functions. i'm aware of stackoverflow - call haskell function in .net, why isn't ghc available .net , hs-dotnet, didn't find documentation , samples (for c# haskell direction).
i'll elaborate here on comment on fuzxxl's post.
examples posted possible using ffi. once export functions using ffi can you've figured out compile program dll.
.net designed intention of being able interface c, c++, com, etc. means once you're able compile functions dll, can call (relatively) easy .net. i've mentioned before in other post you've linked to, keep in mind calling convention specify when exporting functions. standard in .net stdcall, while (most) examples of haskell ffi export using ccall.
so far limitation i've found on can exported ffi polymorphic types, or types not applied. e.g. other kind * (you can't export maybe can export maybe int instance).
i've written tool hs2lib cover , export automatically of functions have in example. has option of generating unsafe c# code makes pretty "plug , play". reason i've choosen unsafe code because it's easier handle pointers with, in turn makes easier marshalling datastructures.
to complete i'll detail how tool handles examples , how plan on handling polymorphic types.
- higher order functions
when exporting higher order functions, function needs changed. higher-order arguments need become elements of funptr. they're treated explicit function pointers (or delegates in c#), how higher orderedness typically done in imperative languages.
assuming convert int cint type of double transformed
(int -> int) -> int -> int into
funptr (cint -> cint) -> cint -> io cint these types generated wrapper function (doublea in case) exported instead of double itself. wrapper functions maps between exported values , expected input values original function. io needed because constructing funptr not pure operation.
1 thing remember way construct or dereference funptr statically creating imports instruct ghc create stubs this.
foreign import stdcall "wrapper" mkfunptr :: (cint -> cint) -> io (funptr (cint -> cint)) foreign import stdcall "dynamic" dynfunptr :: funptr (cint -> cint) -> cint -> cint the "wrapper" function allows create funptr , "dynamic" funptr allows 1 deference one.
in c# declare input intptr , use marshaller helper function marshal.getdelegateforfunctionpointer create function pointer can call, or inverse function create intptr function pointer.
also remember calling convention of function being passed argument funptr must match calling convention of function argument being passed to. in other words, passing &foo bar requires foo , bar have same calling convention.
- user datatypes
exporting user datatype quite straight forward. every datatype needs exported storable instance has created type. instances specifies marshalling information ghc needs in order able export/import type. among other things need define size , alignment of type, along how read/write pointer values of type. partially use hsc2hs task (hence c macros in file).
newtypes or datatypes one constructor easy. these become flat struct since there's 1 possible alternative when constructing/destructing these types. types multiple constructors become union (a struct layout attribute set explicit in c#). need include enum identify construct being used.
in general, datatype single defined as
data single = single { sint :: int , schar :: char } creates following storable instance
instance storable single sizeof _ = 8 alignment _ = #alignment single_t poke ptr (single a1 a2) = a1x <- tonative a1 :: io cint (#poke single_t, sint) ptr a1x a2x <- tonative a2 :: io cwchar (#poke single_t, schar) ptr a2x peek ptr = a1' <- (#peek single_t, sint) ptr :: io cint a2' <- (#peek single_t, schar) ptr :: io cwchar x1 <- fromnative a1' :: io int x2 <- fromnative a2' :: io char return $ single x1 x2 and c struct
typedef struct single single_t; struct single { int sint; wchar_t schar; } ; the function foo :: int -> single exported foo :: cint -> ptr single while datatype multiple constructor
data multi = demi { mints :: [int] , mstring :: string } | semi { semi :: [single] } generates following c code:
enum listmulti {cmultidemi, cmultisemi}; typedef struct multi multi_t; typedef struct demi demi_t; typedef struct semi semi_t; struct multi { enum listmulti tag; union multiunion* elt; } ; struct demi { int* mints; int mints_size; wchar_t* mstring; } ; struct semi { single_t** semi; int semi_size; } ; union multiunion { struct demi var_demi; struct semi var_semi; } ; the storable instance relatively straight forward , should follow easier c struct definition.
- applied types
my dependency tracer emit for type maybe int dependency on both type int , maybe. means, when generating storable instance maybe int head looks
instance storable int => storable (maybe int) that is, aslong there's storable instance arguments of application type can exported.
since maybe a defined having polymorphic argument just a, when creating structs, type information lost. structs contain void* argument, have manually convert right type. alternative cumbersome in opinion, create specialized structs aswell. e.g. struct maybeint. amount of specialized structures generated normal module can explode way. (might add flag later on).
to ease loss of information tool export haddock documentation found function comments in generated includes. place original haskell type signature in comment well. ide present these part of intellisense (code compeletion).
as of these examples i've ommited code .net side of things, if you're interested in can view output of hs2lib.
there few other types need special treatment. in particular lists , tuples.
- lists need passed size of array marshall from, since we're interfacing unmanaged languages size of arrays not implicitly known. conversly when return list, need return size of list.
tuples special build in types, in order export them, have first map them "normal" datatype, , export those. in tool done untill 8-tuples.
- polymorphic types
the problem polymorphic types e.g. map :: (a -> b) -> [a] -> [b] size of a , b not know. is, there's no way reserve space arguments , return value since don't know are. plan support allowing specify possible values a , b , create specialized wrapper function these types. on other size, in imperative language use overloading present types you've chosen user.
as classes, haskell's open world assumption problem (e.g. instance can added time). @ time of compilation statically known list of instances available. intend offer option automatically export specialized instances possible using these list. e.g. export (+) exports specialized function known num instances @ compile time (e.g. int, double, etc).
the tool rather trusting. since can't inspect code purity, trust programmer honest. e.g. don't pass function has side-effects function expects pure function. honest , mark higher-ordered argument being impure avoid problems.
i hope helps, , hope wasn't long.
update : there's of big gotcha i've discovered. have remember string type in .net immutable. when marshaller sends out haskell code, cwstring there copy of original. have free this. when gc performed in c# won't affect the cwstring, copy.
the problem when free in haskell code can't use freecwstring. pointer not allocated c (msvcrt.dll)'s alloc. there 3 ways (that know of) solve this.
- use char* in c# code instead of string when calling haskell function. have pointer free when call returns, or initialize function using fixed.
- import cotaskmemfree in haskell , free pointer in haskell
- use stringbuilder instead of string. i'm not entirely sure one, idea since stringbuilder implemented native pointer, marshaller passes pointer haskell code (which can update btw). when gc performed after call returns, stringbuilder should freed.
Comments
Post a Comment