2011-08-11 13 views
9

Có thể có chức năng nhận cuộc gọi hàm nước ngoài trong đó một số đối số của hàm nước ngoài là CString và trả về một hàm có chấp nhận String không?Chức năng đa biến Haskell Với IO

Dưới đây là một ví dụ về những gì tôi đang tìm kiếm:

foreign_func_1 :: (CDouble -> CString -> IO()) 
foreign_func_2 :: (CDouble -> CDouble -> CString -> IO()) 

externalFunc1 :: (Double -> String -> IO()) 
externalFunc1 = myFunc foreign_func_1 

externalFunc2 :: (Double -> Double -> String -> IO()) 
externalFunc2 = myFunc foreign_func_2 

tôi đã tìm ra cách để làm điều này với các loại số C. Tuy nhiên, tôi không thể tìm ra cách để làm điều đó có thể cho phép chuyển đổi chuỗi.

Sự cố có vẻ phù hợp trong các hàm IO, vì mọi thứ chuyển đổi thành CStrings chẳng hạn như newCString hoặc withCString là IO.

Dưới đây là những gì mã trông giống như chỉ xử lý chuyển đổi tăng gấp đôi.

class CConvertable interiorArgs exteriorArgs where 
    convertArgs :: (Ptr OtherIrrelevantType -> interiorArgs) -> exteriorArgs 

instance CConvertable (IO()) (Ptr OtherIrrelevantType -> IO()) where 
    convertArgs = doSomeOtherThingsThatArentCausingProblems 
instance (Real b, Fractional a, CConvertable intArgs extArgs) => CConvertable (a->intArgs) (b->extArgs) where 
    convertArgs op x= convertArgs (\ctx -> op ctx (realToFrac x)) 
+0

Bạn có thể cho chúng ta thấy những gì bạn đã viết? –

+0

Đây là một công việc khá lộn xộn :-) Tôi tưởng tượng câu trả lời nếu nó tồn tại quá đau đớn để sử dụng thực sự. Bạn đã xem 'hsc2hs' chưa? Nó khá mạnh mẽ và có thể tạo ra các loại chữ ký mà bạn muốn làm bước tiền xử lý. – sclv

+0

Một giải pháp mà tôi đã xem xét là tạo ra một thứ như hàm convertNth, nó sẽ lấy một số và một hàm, và thực hiện chuyển đổi sang vị trí đó. Tôi nghĩ rằng tôi sắp xếp như thế nào mà sẽ làm việc, mặc dù tôi đã không thử nó được nêu ra, vì vậy có lẽ nó sẽ trình bày một số khó khăn tôi đã không nghĩ đến. Bên cộng sẽ là tôi vẫn có thể sử dụng chức năng hiện tại của tôi cho không dây và chỉ phải gọi một cách rõ ràng các chuỗi. Lý tưởng nhất, tất nhiên, tôi hoặc ai đó sẽ chỉ ra cách tự động xử lý các chuỗi. – ricree

Trả lời

15

Có thể có chức năng nhận cuộc gọi hàm nước ngoài trong đó một số đối số của hàm nước ngoài là CString và trả về một hàm chấp nhận String thay thế không?

Bạn có thể yêu cầu không?

<lambdabot> The answer is: Yes! Haskell can do that. 

Ok. Điều tốt chúng tôi đã làm rõ.

Khởi động với một vài thủ tục tẻ nhạt:

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE UndecidableInstances #-} 

Ah, nó không phải là quá tệ mặc dù. Nhìn kìa, ma, không trùng lặp!

Sự cố có vẻ phù hợp với chức năng IO, vì mọi thứ chuyển đổi thành CStrings chẳng hạn như newCString hoặc withCString là IO.

Phải. Điều cần quan sát ở đây là có hai vấn đề liên quan đến nhau liên quan đến chính chúng ta: Sự tương ứng giữa hai loại, cho phép chuyển đổi; và bất kỳ ngữ cảnh bổ sung nào được giới thiệu bằng cách thực hiện chuyển đổi. Để đối phó với điều này hoàn toàn, chúng tôi sẽ làm cho cả hai phần rõ ràng và trộn chúng xung quanh một cách thích hợp. Chúng tôi cũng cần chú ý đến phương sai; nâng toàn bộ chức năng yêu cầu làm việc với các loại ở cả vị trí biến đổi và đối xứng, vì vậy chúng tôi sẽ cần chuyển đổi theo cả hai hướng.

