有没有办法像 withCString 这样链接函数?
Is there a way to chain functions like withCString?
有没有办法像 withCString
那样链接函数?我的意思是任何
看起来像 f :: Foo -> (CFoo -> IO a) -> IO a
.
的函数
例如,假设有一个函数cFunc :: CString -> CFoo -> CBar -> IO ()
通常,我会做这样的事情:
haskellFunc string foo bar =
withCString string $ \ cString ->
withCFoo foo $ \ cFoo ->
withCBar bar $ \ cBar ->
cFunc cString cFoo cBar
但我想做类似的事情:
haskellFunc = (withCString |.| withCFoo |.| withCBar) cFunc
使用一些适当的合成运算符 |.|
。
我正在编写包含大量 C 绑定的库,此样板文件随附
经常。我做错了什么吗?
不幸的是,您无法编写一个函数来执行您希望执行的一般操作。问题出在 Haskell 的类型系统上。在您的示例中, cFunc
接受三个参数,因此当您编写便利函数时,它会期望一个接受三个参数的 C 函数。没有办法编写可以接受任意数量参数的 cFunc
的函数; Haskell 的类型系统太严格了。但是,考虑到这一点,您可以编写几个不同的函数,每个函数用于 cFunc
具有不同数量的参数。这是否值得付出努力取决于您需要多久使用一次这种样板。
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
现在,您可以像这样使用 C 函数了。
haskellFunc :: String -> Foo -> Bar -> IO ()
haskellFunc = cApply3 cFunc withCString withCFoo withCBar
我试了一下。结果并不漂亮,但它确实有效。 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
作为 [=24= 的单个名称]:
-- 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))
这工作得很好,但如果我们不需要为我们所追求的每一个参数定义其中之一,那就太好了。我们已经知道您可以用 <$>
和 <*>
链替换所有 liftM2
、liftM3
等函数,在这里做同样的事情会很好.
所以我的第一个想法是尝试将 liftC
变成一个运算符,并将它穿插在每个参数之间。所以它看起来像这样:
func <^> x <^> y <^> z
嗯...我们不能完全做到这一点。因为类型不起作用。考虑一下:
(<^>) :: CType a c => (c -> IO b) -> (a -> IO b)
cFunc <^> x = withCType x cFunc
withCType
的 IO
部分使这变得困难。为了让它很好地链接起来,我们需要取回 (c -> IO b)
形式的另一个函数,但我们取而代之的是取回 IO
配方来生成它。例如,在 "binary" 函数上调用上述 <^>
的结果是 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
我们可以像这样使用这个怪异的弗兰肯斯坦(为更高级的函数添加更多 <^>
s):
main = do
x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a'
print x
cFunc :: CString -> CInt -> CChar -> IO ()
cFunc _ _ _ = pure ()
这有点不雅。我很想看到一种更简洁的方法来解决这个问题。而且我不喜欢我为那些运算符选择的符号...
您可以使用 Cont
inuation applicative 来编写这些 a -> (b -> IO c) -> IO c
函数:
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)
或者使用一些额外的语法:
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
有没有办法像 withCString
那样链接函数?我的意思是任何
看起来像 f :: Foo -> (CFoo -> IO a) -> IO a
.
例如,假设有一个函数cFunc :: CString -> CFoo -> CBar -> IO ()
通常,我会做这样的事情:
haskellFunc string foo bar =
withCString string $ \ cString ->
withCFoo foo $ \ cFoo ->
withCBar bar $ \ cBar ->
cFunc cString cFoo cBar
但我想做类似的事情:
haskellFunc = (withCString |.| withCFoo |.| withCBar) cFunc
使用一些适当的合成运算符 |.|
。
我正在编写包含大量 C 绑定的库,此样板文件随附 经常。我做错了什么吗?
不幸的是,您无法编写一个函数来执行您希望执行的一般操作。问题出在 Haskell 的类型系统上。在您的示例中, cFunc
接受三个参数,因此当您编写便利函数时,它会期望一个接受三个参数的 C 函数。没有办法编写可以接受任意数量参数的 cFunc
的函数; Haskell 的类型系统太严格了。但是,考虑到这一点,您可以编写几个不同的函数,每个函数用于 cFunc
具有不同数量的参数。这是否值得付出努力取决于您需要多久使用一次这种样板。
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
现在,您可以像这样使用 C 函数了。
haskellFunc :: String -> Foo -> Bar -> IO ()
haskellFunc = cApply3 cFunc withCString withCFoo withCBar
我试了一下。结果并不漂亮,但它确实有效。 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
作为 [=24= 的单个名称]:
-- 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))
这工作得很好,但如果我们不需要为我们所追求的每一个参数定义其中之一,那就太好了。我们已经知道您可以用 <$>
和 <*>
链替换所有 liftM2
、liftM3
等函数,在这里做同样的事情会很好.
所以我的第一个想法是尝试将 liftC
变成一个运算符,并将它穿插在每个参数之间。所以它看起来像这样:
func <^> x <^> y <^> z
嗯...我们不能完全做到这一点。因为类型不起作用。考虑一下:
(<^>) :: CType a c => (c -> IO b) -> (a -> IO b)
cFunc <^> x = withCType x cFunc
withCType
的 IO
部分使这变得困难。为了让它很好地链接起来,我们需要取回 (c -> IO b)
形式的另一个函数,但我们取而代之的是取回 IO
配方来生成它。例如,在 "binary" 函数上调用上述 <^>
的结果是 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
我们可以像这样使用这个怪异的弗兰肯斯坦(为更高级的函数添加更多 <^>
s):
main = do
x <- cFunc <^ "hello" <^> (10 :: Int) ^> 'a'
print x
cFunc :: CString -> CInt -> CChar -> IO ()
cFunc _ _ _ = pure ()
这有点不雅。我很想看到一种更简洁的方法来解决这个问题。而且我不喜欢我为那些运算符选择的符号...
您可以使用 Cont
inuation applicative 来编写这些 a -> (b -> IO c) -> IO c
函数:
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)
或者使用一些额外的语法:
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