将IO (Maybe (IO (Maybe a)))简化为IO (Maybe a)。

4

我有一个函数,使用HsOpenSsl的readPrivateKey函数读取Rsa密钥,但不幸的是,我的函数签名为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

现在,您可以看到类型签名在我的意识中不是最优的,我希望拥有 IO (Maybe RsaKey)。我该如何实现?

编辑:

我实际上已经成功了,但我正在使用 unsafePerformIO

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

据我所知,你永远不应该使用unsafePerformIO,是否有其他方法可以做到这一点?
2个回答

5

发现一个很不错的技巧,使用 case。在这个情景下,绝对不能使用 unsafePerformIO。下面是一种更加简洁的方式,供大家娱乐。

flattenMaybe :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a)
flattenMaybe m = m >>= fromMaybe (return Nothing)

而额外有趣的是,像这样平展图层的能力是单子(monads)的特征能力;我们只是在 m (Maybe ...) 上使用了该能力,也就是所谓的MaybeT。因此,我们也可以这样写:

flattenMaybe = runMaybeT . join . fmap MaybeT . MaybeT

在使用 join 函数将 MaybeT m (MaybeT m a) 转换为 MaybeT m a 时,需要进行必要的包装和解包装操作。


2
我认为值得注意的是,如果一开始就使用MaybeT编写openSslKeyToRsaKeymkRsaKey,则根本不需要flattenMaybe - amalloy
1
join . fmap MaybeT = (>>= MaybeT) - dfeuer

4

找到了一种在不使用unsafePerformIO的情况下进行操作的方法,诀窍是使用一个 case 语句,在 Nothing 情况下仅使用返回函数。以下是实现:

mkRsaKey :: String -> IO (Maybe RsaKey)
mkRsaKey privateKey = do
  someOpenSslKey <- readPrivateKey privateKey PwNone
  let maybeOpenSslKey = toKeyPair someOpenSslKey
  case maybeOpenSslKey of
    Just key -> openSslKeyToRsaKey key
    Nothing -> return Nothing

5
如果你不知道,case表达式的被匹配对象可以是一个表达式,它不一定是一个简单的变量。 - jlwoodwa
另外:maybeOpenSslKey <- toKeyPair <$> readPrivateKey privateKey PwNone - chepner
1
@jlwoodwa 我知道,但我觉得这样更易读。 - Gian Laager

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接