Bây giờ, cho một hàm chúng ta muốn dịch, kế hoạch đi một cái gì đó như thế này:

  • Chuyển đổi tranh luận của chức năng, nhận được một loại mới và một số ngữ cảnh.
  • Trì hoãn ngữ cảnh vào kết quả của hàm, để lấy đối số theo cách chúng tôi muốn.
  • Collapse bối cảnh dư thừa nếu có thể
  • Đệ quy dịch kết quả của chức năng, để đối phó với các chức năng đa luận

Vâng, đó là âm thanh không quá khó khăn. Thứ nhất, bối cảnh rõ ràng:

class (Functor f, Cxt t ~ f) => Context (f :: * -> *) t where 
    type Collapse t :: * 
    type Cxt t :: * -> * 
    collapse :: t -> Collapse t 

này nói rằng chúng ta có một bối cảnh f, và một số loại t với bối cảnh đó. Hàm loại Cxt trích xuất bối cảnh thuần túy từ tCollapse cố gắng kết hợp ngữ cảnh nếu có thể. Hàm collapse cho phép chúng ta sử dụng kết quả của hàm kiểu.

Còn bây giờ, chúng tôi có những bối cảnh tinh khiết, và IO:

newtype PureCxt a = PureCxt { unwrapPure :: a } 

instance Context IO (IO (PureCxt a)) where 
    type Collapse (IO (PureCxt a)) = IO a 
    type Cxt (IO (PureCxt a)) = IO 
    collapse = fmap unwrapPure 

{- more instances here... -} 

đủ đơn giản. Xử lý các kết hợp khác nhau của ngữ cảnh hơi tẻ nhạt, nhưng các trường hợp rõ ràng và dễ viết.

Chúng tôi cũng sẽ cần một cách để xác định bối cảnh cho loại chuyển đổi. Hiện tại, bối cảnh cũng giống nhau theo một trong hai hướng, nhưng chắc chắn nó có thể hiểu được nếu không, vì vậy tôi đã xử lý chúng một cách riêng biệt. Do đó, chúng tôi có hai gia đình loại, cung cấp bối cảnh ngoài cùng mới cho một chuyển đổi nhập/xuất khẩu:

type family ExpCxt int :: * -> * 
type family ImpCxt ext :: * -> * 

Một số trường hợp ví dụ:

type instance ExpCxt() = PureCxt 
type instance ImpCxt() = PureCxt 

type instance ExpCxt String = IO 
type instance ImpCxt CString = IO 

Tiếp theo, chuyển đổi loại hình cá nhân. Chúng tôi sẽ lo lắng về đệ quy sau này.Thời gian cho một lớp học loại:

class (Foreign int ~ ext, Native ext ~ int) => Convert ext int where 
    type Foreign int :: * 
    type Native ext :: * 
    toForeign :: int -> ExpCxt int ext 
    toNative :: ext -> ImpCxt ext int 

này nói rằng hai loại extint là duy nhất chuyển đổi cho nhau. Tôi nhận ra rằng có thể không phải lúc nào cũng mong muốn chỉ có một bản đồ cho từng loại, nhưng tôi không cảm thấy muốn làm phức tạp thêm nữa (ít nhất, không phải bây giờ).

Như đã lưu ý, tôi cũng đã xử lý các chuyển đổi đệ quy tại đây; có lẽ chúng có thể được kết hợp, nhưng tôi cảm thấy nó sẽ rõ ràng hơn theo cách này. Chuyển đổi không đệ quy có ánh xạ đơn giản, được xác định rõ ràng giới thiệu ngữ cảnh tương ứng, trong khi chuyển đổi đệ quy cần phải truyền và hợp nhất ngữ cảnh và xử lý phân biệt các bước đệ quy từ trường hợp cơ sở.

Ồ, và bạn có thể đã nhận thấy rằng doanh nghiệp buồn cười vui nhộn đang diễn ra ở đó trong bối cảnh lớp học. Điều đó cho thấy một ràng buộc rằng hai loại phải bằng nhau; trong trường hợp này, nó liên kết từng loại hàm với tham số kiểu đối diện, cung cấp tính chất hai chiều được đề cập ở trên. Er, bạn có thể muốn có một GHC khá gần đây, mặc dù. Trên các GHC cũ hơn, điều này sẽ cần các phụ thuộc chức năng thay thế, và sẽ được viết như một cái gì đó như class Convert ext int | ext -> int, int -> ext.

