如何在Haskell FFI到C++中编写纯String到String函数

tin*_*lyx 6 string haskell ffi purely-functional

我想通过Haskell FFI在C++中实现一个函数,它应该具有(最终)类型 String -> String.比如说,是否可以在C++中使用完全相同的签名重新实现以下函数?

import Data.Char
toUppers:: String -> String
toUppers s = map toUpper s
Run Code Online (Sandbox Code Playgroud)

特别是,我想避免在返回类型中使用IO,因为在这个简单的任务中引入杂质(我的意思是IO monad)在逻辑上是不必要的.到目前为止我所看到的所有涉及C字符串的例子都涉及返回IO某事或Ptr,它们无法转换回纯粹的String.

我之所以这样做,是因为我觉得封送会对FFI造成混乱.也许如果我能解决上面最简单的情况(除了原始类型,如int),那么我可以在C++端做任何我想要的数据解析,这应该很容易.

与我想要在编组到/从字符串之间进行的计算相比,解析的成本可以忽略不计.

提前致谢.

Pet*_*ann 7

IO至少需要 在某个时刻参与,为C字符串分配缓冲区.这里直截了当的解决方案可能是:

import Foreign
import Foreign.C
import System.IO.Unsafe as Unsafe

foreign import ccall "touppers" c_touppers :: CString -> IO ()
toUppers :: String -> String
toUppers s =
  Unsafe.unsafePerformIO $
    withCString s $ \cs ->
      c_touppers cs >> peekCString cs
Run Code Online (Sandbox Code Playgroud)

我们withCString用来将Haskell字符串编组到缓冲区中,将其更改为大写字母,最后将(已更改的!)缓冲区内容解组为新的Haskell字符串.

另一种解决方案可能是将混乱委托IObytestring图书馆.如果你对性能感兴趣,这可能是一个好主意.解决方案看起来大致如下:

import Data.ByteString.Internal

foreign import ccall "touppers2" 
  c_touppers2 :: Int -> Ptr Word8 -> Ptr Word8 -> IO ()
toUppers2 :: ByteString -> ByteString
toUppers2 s =
  unsafeCreate l $ \p2 -> 
    withForeignPtr fp $ \p1 ->
      c_touppers2 l (p1 `plusPtr` o) p2
 where (fp, o, l) = toForeignPtr s
Run Code Online (Sandbox Code Playgroud)

这有点优雅,因为我们现在实际上不需要进行任何编组,只需转换指针.另一方面,C++方面在两个方面发生了变化 - 我们必须处理可能非空终止的字符串(需要传递长度),现在必须写入不同的缓冲区,因为输入不再是副本.


作为参考,这里有两个适合上述导入的快速和简单的C++函数:

#include <ctype.h>
extern "C" void touppers(char *s) {
    for (; *s; s++) *s = toupper(*s);
}
extern "C" void touppers2(int l, char *s, char *t) {
    for (int i = 0; i < l; i++) t[i] = toupper(s[i]);
}
Run Code Online (Sandbox Code Playgroud)