2010-07-26 14 views
5

he definido el siguiente módulo para ayudarme con la exportación función de FFI:La conversión automática de tipos de FFI llama en Haskell

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances #-} 
module ExportFFI where 

import Foreign 
import Foreign.C 


class FFI basic ffitype | basic -> ffitype where 
    toFFI :: basic -> IO ffitype 
    fromFFI :: ffitype -> IO basic 
    freeFFI :: ffitype -> IO() 

instance FFI String CString where 
    toFFI = newCString 
    fromFFI = peekCString 
    freeFFI = free 

Estoy luchando con la instancia de funciones. ¿Alguien me puede ayudar?

+0

¿Conoces [FunPtr] (http://haskell.org/ghc/docs/6.12.1/html/libraries/base-4.2.0.0/Foreign-Ptr.html#t%3AFunPtr)? De lo contrario, consulte Foreign.Ptr y su documentación relacionada. –

+0

Sí, lo sé. La idea aquí es hacer una conversión automática hacia/desde FFI. Por ejemplo, 'String -> String' se convertiría en' CString -> CString', o algo similar. – Tener

+0

¡No utilice extensiones Haskell en bibliotecas de enlaces FFI! hace que sea muy difícil compilar con otros compiladores Haskell que no son compatibles con las extensiones utilizadas. –

Respuesta

6

Hay dos cosas que puede hacer con las funciones relacionadas con el FFI: 1) Marcado: esto significa convertir una función en un tipo que se puede exportar a través del FFI. Esto se realiza por FunPtr. 2) Exportar: esto significa crear un medio para que el código no Haskell llame a una función Haskell.

Su clase FFI ayuda con la clasificación, y primero creo algunas instancias de muestra de cómo ordenar las funciones.

Esto no se ha probado, pero se compila y espero que funcione. En primer lugar, vamos a cambiar ligeramente la clase:

class FFI basic ffitype | basic -> ffitype, ffitype -> basic where 
    toFFI :: basic -> IO ffitype 
    fromFFI :: ffitype -> IO basic 
    freeFFI :: ffitype -> IO() 

Este dice que, dado el tipo de bien "básica" o "ffitype", el otro se fija [1]. Esto significa que ya no es posible agrupar dos valores diferentes en el mismo tipo, p. ya no se puede tener ambas cosas

instance FFI Int CInt where 

instance FFI Int32 CInt where 

La razón de esto es porque freeFFI no se puede utilizar como usted ha definido ella; no hay forma de determinar qué instancia seleccionar desde solo ffitype. Alternativamente, puede cambiar el tipo a freeFFI :: ffitype -> basic -> IO(), o (¿mejor?) freeFFI :: ffitype -> IO basic. Entonces no necesitarías fundeps en absoluto.

La única forma de asignar un FunPtr es con una instrucción de "importación externa", que solo funciona con tipos completamente instanciados. También necesita habilitar la extensión ForeignFunctionInterface. Como resultado, la función toFFI, que debería devolver un IO (FunPtr x), no puede ser polimórfica sobre los tipos de funciones. En otras palabras, necesitaría esto:

foreign import ccall "wrapper" 
    mkIntFn :: (Int32 -> Int32) -> IO (FunPtr (Int32 -> Int32)) 

foreign import ccall "dynamic" 
    dynIntFn :: FunPtr (Int32 -> Int32) -> (Int32 -> Int32) 

instance FFI (Int32 -> Int32) (FunPtr (Int32 -> Int32)) where 
    toFFI = mkIntFn 
    fromFFI = return . dynIntFn 
    freeFFI = freeHaskellFunPtr 

para cada tipo de función diferente que desea calcular. También necesita la extensión FlexibleInstances para esta instancia. Hay algunas restricciones impuestas por el FFI: cada tipo debe ser un tipo foráneo marshallable, y el tipo de devolución de función debe ser un tipo foráneo marshallable o una acción IO que devuelve un tipo foráneo Marshallable.

Para los tipos que no son Marshallable (por ejemplo, Strings) necesita algo un poco más complejo. En primer lugar, dado que el marshalling ocurre en IO, solo puede reunir funciones que den como resultado una acción IO. Si desea reunir funciones puras, p. (Cadena -> Cadena), debe levantarlos a la forma (Cadena -> Cadena IO). [2] Definamos dos ayudantes:

wrapFn :: (FFI a ca, FFI b cb) => (a -> IO b) -> (ca -> IO cb) 
wrapFn fn = fromFFI >=> fn >=> toFFI 

unwrapFn :: (FFI a ca, FFI b cb) => (ca -> IO cb) -> (a -> IO b) 
unwrapFn fn a = bracket (toFFI a) freeFFI (fn >=> fromFFI) 