Chức năng chuyển đổi cấp cụm từ khá đơn giản - lưu ý ứng dụng chức năng loại trong kết quả của chúng; ứng dụng là kết hợp trái như thường lệ, do đó, đó chỉ là áp dụng ngữ cảnh từ các họ kiểu cũ hơn. Ngoài ra, hãy lưu ý tên chéo trong tên, trong đó xuất hiện ngữ cảnh từ tra cứu bằng cách sử dụng loại gốc tự nhiên.

Vì vậy, chúng ta có thể chuyển đổi các loại mà không cần IO:

instance Convert CDouble Double where 
    type Foreign Double = CDouble 
    type Native CDouble = Double 
    toForeign = pure . realToFrac 
    toNative = pure . realToFrac 

... cũng như các loại mà làm:

instance Convert CString String where 
    type Foreign String = CString 
    type Native CString = String 
    toForeign = newCString 
    toNative = peekCString 

Bây giờ để tấn công ở trung tâm của vấn đề và dịch toàn bộ các hàm một cách đệ quy. Sẽ không có gì ngạc nhiên khi tôi đã giới thiệu nhưng lớp học khác là. Trên thực tế, hai, khi tôi đã tách các chuyển đổi xuất/nhập lần này.

class FFImport ext where 
    type Import ext :: * 
    ffImport :: ext -> Import ext 

class FFExport int where 
    type Export int :: * 
    ffExport :: int -> Export int 

Không có gì thú vị ở đây. Bạn có thể nhận thấy một mô hình phổ biến bây giờ - chúng tôi đang thực hiện số lượng bằng nhau về tính toán ở cả cấp độ và loại, và chúng tôi đang thực hiện chúng song song, ngay cả đến điểm bắt chước tên và cấu trúc biểu thức. Điều này là khá phổ biến nếu bạn đang thực hiện tính toán cấp độ loại cho những thứ liên quan đến giá trị thực, vì GHC bị kén chọn nếu nó không hiểu bạn đang làm gì. Lót những thứ như thế này làm giảm đáng kể đau đầu.

Dù sao, đối với mỗi lớp này, chúng ta cần một cá thể cho mỗi trường hợp cơ bản có thể và một cho trường hợp đệ quy. Than ôi, chúng ta không thể dễ dàng có một trường hợp cơ bản chung, do sự vô nghĩa khó chịu thông thường với chồng chéo. Nó có thể được thực hiện bằng cách sử dụng fundeps và loại điều kiện bình đẳng, nhưng ... ugh. Có lẽ sau này. Một tùy chọn khác sẽ là tham số hóa hàm chuyển đổi theo một loại cấp cho độ sâu chuyển đổi mong muốn, có nhược điểm là ít tự động hơn, nhưng cũng có được lợi ích rõ ràng, chẳng hạn như ít có khả năng vấp ngã đa hình hoặc các loại mơ hồ.

Hiện tại, tôi sẽ giả định rằng mọi chức năng kết thúc bằng một cái gì đó trong IO, vì IO a có thể phân biệt với a -> b mà không trùng lặp.

Thứ nhất, trường hợp cơ sở:

instance (Context IO (IO (ImpCxt a (Native a))) 
     , Convert a (Native a) 
     ) => FFImport (IO a) where 
    type Import (IO a) = Collapse (IO (ImpCxt a (Native a))) 
    ffImport x = collapse $ toNative <$> x 

Các trở ngại ở đây khẳng định một bối cảnh cụ thể sử dụng một trường hợp được biết đến, và rằng chúng ta có một số loại hình cơ bản với một chuyển đổi. Một lần nữa, lưu ý cấu trúc song song được chia sẻ theo chức năng loại Import và hàm số ffImport. Ý tưởng thực tế ở đây phải khá rõ ràng - chúng tôi ánh xạ hàm chuyển đổi trên IO, tạo ngữ cảnh lồng nhau của một số loại, sau đó sử dụng Collapse/collapse để làm sạch sau đó.

Trường hợp đệ quy là tương tự, nhưng phức tạp hơn:

instance (FFImport b, Convert a (Native a) 
     , Context (ExpCxt (Native a)) (ExpCxt (Native a) (Import b)) 
     ) => FFImport (a -> b) where 
    type Import (a -> b) = Native a -> Collapse (ExpCxt (Native a) (Import b)) 
    ffImport f x = collapse $ ffImport . f <$> toForeign x 

