使用State Monad将我所有的功能转换为Monadic函数

时间:2019-01-09 06:27:14

标签: haskell monads state-monad

我在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'代替。该解决方案提出了两个问题。

  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之前还是之后进行“剪切”。

1 个答案:

答案 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