2016-04-08 9 views
4

Я потратил много времени на кодирование инвариантов в своих типах данных, и теперь я работаю над тем, чтобы разоблачить мою библиотеку на C через FFI. Вместо структур данных маршала по языковому барьеру я просто использую непрозрачные указатели, чтобы C мог создавать АСТ, а затем eval. Haskell нужно только перевести строку на C.Typesafe StablePtrs

Вот какой код лучше освещает.

-- excerpt from Query.hs 
data Sz = Selection | Reduction deriving Show 

-- Column Datatype 

data Column (a :: Sz) where 
    Column :: String -> Column Selection 
    BinExpr :: BinOp -> Column a -> Column b -> Column (OpSz a b) 
    AggExpr :: AggOp -> Column Selection -> Column Reduction 

type family OpSz (a :: Sz) (b :: Sz) where 
    OpSz Selection Selection = Selection 
    OpSz Selection Reduction = Selection 
    OpSz Reduction Selection = Selection 
    OpSz Reduction Reduction = Reduction 

data Query (a :: Sz) where 
    ... etc 


-- excerpt from Export.hs 

foreign export ccall "selection" 
    select :: StablePtr [Column a] -> StablePtr (Query b) -> IO (StablePtr (Query Selection)) 

foreign export ccall 
    add :: StablePtr (Column a) -> StablePtr (Column b) -> IO (StablePtr (Column (OpSz a b))) 

foreign export ccall 
    mul :: StablePtr (Column a) -> StablePtr (Column b) -> IO (StablePtr (Column (OpSz a b))) 

foreign export ccall 
    eval :: StablePtr (Query Selection) -> IO CString 

Насколько я могу судить, однако, это то, что это, кажется, выбрасывает тип безопасности из окна. По сути дела, что C отдает Haskell, предполагается, что этот тип полностью отрицает причину, по которой я написал dsl в Haskell. Есть ли способ получить преимущества использования StablePtr и сохранить тип безопасного? Последнее, что я хочу, это повторить инварианты в C.

+0

C, вероятно, не имеет достаточно прочной системы для проверки любой части вашего DSL. Может быть, C++ шаблоны могли бы это сделать .. но это кошмар. Я думаю, вы должны признать, что функция * может * быть вызвана неправильно на стороне C и проверить на стороне Haskell, что это было во время выполнения. Конечно, вы все равно можете получить информацию о типе во время выполнения (например, с помощью «Тип»), чтобы вы все еще могли использовать ваши хорошо типизированные функции Haskell (после того, как вы правильно набрали ввод из C). – user2407038

ответ

1

Соответствие C StablePtr a является typedef для void * - защита от потери на границе FFI.

Проблема в том, что существует бесконечное число возможностей для a :: *, поэтому для StablePtr a. Кодирование этих типов в C, которое имеет систему ограниченного типа (без параметрических типов!), Не может быть выполнено, если не прибегать к очень унииоматическим типам C (см. Ниже).

В вашем конкретном случае, a, b :: Sz, поэтому у нас есть только конечное число случаев, и некоторый инструмент FFI может помочь в кодировании этих случаев. Тем не менее, это может привести к взрыву комбинаторного случаев:

typedef struct HsStablePtr_Selection_ { void *p; } HsStablePtr_Selection; 
typedef struct HsStablePtr_Reduction_ { void *p; } HsStablePtr_Reduction; 

HsStablePtr_Selection 
add_Selection_Selection(HsStablePtr_Selection a, HsStablePtr_Selection b); 
HsStablePtr_Selection 
add_Selection_Reduction(HsStablePtr_Selection a, HsStablePtr_Reduction b); 
HsStablePtr_Selection 
add_Reduction_Selection(HsStablePtr_Reduction a, HsStablePtr_Selection b); 
HsStablePtr_Reduction 
add_Reduction_Reduction(HsStablePtr_Reduction a, HsStablePtr_Reduction b); 

В С11 можно уменьшить этот беспорядок, используя type-generic expressions, , который может добавить типа «вправо» отбрасывает без комбинаторных взрыва. Тем не менее, никто не написал инструмент FFI, использующий это. Например:

void *add_void(void *x, void *y); 
#define add(x,y) \ 
    _Generic((x) , \ 
    HsStablePtr_Selection: _Generic((y) , \ 
     HsStablePtr_Selection: (HsStablePtr_Selection) add_void(x,y), \ 
     HsStablePtr_Reduction: (HsStablePtr_Selection) add_void(x,y) \ 
    ) \ 
    HsStablePtr_Reduction: _Generic((y) , \ 
     HsStablePtr_Selection: (HsStablePtr_Selection) add_void(x,y), \ 
     HsStablePtr_Reduction: (HsStablePtr_Reduction) add_void(x,y) \ 
    ) \ 
    ) 

(. Брикеты выше взяты из указателя на структуру, так что они не работают, и мы должны использовать STRUCT литералов вместо этого, но давайте игнорировать это)

В C++ мы бы богаче типы для использования, но FFI предназначен для использования C как общего lingua franca для привязки к другим языкам. (! Мономорфная)


Возможное кодирование Haskell параметрические типа может быть достигнуто, теоретически, эксплуатировать только конструкторы типа С имеет: указатели, массивы, указатели на функции, константные, летучие, ....

например, стабильный указатель на type T = Either Char (Int, Bool) может быть представлена ​​следующим образом:

typedef struct HsBool_ { void *p } HsBool; 
typedef struct HsInt_ { void *p } HsInt; 
typedef struct HsChar_ { void *p } HsChar; 
typedef struct HsEither_ HsEither; // incomplete type 
typedef struct HsPair_ HsPair;  // incomplete type 

typedef void (**T)(HsEither x1, HsChar x2 
        void (**)(HsPair x3, HsInt x4, HsBool x5)); 

конечно, с точки зрения с, типом T является грубой ложью !! значение типа T на самом деле было бы void *, указывающее на некоторое представление на стороне Haskell типа StablePtr T и, конечно же, не указатель на указатель на функцию C! Тем не менее, прохождение T вокруг сохранит безопасность типов.

Обратите внимание, что вышеупомянутое можно назвать только «кодированием» в очень слабом смысле, а именно, это инъективное отображение из мономорфных типов Haskell в типы C, полностью игнорируя семантику типов C. Это делается только для того, чтобы, если такие стабильные указатели были возвращены в Haskell, на стороне C есть проверка определенного типа.

Я использовал C неполных типов, так что вы никогда не можете вызвать эти функции в C. Я использовал указатели для указателей, поскольку указатели (IIRC) на функции не могут быть переведены в void * безопасно.

Обратите внимание, что такая сложная кодировка может использоваться в C, но может быть трудно интегрировать с другими языками. Например, Java и Haskell могут взаимодействовать с JNI + FFI, но я не уверен, что часть JNI может справиться с такой сложной кодировкой. Возможно, void * более практичен, хотя и небезопасен.

Безопасно кодирующая полиморфные функции, GADTs, классы типа ... остается для будущей работы :-P


TL; DR: ИПП может стараться кодировать статические типы в C, но это сложно, и на данный момент нет большого спроса на это. Возможно, в будущем это может измениться.

+0

Я надеялся на решение со стороны haskell, так как кажется невозможным реплицировать типы в C. Например, я мог бы сделать что-то вроде '' 'newtype QuerySelection = QS (StablePtr (Query Selection))' '' make ' QuerySelection' и другие экземпляры 'Storable', а затем маршалируют этот тип до C? –

+0

@BrandonOgle Это может работать, но вам все еще нужно много вариантов 'add' и т. Д. На стороне C, я думаю. – chi