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à <*>
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 ...
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
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
Một lần nữa, haskell không thất vọng. Vì vậy, thanh lịch và xinh đẹp :) – ivokosir