12

Có cách nào để chuỗi các chức năng như withCString không? Theo đó, tôi có nghĩa là mọi chức năng trông giống như f :: Foo -> (CFoo -> IO a) -> IO a.Có cách nào để chuỗi các chức năng như withCString không?

Ví dụ, cho phép nói rằng có một chức năng cFunc :: CString -> CFoo -> CBar -> IO()

Thông thường, tôi sẽ làm một cái gì đó như:

haskellFunc string foo bar = 
    withCString string $ \ cString -> 
    withCFoo foo $ \ cFoo -> 
     withCBar bar $ \ cBar -> 
     cFunc cString cFoo cBar 

Nhưng tôi muốn làm điều gì đó như:

haskellFunc = (withCString |.| withCFoo |.| withCBar) cFunc 

với một số nhà điều hành thành phần thích hợp |.|.

Tôi đang viết thư viện với nhiều liên kết C, và bản in này thường xuất hiện thường là . Tôi có làm điều gì sai?

Trả lời

17

Bạn có thể sử dụng Continuation applicative để soạn những a -> (b -> IO c) -> IO c chức năng:

import Control.Monad.Cont 

haskellFunc :: String -> Foo -> Bar -> IO() 
haskellFunc string foo bar = flip runCont id $ 
    cFunc <$> 
     cont (withCString string) <*> 
     cont (withCFoo foo) <*> 
     cont (withCBar bar) 

Hoặc với một chút cú pháp bổ sung:

haskellFunc' :: String -> Foo -> Bar -> IO() 
haskellFunc' string foo bar = flip runCont id $ 
    cFunc <<$>> withCString string <<*>> withCFoo foo <<*>> withCBar bar 
    where 
    f <<$>> x = f <$> cont x 
    f <<*>> x = f <*> cont x 
+1

Hoặc nếu chúng tôi nhận được các dấu ngoặc đơn (hoặc bạn muốn sử dụng SHE), '(| cFunc (cont (withCString string)) (cont (withCFoo foo)) (cont (withCBar bar)) |)' – copumpkin

+2

Một ưu điểm khác của nhận ra rằng đây chỉ là 'Cont' trong ngụy trang là bạn nhận được những thứ khác miễn phí. Ví dụ, bạn cần một bộ sưu tập tùy ý của các trình cấp phát kiểu CPS này: bạn chỉ có thể sử dụng 'chuỗi',' đi ngang' hoặc tương tự để lấy danh sách hoặc tập hợp các giá trị khác cùng một lúc. – copumpkin

+1

Một lần nữa, haskell không thất vọng. Vì vậy, thanh lịch và xinh đẹp :) – ivokosir

1

Thật không may, bạn không thể viết một chức năng làm điều gì đó chung chung như bạn muốn làm. Vấn đề là với hệ thống kiểu của Haskell. Trong ví dụ của bạn, cFunc có ba đối số, vì vậy khi bạn viết hàm tiện lợi của bạn, nó sẽ mong đợi một hàm C lấy ba đối số. Sẽ không có cách nào để viết một hàm có thể chấp nhận số cFunc của bất kỳ số đối số nào; Hệ thống kiểu của Haskell quá nghiêm ngặt. Tuy nhiên, với ý nghĩ đó, bạn có thể viết nhiều hàm khác nhau, mỗi hàm cho một số cFunc với một số đối số khác nhau. Có hay không điều này là giá trị nỗ lực phụ thuộc vào mức độ thường xuyên bạn sẽ cần phải sử dụng loại tấm nồi hơi.