Éstos convierten los tipos de funciones en los valores agrupados apropiados, p. wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn. Tenga en cuenta que unwrapFn utiliza "Control.Exception.bracket" para garantizar que el recurso se libere en caso de excepciones. Ignorando esto, puede escribir unwrapFn fn = toFFI >=> fn >=> fromFFI; ver la similitud con wrapFn.

Ahora que tenemos estos ayudantes podemos empezar a escribir casos:

foreign import ccall "wrapper" 
    mkStrFn :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString)) 

foreign import ccall "dynamic" 
    dynStrFn :: FunPtr (CString -> IO CString) -> (CString -> IO CString) 

instance FFI (String -> IO String) (FunPtr (CString -> IO CString)) where 
    toFFI = mkStrFn . wrapFn 
    fromFFI = return . unwrapFn . dynStrFn 
    freeFFI = freeHaskellFunPtr 

Al igual que antes, no es posible realizar estas funciones polimórficas, lo que conduce a mi mayor reserva sobre este sistema. Es una gran sobrecarga porque necesita crear contenedores e instancias separadas para cada tipo de función. A menos que esté organizando muchas funciones, dudaría seriamente de que valga la pena el esfuerzo.

Así es como se pueden ordenar las funciones, pero ¿qué ocurre si se quiere que estén disponibles para llamar al código? Este otro proceso es exportando la función, y ya hemos desarrollado la mayoría de lo necesario.

Las funciones exportadas deben tener tipos Marshallable, al igual que FunPtr s. Simplemente podemos reutilizar el wrapFn para hacer esto. Para exportar unas pocas funciones todo lo que tiene que hacer es envolverlos con wrapFn y exportar las versiones envueltos:

f1 :: Int -> Int 
f1 = (+2) 

f2 :: String -> String 
f2 = reverse 

f3 :: String -> IO Int 
f3 = return . length 

foreign export ccall f1Wrapped :: CInt -> IO CInt 
f1Wrapped = wrapFn (return . f1) 

foreign export ccall f2Wrapped :: CString -> IO CString 
f2Wrapped = wrapFn (return . f2) 

foreign export ccall f3Wrapped :: CString -> IO CInt 
f3Wrapped = wrapFn f3 

Por desgracia, esta configuración sólo funciona para las funciones de un solo argumento. Para apoyar todas las funciones, vamos a hacer otra clase:

class ExportFunction a b where 
    exportFunction :: a -> b 

instance (FFI a ca, FFI b cb) => ExportFunction (a->b) (ca -> IO cb) where 
    exportFunction fn = (wrapFn (return . fn)) 

instance (FFI a ca, FFI b cb, FFI d cd) => ExportFunction (a->b->d) (ca->cb->IO cd) where 
    exportFunction fn = \ca cb -> do 
    a <- fromFFI ca 
    b <- fromFFI cb 
    toFFI $ fn a b 

Ahora podemos usar exportFunction para las funciones de 1 y 2 argumentos:

f4 :: Int -> Int -> Int 
f4 = (+) 

f4Wrapped :: CInt -> CInt -> IO CInt 
f4Wrapped = exportFunction f4 

foreign export ccall f4Wrapped :: CInt -> CInt -> IO CInt 

f3Wrapped2 = :: CString -> IO CInt 
f3Wrapped2 = exportFunction f3 

foreign export ccall f3Wrapped2 :: CString -> IO CInt 
f3Wrapped2 = exportFunction f3 

Ahora sólo tiene que escribir más instancias de ExportFunction para convertir automáticamente cualquier función al tipo apropiado para exportar. Creo que esto es lo mejor que puede hacer sin usar un tipo de pre-procesador o UnSafePerformIO.

[1] Técnicamente, no creo que haya ninguna necesidad para el fundep "basic -> ffitype", por lo que podría eliminarlo para permitir que un tipo básico se asigne a múltiples ffitypes. Una razón para hacerlo sería mapear todos los tamaños de enteros a enteros, aunque las implementaciones toFFI serían con pérdida.

[2] Una ligera simplificación. Puede ordenar una función String -> String en el tipo de FFI CString -> IO CString. Pero ahora no puede convertir la función CString -> IO CString a String -> String debido a IO en el tipo de devolución.

+0

Olvidé mencionar que esto solo se aplica a la exportación de funciones de Haskell. Importar funciones a través del FFI se manejaría mejor con algo como c2hs o GreenCard. –

+1

Mi enfoque principal es en 'exportación extranjera'. No estoy seguro de cómo se correlacionaría su solución con las funciones de exportación. FunPtr no es realmente importante aquí debido a esto. – Tener

+1

Necesita un FunPtr para coordinar una función, que es lo que hace su clase FFI (es decir, coordina cosas). He agregado un poco para explicar cómo usarlo para exportar funciones, pero todavía hay trabajo por hacer. –

Cuestiones relacionadas