【发布时间】: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'。这个解决方案提出了两个问题。
- 这是个坏主意吗?我不认为它很优雅。
- 这个从一元代码到非一元代码的“切割”应该在哪里?因为我也有这样的功能
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 => ...签名来明确表明它似乎是合适的。
标签: haskell monads state-monad