将 IO (Maybe (IO (Maybe a)) 减少为 IO (Maybe a)

Gia*_*ger 4 monads haskell function-signature

我有一个函数可以使用 HsOpenSsl 的函数读取 Rsa 密钥,readPrivateKey不幸的是我的函数的签名是 this String -> IO (Maybe (IO Maybe RsaKey))。我需要 PEM 格式和 Cryptonite.RSA 密钥,并且我编写了函数mkRsaKey来从 PEM 格式的字符串中生成密钥。

代码如下:

import qualified Crypto.PubKey.RSA as Rsa --from cryptonite
import OpenSSL.EVP.PKey -- from HsOpenSSL
import OpenSSL.PEM -- from HsOpenSSL
import OpenSSL.RSA -- from HsOpenSSL
import Prelude

data RsaKey = RsaKey
  { rsaKeyCryptoniteKey :: Rsa.PrivateKey,
    rsaKeyStringRepr :: String
  }
  deriving (Show)

openSslKeyToCryptoniteKey :: RSAKeyPair -> Maybe Rsa.PrivateKey
openSslKeyToCryptoniteKey key = do
  let d = rsaD key
  let p = rsaP key
  let q = rsaQ key
  let mdP = rsaDMP1 key
  let mdQ = rsaDMQ1 key
  let mqinv = rsaIQMP key
  let size = rsaSize key
  let n = rsaN key
  let e = rsaE key
  dP <- mdP
  dQ <- mdQ
  qinv <- mqinv

  let pub = Rsa.PublicKey size n e
  return $ Rsa.PrivateKey pub d p q dP dQ qinv

openSslKeyToRsaKey :: RSAKeyPair -> IO (Maybe RsaKey)
openSslKeyToRsaKey key = do
  stringRepr <- writePublicKey key
  let maybeCryptoKey = openSslKeyToCryptoniteKey key
  return $ do
    cryptoKey <- maybeCryptoKey
    return $ RsaKey cryptoKey stringRepr

mkRsaKey :: String -> IO (Maybe (IO (Maybe RsaKey)))
mkRsaKey privateKey = do
  someOpenSslKey <- readPrivateKey privateKey PwNone
  let openSslKey = toKeyPair someOpenSslKey
  return $ openSslKeyToRsaKey <$> openSslKey
Run Code Online (Sandbox Code Playgroud)

现在,正如您所看到的,在我看来,类型签名不是最佳的,我希望拥有IO (Maybe RsaKey)。我怎样才能实现这个目标?

编辑:

我实际上设法做到了,但我正在使用unsafePerformIO

mkRsaKey :: String -> IO (Maybe RsaKey)
mkRsaKey privateKey = do
  someOpenSslKey <- readPrivateKey privateKey PwNone
  return $ do
    openSslKey <- toKeyPair someOpenSslKey
    unsafePerformIO (openSslKeyToRsaKey $ openSslKey)
Run Code Online (Sandbox Code Playgroud)

据我所知,您永远不应该使用,unsafePerformIO如果没有它,是否有某种方法可以做到这一点?

luq*_*qui 5

不错的发现case。这绝对不是您应该使用的地方unsafePerformIO。这是一种更紧凑的方式,仅供娱乐。

flattenMaybe :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a)
flattenMaybe m = m >>= fromMaybe (return Nothing)
Run Code Online (Sandbox Code Playgroud)

为了额外的乐趣,像这样展平层的能力是 monad 的特征能力;我们只是在 上使用该功能m (Maybe ...),也称为MaybeT。所以我们也可以这样写:

flattenMaybe = runMaybeT . join . fmap MaybeT . MaybeT
Run Code Online (Sandbox Code Playgroud)

进行必要的包装/展开以joinMaybeT m (MaybeT m a) -> MaybeT m a.

  • 我认为值得注意的是,如果“openSslKeyToRsaKey”和“mkRsaKey”一开始就是用 MaybeT 编写的,那么您根本不需要“flattenMaybe”。 (2认同)