【问题标题】:Using State Monad turns all of my functions into monadic functions使用 State Monad 将我的所有函数都转换为 monadic 函数
【发布时间】:2019-01-09 06:27:13
【问题描述】:

我在 Haskell 中编写了一个密码学库来学习密码学和 monad。 (供实际使用!)我的素性测试函数类型是

prime :: (Integral a, Random a, RandomGen g) => a -> State g Bool

如您所见,我使用了 State Monad,因此我不会一直让线程通过生成器。在内部,素数函数使用依赖随机数的 Miller-Rabin 检验,这就是素数函数也必须依赖随机数的原因。这在某种程度上是有道理的,因为素数函数只进行概率测试。

仅供参考,完整的prime函数如下,但我认为你不需要阅读它。

-- | findDS n, for odd n, gives odd d and s >= 0 s.t. n=2^s*d.
findDS :: Integral a => a -> (a, a)
findDS n = findDS' (n-1) 0
  where
    findDS' q s
      | even q = findDS' (q `div` 2) (s+1)
      | odd  q = (q,s)

-- | millerRabinOnce n d s a does one MR round test on
-- n using a.
millerRabinOnce :: Integral a => a -> a -> a -> a -> Bool
millerRabinOnce n d s a
  | even n           = False
  | otherwise        = not (test1 && test2)
  where
    (d,s) = findDS n

    test1 = powerModulo a d n /= 1
    test2 = and $ map (\t -> powerModulo a ((2^t)*d) n /= n-1) 
                      [0..s-1]

-- | millerRabin k n does k MR rounds testing n for primality.
millerRabin :: (RandomGen g, Random a, Integral a) =>
  a -> a -> State g Bool
millerRabin k n = millerRabin' k
  where
    (d, s)          = findDS n
    millerRabin' 0 = return True
    millerRabin' k = do
      rest <- millerRabin' $ k - 1
      test <- randomR_st (1, n - 1)
      let this = millerRabinOnce n d s test
      return $ this && rest

-- | primeK k n. Probabilistic primality test of n
-- using k Miller-Rabin rounds.
primeK :: (Integral a, Random a, RandomGen g) => 
  a -> a -> State g Bool
primeK k n
  | n < 2            = return False
  | n == 2 || n == 3 = return True
  | otherwise        = millerRabin (min n k) n

-- | Probabilistic primality test with 64 Miller-Rabin rounds.
prime :: (Integral a, Random a, RandomGen g) => 
  a -> State g Bool
prime = primeK 64

问题是,在任何需要使用素数的地方,我也必须将该函数转换为一元函数。即使它似乎不涉及任何随机性。例如,下面是我的 former 函数,用于恢复 Shamir 的秘密共享方案中的秘密。确定性操作,对吧?

recover :: Integral a => [a] -> [a] -> a -> a
recover pi_s si_s q = sum prods `mod` q
  where
    bi_s  = map (beta pi_s q) pi_s
    prods = zipWith (*) bi_s si_s

那时我使用了一个简单的、确定性的素数测试函数。我还没有重写recover 函数,但我已经知道beta 函数依赖于素数,因此它和recover 也会。两者都必须从简单的非单子函数变为两个单子函数,即使他们使用状态单子/随机性的原因确实很深。

我不禁认为所有代码都变得更加复杂,因为它必须是单子的。我是否遗漏了什么,或者在 Haskell 中的这种情况下总是如此?

我能想到的一个解决方案是

prime' n = runState (prime n) (mkStdGen 123)

并改用prime'。这个解决方案提出了两个问题。

  1. 这是个坏主意吗?我不认为它很优雅。
  2. 这个从一元代码到非一元代码的“切割”应该在哪里?因为我也有这样的功能genPrime:

_

genPrime :: (RandomGen g, Random a, Integral a) => a -> State g a
genPrime b = do
  n  <- randomR_st (2^(b-1),2^b-1)
  ps <- filterM prime [n..]
  return $ head ps

问题变成了是否在genPrime之前或之后进行“切割”等等。

【问题讨论】:

  • 一切都或多或少是正确的。您似乎缺少的唯一一件事是一元代码不一定比非一元代码复杂。它只是有点不同,需要一点时间来适应。
  • 好吧,is recover 即使在使用概率素数检查器时仍然是确定性的?如果它只是渐近确定性的(即蒙特卡洛),那么使用MonadRandom m =&gt; ... 签名来明确表明它似乎是合适的。

标签: haskell monads state-monad


【解决方案1】:

这确实是对在 Haskell 中实现的 monad 的有效批评。我在短期内没有看到比你提到的更好的解决方案,并且将所有代码切换为单子样式可能是最强大的一种,即使它们比自然样式更重量级,而且确实可能​​会很痛苦移植大型代码库,但如果您想添加更多外部效果,它可能会在以后得到回报。

我认为代数效应可以优雅地解决这个问题,例如:

所有函数都用它们的效果注释a -&gt; eff b,然而,与Haskell相反,它们都可以像纯函数a -&gt; b一样简单地组合(因此这是有效函数的一种特殊情况,具有空的效果签名)。然后该语言确保效果形成一个半格,以便可以组合具有不同效果的函数。

在 Haskell 中似乎很难有这样的系统。 Free(r) monads 库允许以类似的方式组合效果类型,但仍需要在术语级别明确的 monadic 样式。 一个有趣的想法是重载函数应用程序,因此可以将其隐式更改为(&gt;&gt;=),但这样做的原则性方法让我望而却步。主要问题是函数a -&gt; m b 被视为具有m 和codomain b 的效果的有效函数,以及具有codomain m b 的纯函数。我们如何推断何时使用($)(&gt;&gt;=)

在随机性的特殊情况下,我曾经有一个有点相关的想法,涉及可拆分随机生成器(无耻插件):https://blog.poisson.chat/posts/2017-03-04-splittable-generators.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多