【问题标题】:Reduce IO (Maybe (IO (Maybe a)) to IO (Maybe a)将IO(也许(IO(也许a))减少到IO(也许a)
【发布时间】:2022-01-23 11:43:32
【问题描述】:

我有一个使用 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 没有它有什么方法可以做到这一点吗?

【问题讨论】:

    标签: haskell monads function-signature


    【解决方案1】:

    case 的发现很好。这绝对不是你应该使用unsafePerformIO 的地方。这是一种更紧凑的方式,很有趣。

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

    为了更有趣,像这样扁平化层的能力是 monad 的特性;我们只是在m (Maybe ...) 上使用该功能,也称为MaybeT。所以我们也可以这样写:

    flattenMaybe = runMaybeT . join . fmap MaybeT . MaybeT
    

    进行必要的包装/解包以在MaybeT m (MaybeT m a) -&gt; MaybeT m a 使用join

    【讨论】:

    • 我认为值得注意的是,如果 openSslKeyToRsaKeymkRsaKey 一开始是用 MaybeT 编写的,那么您根本不需要 flattenMaybe
    • join . fmap MaybeT = (&gt;&gt;= MaybeT)
    【解决方案2】:

    找到了一种不使用unsafePerformIO 的方法,诀窍是使用仅在Nothing 情况下使用return 函数的case 语句。这是实现:

    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
    

    【讨论】:

    • 如果您不知道,case 表达式的审查对象本身可以是表达式;它不必是一个简单的变量。
    • 还有:maybeOpenSslKey &lt;- toKeyPair &lt;$&gt; readPrivateKey privateKey PwNone.
    • @jlwoodwa 我知道,但我发现这种方式更具可读性
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    • 1970-01-01
    • 2017-08-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多