Chúng tôi đã thêm một ràng buộc FFImport cho cuộc gọi đệ quy, và tranh cãi bối cảnh đã trở nên lúng túng hơn vì chúng ta không biết chính xác những gì nó là, chỉ xác định đủ để đảm bảo chúng ta có thể đối phó với nó. Cũng lưu ý rằng sự tương phản ở đây, trong đó chúng tôi đang chuyển đổi chức năng thành kiểu gốc, nhưng chuyển đổi đối số thành loại nước ngoài. Ngoài ra, nó vẫn còn khá đơn giản.

Bây giờ, tôi đã để lại một số trường hợp tại thời điểm này, nhưng mọi thứ khác theo cùng các mẫu như trên, vì vậy, hãy bỏ qua đến cuối và phạm vi hàng hóa. Một số chức năng nước ngoài tưởng tượng:

foreign_1 :: (CDouble -> CString -> CString -> IO()) 
foreign_1 = undefined 

foreign_2 :: (CDouble -> SizedArray a -> IO CString) 
foreign_2 = undefined 

Và chuyển đổi:

imported1 = ffImport foreign_1 
imported2 = ffImport foreign_2 

gì, không có loại chữ ký? Nó có hoạt động không?

> :t imported1 
imported1 :: Double -> String -> [Char] -> IO() 
> :t imported2 
imported2 :: Foreign.Storable.Storable a => Double -> AsArray a -> IO [Char] 

Đúng, đó là loại phỏng đoán. Ah, đó là những gì tôi muốn thấy.

Chỉnh sửa: Đối với bất kỳ ai muốn thử điều này, tôi đã lấy mã đầy đủ để trình diễn ở đây, làm sạch nó một chút và uploaded it to github.

+0

Đẹp! Hai vấn đề mặc dù - vì bạn chỉ sử dụng 'newCString' và không phải' withCString', điều này sẽ bị rò rỉ như một nguồn chưa được đặt tên trong hình ngũ giác. Thứ hai, không có trường hợp undecidable, tôi giả định mã này cũng không thể mặc định để cho qua các giá trị tùy ý (không có chuyển đổi trường hợp) không thay đổi? – sclv

+0

@sclv: Điểm tốt về việc phân bổ - sử dụng 'withCString' thực sự tạo ra một ví dụ thú vị. Đối với các trường hợp mặc định, điều đó chỉ có thể xảy ra trong trường hợp chung với các trường hợp * chồng chéo * và, do đó, các quỹ nhỏ. Các thể hiện không thể xác định là cần thiết cho đệ quy trong 'Nhập' và một vài trường hợp khác. –

+0

Đúng - trộn lẫn không thể xác định và chồng chéo của tôi. – sclv

0

Chắc chắn là có thể. Cách tiếp cận thông thường là tạo lambdas để chuyển đến withCString. Sử dụng ví dụ của bạn:

myMarshaller :: (CDouble -> CString -> IO()) -> CDouble -> String -> IO() 
myMarshaller func cdouble string = ... 

withCString :: String -> (CString -> IO a) -> IO a 

Chức năng bên trong đã gõ CString -> IO a, mà chính là loại sau khi áp dụng một CDouble vào thư mục C chức năng func. Bạn cũng có phạm vi CDouble, vì vậy đó là mọi thứ bạn cần.

myMarshaller func cdouble string = 
    withCString string (\cstring -> func cdouble cstring) 
+0

Xin lỗi vì không rõ ràng. Tôi đang cố gắng để có được một chức năng chấp nhận một số lượng không xác định các đối số. Tôi đã cập nhật câu hỏi để hy vọng rõ ràng hơn. – ricree

+0

Xin lỗi vì sự hiểu lầm. Đó là khó khăn hơn, nhưng không phải là không thể. –

4

Đây là một giải pháp hai lớp kinh khủng. Phần đầu tiên (có tên, không hữu ích, foo) sẽ lấy các loại như Double -> Double -> CString -> IO() và biến chúng thành những thứ như IO (Double -> IO (Double -> IO (String -> IO()))). Vì vậy, mỗi chuyển đổi được buộc vào IO chỉ để giữ cho mọi thứ hoàn toàn đồng nhất.

