如何创建两个调用此外部库API的ByteStrings?

dna*_*naq 5 haskell ffi bytestring

我目前正在编写绑定到加密库,该库公开了生成密钥对的函数:

const size_t PUBLICKEYBYTES = 32;
const size_t SECRETKEYBYTES = 32;
int random_keypair(unsigned char pk[PUBLICKEYBYTES],
                   unsigned char sk[SECRETKEYBYTES]);
Run Code Online (Sandbox Code Playgroud)

此函数随机生成一个密钥,计算相应的公钥并将结果放入pksk.

当刚刚返回一个ByteString我发现,最简单的方法是使用create :: Int -> (Ptr Word8 -> IO ()) -> IO ByteStringData.ByteString.Internal.但是,该功能不能同时创建两个ByteStrings.

我的第一个方法是写下这样的东西:

newtype PublicKey = PublicKey ByteString
newtype SecretKey = SecretKey ByteString
randomKeypair :: IO (PublicKey, SecretKey)
randomKeypair = do
    let pk = B.replicate 0 publicKeyBytes
        sk = B.replicate 0 secretKeyBytes
    B.unsafeUseAsCString pk $ \ppk ->
        B.unsafeUseAsCString sk $ \psk ->
        c_random_keypair ppk psk
    return (PublicKey pk, SecretKey sk)
Run Code Online (Sandbox Code Playgroud)

但是,这似乎不适用于GHC 7.10.2.运行测试套件时,我发现我似乎ByteString在函数调用之间共享s,导致加密/解密失败并给出不正确的结果.

我设法通过定义自己的函数来解决这个问题:

createWithResult :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString, a)
createWithResult i f = do
    fp <- B.mallocByteString i
    r <- withForeignPtr fp f
    return (B.fromForeignPtr fp 0 i, r)
Run Code Online (Sandbox Code Playgroud)

并使用它像:

randomKeypair = fmap (PublicKey *** SecretKey) $
    createWithResult publicKeyBytes $ \ppk ->
    B.create secretKeyBytes $ \psk ->
    void $ c_random_keypair ppk psk
Run Code Online (Sandbox Code Playgroud)

这似乎有效,所有测试都通过.

我的问题是,当谈到IOmonad 时,分享和引用透明度的语义究竟是什么?

我的直觉告诉我(错误地)我可以用第一种方式解决问题,但显然我做不到.我认为正在发生的事情是优化器看到let-statements可以浮出最高级定义,这就是我遇到这些问题的原因.

小智 2

第一种方法的问题是您试图修改一个不可变的值(pk以及sk在您的函数中)。unsafeUseAsCString 的文档说:

修改 CString(无论是在 C 中还是使用 poke)都会导致 ByteString 的内容发生更改,从而破坏引用透明度

在共享和引用透明度方面,单子IO没有不同的语义。事实上,块let中的与monaddo没有任何关系;IO你的代码相当于:

randomKeypair :: IO (PublicKey, SecretKey)
randomKeypair =
    let pk = B.replicate 0 publicKeyBytes
        sk = B.replicate 0 secretKeyBytes
    in B.unsafeUseAsCString pk (\ppk ->
        B.unsafeUseAsCString sk $ \psk ->
        c_random_keypair ppk psk) >>
    return (PublicKey pk, SecretKey sk)
Run Code Online (Sandbox Code Playgroud)

现在它清晰可见,pk并且sk可以浮动到顶层。