有没有办法链接像withCString这样的函数?

时间:2016-05-22 21:46:48

标签: haskell continuations function-composition continuation-passing

有没有办法链接像withCString这样的函数?我的意思是任何 看起来像f :: Foo -> (CFoo -> IO a) -> IO a的函数。

例如,假设有一个函数cFunc :: CString -> CFoo -> CBar -> IO ()

Usualy,我会做类似的事情:

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

但我想做的事情如下:

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

使用一些合适的合成运算符|.|

我正在编写带有大量C绑定的库,这个样板来了 经常。我做错了吗?

3 个答案:

答案 0 :(得分:17)

您可以使用Continuation 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

答案 1 :(得分:4)

我抓住了这个。结果不是漂亮,但它有效。 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的单个名称,定义一个类型类来表示CBarwithCTypewithC___的共同特性:

-- 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定义其中一个,那就太棒了。我们已经知道您可以使用liftM2liftM3的链替换所有<$><*>等函数,这样做会很好

所以我的第一个想法是尝试将liftC变成一个运算符,并在每个参数之间散布它。所以它看起来像这样:

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

嗯......我们不能那样做。因为类型不起作用。考虑一下:

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

IO的{​​{1}}部分使这很困难。为了使它能很好地链接,我们需要返回withCType形式的另一个函数,而是返回(c -> IO b)配方来生成它。例如,在“二进制”函数上调用上述IO的结果是<^>。那令人不安。

我们可以通过提供三个不同的运算符来解决这个问题......其中一些运算符在IO (c -> IO b)中工作,而其中一些运算符不工作,并且在调用链中的正确位置使用它们。这不是很整洁或不错。但它确实有效。必须有一个更清洁的方式来做同样的事情......

IO

我们可以像这样使用这种奇怪的frankenstein(为更高级的功能添加更多-- 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 ):

<^>

这有点不雅观。我希望看到一个更清洁的方式来实现这个目标。而且我不喜欢我为这些运营商选择的符号......

答案 2 :(得分:1)

不幸的是,你不能写一个像你想做的那样做一般事情的函数。问题出在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