Phần thứ hai, (tên cio cho "sự sụp đổ io) sẽ mất những điều đó và xô tất cả các IO bit đến cùng.

class Foo a b | a -> b where 
    foo :: a -> b 
instance Foo (IO a) (IO a) where 
    foo = id 
instance Foo a (IO b) => Foo (CString -> a) (IO (String -> IO b)) where 
    foo f = return $ \s -> withCString s $ \cs -> foo (f cs) 
instance Foo a (IO b) => Foo (Double -> a) (IO (Double -> IO b)) where 
    foo f = return $ \s -> foo (f s) 

class CIO a b | a -> b where 
    cio :: a -> b 
instance CIO (IO()) (IO()) where 
    cio = id 
instance CIO (IO b) c => CIO (IO (a -> IO b)) (a -> c) where 
    cio f = \a -> cio $ f >>= ($ a) 

{- 
*Main> let x = foo (undefined :: Double -> Double -> CString -> IO()) 
*Main> :t x 
x :: IO (Double -> IO (Double -> IO (String -> IO()))) 
*Main> :t cio x 
cio x :: Double -> Double -> String -> IO() 
-} 

Bên cạnh là một điều thường khủng khiếp để làm, có hai Đầu tiên là trường hợp bắt giữ của Foo không thể được viết.Vì vậy, đối với mọi loại bạn muốn chuyển đổi, ngay cả khi chuyển đổi chỉ là id, bạn cần một phiên bản Foo. trường hợp của CIO không thể được viết vì IO hàm bao quanh mọi thứ ing. Vì vậy, điều này chỉ hoạt động cho những thứ trả về IO(). Nếu bạn muốn nó hoạt động cho một cái gì đó trả về IO Int, bạn cũng cần thêm cá thể đó.

Tôi nghi ngờ rằng với đủ công việc và một số thủ thuật typeCast những hạn chế này có thể được khắc phục. Nhưng mã này đủ khủng khiếp như vậy, nên tôi sẽ không giới thiệu nó.

7

Điều này có thể được thực hiện bằng mẫu haskell. Theo nhiều cách, nó đơn giản hơn so với các lựa chọn thay thế liên quan đến các lớp học, vì nó phù hợp với mô hình dễ dàng hơn trên Language.Haskell.TH.Type hơn làm cùng một điều với các thể hiện.

{-# LANGUAGE TemplateHaskell #-} 
-- test.hs 
import FFiImport 
import Foreign.C 

foreign_1 :: CDouble -> CString -> CString -> IO CString 
foreign_2 :: CDouble -> CString -> CString -> IO (Int,CString) 
foreign_3 :: CString -> IO() 

foreign_1 = undefined; foreign_2 = undefined; foreign_3 = undefined 

fmap concat (mapM ffimport ['foreign_1, 'foreign_2, 'foreign_3]) 

loại suy ra các chức năng được tạo ra là:

imported_foreign_1 :: Double -> String -> String -> IO String 
imported_foreign_2 :: Double -> String -> String -> IO (Int, String) 
imported_foreign_3 :: String -> IO() 

Kiểm tra mã được tạo ra bởi tải test.hs với -ddump-chỗ nối (lưu ý rằng GHC vẫn dường như bỏ lỡ một số ngoặc đơn trong khá in) cho thấy rằng nước ngoài_2 viết định nghĩa sau khi một số định nghĩa trông giống như sau:

imported_foreign_2 w x y 
    = (\ (a, b) -> ((return (,) `ap` return a) `ap` peekCString b) =<< 
    join 
     (((return foreign_2 `ap` 
      (return . (realToFrac :: Double -> CDouble)) w) `ap` 
     newCString x) `ap` 
     newCString y)) 

hoặc dịch để ký hiệu:

imported_foreign_2 w x y = do 
     w2 <- return . (realToFrac :: Double -> CDouble) w 
     x2 <- newCString x 
     y2 <- newCString y 
     (a,b) <- foreign_2 w2 x2 y2 
     a2 <- return a 
     b2 <- peekCString b 
     return (a2,b2) 

Tạo mã theo cách đầu tiên đơn giản hơn vì ít biến hơn theo dõi .Trong khi foldl ($) f [x, y, z] không gõ kiểm tra khi nó có nghĩa là ((f $ x) $ y $ z) = fxyz nó có thể chấp nhận được trong mẫu haskell chỉ liên quan đến một số khác nhau loại.

Bây giờ cho việc thực hiện thực tế của những ý tưởng:

{-# LANGUAGE TemplateHaskell #-} 
-- FFiImport.hs 
module FFiImport(ffimport) where 
import Language.Haskell.TH; import Foreign.C; import Control.Monad 

-- a couple utility definitions 

-- args (a -> b -> c -> d) = [a,b,c] 
args (AppT (AppT ArrowT x) y) = x : args y 
args _ = [] 

-- result (a -> b -> c -> d) = d 
result (AppT (AppT ArrowT _) y) = result y 
result y = y 

-- con (IO a) = IO 
-- con (a,b,c,d) = TupleT 4 
con (AppT x _) = con x 
con x = x 

-- conArgs (a,b,c,d) = [a,b,c,d] 
-- conArgs (Either a b) = [a,b] 
conArgs ty = go ty [] where 
    go (AppT x y) acc = go x (y:acc) 
    go _ acc = acc 

Các mối nối $ (ffimport 'foreign_2) nhìn vào loại foreign_2 với cụ thể hóa để quyết định mà trên đó các chức năng để áp dụng cho các đối số hoặc kết quả.

-- Possibly useful to parameterize based on conv' 
ffimport :: Name -> Q [Dec] 
ffimport n = do 
    VarI _ ntype _ _ <- reify n 

    let ty :: [Type] 
     ty = args ntype 

    let -- these define conversions 
     -- (ffiType, (hsType -> IO ffiType, ffiType -> IO hsType)) 
     conv' :: [(TypeQ, (ExpQ, ExpQ))] 
     conv' = [ 
      ([t| CString |], ([| newCString |], 
           [| peekCString |])), 
      ([t| CDouble |], ([| return . (realToFrac :: Double -> CDouble) |], 
           [| return . (realToFrac :: CDouble -> Double) |])) 
      ] 

     sequenceFst :: Monad m => [(m a, b)] -> m [(a,b)] 
     sequenceFst x = liftM (`zip` map snd x) (mapM fst x) 

    conv' <- sequenceFst conv' 
    -- now conv' :: [(Type, (ExpQ, ExpQ))] 

Cho chuyển đổi 'ở trên, hơi đơn giản khi áp dụng các chức năng đó khi khớp loại. Trường hợp ngược lại sẽ ngắn hơn nếu các thành phần chuyển đổi của các tệp trả về không quan trọng.

let conv :: Type --^type of v 
      -> Name --^variable to be converted 
      -> ExpQ 
     conv t v 
      | Just (to,from) <- lookup t conv' = 
       [| $to $(varE v) |] 
      | otherwise = [| return $(varE v) |] 

     -- | function to convert result types back, either 
     -- occuring as IO a, IO (a,b,c) (for any tuple size) 
     back :: ExpQ 
     back 
      | AppT _ rty <- result ntype, 
       TupleT n <- con rty, 
       n > 0, -- for whatever reason $(conE (tupleDataName 0)) 
         -- doesn't work when it could just be $(conE '()) 
       convTup <- map (maybe [| return |] snd . 
            flip lookup conv') 
            (conArgs rty) 
           = do 
        rs <- replicateM n (newName "r") 
        lamE [tupP (map varP rs)] 
         [| $(foldl (\f x -> [| $f `ap` $x |]) 
           [| return $(conE (tupleDataName n)) |] 
           (zipWith (\c r -> [| $c $(varE r)|]) convTup rs)) 
         |] 
      | AppT _ nty <- result ntype, 
       Just (_,from) <- nty `lookup` conv' = from 
      | otherwise = [| return |] 

Cuối cùng, đặt cả hai phần với nhau trong một định nghĩa hàm:

vs <- replicateM (length ty) (newName "v") 

    liftM (:[]) $ 
     funD (mkName $ "imported_"++nameBase n) 
     [clause 
      (map varP vs) 
      (normalB [| $back =<< join 
         $(foldl (\x y -> [| $x `ap` $y |]) 
           [| return $(varE n) |] 
           (zipWith conv ty vs)) 
       |]) 
      []] 
+0

Rất tuyệt! Tốt để xem các ví dụ làm việc về việc làm với TH. Trong nhiều cách, tôi thích các lớp kiểu cho những thứ như thế này, nơi mà khái niệm nó thực sự là một hàm trên các kiểu mang các thuật ngữ, nhưng như bạn chỉ ra, TH làm cho một số phần dễ dàng hơn để làm việc. –