我在Haskell中编写了一个密码学库,以了解密码学和monad。 (不供实际使用!)用于素数测试的函数类型为
prime :: (Integral a, Random a, RandomGen g) => a -> State g Bool
因此,如您所见,我使用State Monad,所以我一直没有线程通过生成器。内部质数函数使用Miller-Rabin检验,该检验依赖于随机数,这就是为什么质数函数也必须依赖随机数的原因。从某种意义上说,这是有道理的,因为质数函数只能进行概率检验。
仅供参考,下面是整个主要功能,但我认为您不需要阅读它。
-- | 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
也将依赖于质数。而且,尽管它们使用State Monad /随机性的原因确实很深,但两者都必须从简单的非Monadic函数变为两个Monadic函数。
我忍不住想,既然所有代码都必须是一元代码,那么所有代码都会变得更加复杂。我是否缺少某些东西,或者在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
之前还是之后进行“剪切”。
答案 0 :(得分:4)
这确实是对在Haskell中实施的monad的有效批评。在短期内,我认为没有比您提到的更好的解决方案了,即使将所有代码都比自然风格更重,将所有代码切换为单子风格可能也是最健壮的代码。移植大型代码库,尽管如果您想添加更多外部效果,可能会在以后获得回报。
我认为代数效应可以很好地解决这个问题,例如:
所有函数都以其效果a -> eff b
进行注释,但是,与Haskell相反,它们都可以像纯函数a -> b
一样简单地组成(因此,这是有效函数的特殊情况,带有空白效果签名)。然后,该语言确保效果形成半格,从而可以组成具有不同效果的功能。
在Haskell中建立这样的系统似乎很困难。 Free(r)monads库允许以类似方式合成效果类型,但在术语级别仍需要显式monadic样式。
一个有趣的想法是重载函数应用程序,因此可以将其隐式更改为(>>=)
,但是这样做的原则性方法使我难以理解。主要问题在于,函数a -> m b
既被视为在m
和共域b
中具有作用的有效函数,又被视为具有共域m b
的纯函数。我们如何推断何时使用($)
或(>>=)
?
在随机性的特殊情况下,我曾经有一个与之相关的想法,涉及可拆分的随机生成器(无耻插件):https://blog.poisson.chat/posts/2017-03-04-splittable-generators.html