cApply2 :: (a' -> b' -> c) 
     -> (a -> (a' -> c)) 
     -> (b -> (b' -> c)) 
     -> a -> b -> c 
cApply2 cFunc withArg1 withArg2 arg1 arg2 = 
    withArg1 arg1 $ \cArg1 -> 
    withArg2 arg2 $ \cArg2 -> 
     cFunc cArg1 cArg2 

cApply3 :: (a' -> b' -> c' -> d) 
     -> (a' -> (a -> d)) 
     -> (b' -> (b -> d)) 
     -> (c' -> (c -> d)) 
     -> a -> b -> c -> d 
cApply3 cFunc withArg1 withArg2 withArg3 arg1 arg2 arg3 = 
    withArg1 arg1 $ \cArg1 -> 
    withArg2 arg2 $ \cArg2 -> 
     withArg3 arg3 $ \cArg3 -> 
     cFunc cArg1 cArg2 cArg3 

Bây giờ, bạn có thể sử dụng các hàm C như vậy.

haskellFunc :: String -> Foo -> Bar -> IO() 
haskellFunc = cApply3 cFunc withCString withCFoo withCBar 
4

Tôi đã đâm vào lúc này. Kết quả không phải là đẹp, nhưng nó hoạt động. TL; DR là, bởi cuối cùng, chúng ta có thể viết chức năng của bạn như thế này, giả sử tôi đã không có lỗi làm tê liệt:

haskellFunc string foo bar = cFunc <^ string <^> foo ^> bar 

Chúng tôi cần một số phần mở rộng GHC để làm việc này, nhưng họ đang khá hiền lành:

{-# LANGUAGE MultiParamTypeClasses #-} 
-- So that we can declare an instance for String, 
-- aka [Char]. Without this extension, we'd only 
-- be able to declare an instance for [a], which 
-- is not what we want. 
{-# LANGUAGE FlexibleInstances #-} 

Trước tiên tôi xác định một typeclass để đại diện cho tính chất chung của CString, CFoo, và CBar, sử dụng withCType như tên duy nhất cho withC___:

-- I use c as the type variable to indicate that 
-- it represents the "C" version of our type. 
class CType a c where 
    withCType :: a -> (c -> IO b) -> IO b 
.210

Sau đó, một số loại hình nộm và các trường hợp để tôi có thể typecheck này trong sự cô lập:

-- I'm using some dummy types I made up so I could 
-- typecheck this answer standalone. 
newtype CString = CString String 
newtype CInt = CInt Int 
newtype CChar = CChar Char 

instance (CType String CString) where 
    -- In reality, withCType = withCString 
    withCType str f = f (CString str) 

instance (CType Int CInt) where 
    withCType str f = f (CInt str) 

instance (CType Char CChar) where 
    withCType str f = f (CChar str) 

suy nghĩ ban đầu của tôi là chúng tôi muốn có một cái gì đó như thế này mà chúng ta muốn sử dụng để gọi các chức năng của chúng tôi trên C cơ bản các loại ...

liftC :: CType a c => (c -> IO b) -> (a -> IO b) 
liftC cFunc x = withCType x cFunc 

Nhưng điều đó chỉ cho phép chúng tôi nâng chức năng của một đối số. Chúng tôi muốn nâng các chức năng của nhiều đối số ...

liftC2 :: (CType a c, CType a' c') => (c -> c' -> IO b) -> (a -> a' -> IO b) 
liftC2 cFunc x y = withCType x (\cx -> withCType y (cFunc cx)) 

Điều đó làm việc tốt, nhưng sẽ rất tuyệt nếu chúng tôi không cần phải xác định một trong số đó cho mọi điều chúng tôi đang theo đuổi. Chúng tôi đã biết rằng bạn có thể thay thế tất cả các chức năng liftM2, liftM3, v.v. với các chuỗi <$><*> và sẽ tốt hơn nếu thực hiện tương tự ở đây.

Vì vậy, suy nghĩ đầu tiên của tôi là cố gắng biến liftC thành một toán tử và phân tách giữa mỗi đối số. Vì vậy, nó sẽ trông giống như thế này:

func <^> x <^> y <^> z 

Vâng ... chúng ta không thể làm điều đó. Bởi vì các loại không hoạt động. Hãy xem xét điều này:

(<^>) :: CType a c => (c -> IO b) -> (a -> IO b) 
cFunc <^> x = withCType x cFunc 

IO một phần của withCType làm cho việc này trở nên khó khăn. Để chuỗi này trở nên độc đáo, chúng ta cần lấy lại một hàm khác của biểu mẫu (c -> IO b) nhưng thay vào đó, chúng ta lấy lại công thức IO để tạo ra nó. Kết quả của việc gọi hàm <^> trên một hàm "nhị phân", ví dụ, là IO (c -> IO b). Đó là rắc rối.

Chúng tôi có thể giải quyết vấn đề này bằng cách cung cấp ba toán tử khác nhau ... một số hoạt động trong IO và một số không sử dụng chúng đúng vị trí trong chuỗi cuộc gọi. Điều này không phải là rất gọn gàng hoặc tốt đẹp. Nhưng nó hoạt động. Phải có một cách sạch hơn để làm điều này cùng ...

-- Start of the chain: pure function to a pure 
-- value. The "pure value" in our case will be 
-- the "function expecting more arguments" after 
-- we apply its first argument. 
(<^) :: CType a c => (c -> b) -> (a -> IO b) 
cFunc <^ x = withCType x (\cx -> return (cFunc cx)) 

-- Middle of the chain: we have an IO function now, 
-- but it produces a pure value -- "gimme more arguments." 
(<^>) :: CType a c => IO (c -> b) -> a -> IO b 
iocFunc <^> x = iocFunc >>= (<^ x) 

-- End of the chain: we have an IO function that produces 
-- an IO value -- no more arguments need to be provided; 
-- here's the final value. 
(^>) :: CType a c => IO (c -> IO b) -> a -> IO b 
iocFunc ^> x = withCType x =<< iocFunc 

Chúng ta có thể sử dụng Frankenstein lạ này như thế này (bổ sung thêm <^> s cho các chức năng-arity cao hơn):

main = do 
    x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a' 
    print x 

cFunc :: CString -> CInt -> CChar -> IO() 
cFunc _ _ _ = pure() 

Đây là hơi không thích hợp. Tôi rất muốn nhìn thấy một cách rõ ràng hơn để có được điều này. Và tôi không yêu những biểu tượng tôi đã chọn cho những nhà khai thác ...

Các vấn đề liên quan