我对此进行了抨击。结果并不漂亮,但它有效。TL;DR 是,假设我没有犯严重错误,我们可以这样编写你的函数:
haskellFunc string foo bar = cFunc <^ string <^> foo ^> bar
我们需要一些 GHC 扩展才能使其工作,但它们非常温和:
{-# 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 #-}
首先,我定义了一个类型类来表示 、 和的共同性质CString
,CFoo
并用作 的单一名称:CBar
withCType
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
然后是一些虚拟类型和实例,以便我可以单独进行类型检查:
-- 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)
我最初的想法是我们会有这样的东西,我们可以用它来调用我们在底层 C 类型上的函数......
liftC :: CType a c => (c -> IO b) -> (a -> IO b)
liftC cFunc x = withCType x cFunc
但这只能让我们提升一个参数的功能。我们想提升多个参数的功能......
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))
这很好用,但如果我们不需要为我们所追求的每一个 arity 定义其中一个,那就太好了。我们已经知道您可以用 and 的链替换所有,liftM2
等liftM3
函数,在这里也可以这样做。<$>
<*>
所以我的第一个想法是尝试liftC
变成一个运算符,并在每个参数之间穿插。所以它看起来像这样:
func <^> x <^> y <^> z
嗯......我们不能完全做到这一点。因为类型不起作用。考虑一下:
(<^>) :: CType a c => (c -> IO b) -> (a -> IO b)
cFunc <^> x = withCType x cFunc
的IO
部分withCType
使这变得困难。为了让它很好地链接,我们需要取回表单的另一个函数,(c -> IO b)
但我们取回IO
生成它的配方。<^>
例如,在“二进制”函数上调用上述内容的结果是IO (c -> IO b)
. 这很令人不安。
我们可以通过提供三种不同的运算符来解决这个问题……其中一些可以工作,IO
而另一些不能,并在调用链中的正确位置使用它们。这不是很整洁或很好。但它确实有效。必须有一种更清洁的方法来做同样的事情......
-- 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
我们可以像这样使用这个奇怪的 frankenstein(<^>
为更高元数的函数添加更多 s):
main = do
x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a'
print x
cFunc :: CString -> CInt -> CChar -> IO ()
cFunc _ _ _ = pure ()
这有点不雅。我很想看到一种更清洁的方法来解决这个问题。而且我不喜欢我为这些运算符选择的符号...