简体   繁体   English

在Haskell中使用IO Bool进行列表理解

[英]List comprehension with IO Bool in Haskell

I'm trying to find probable Mersenne primes by checking Mersenne numbers with the Miller Rabin primality check. 我正在尝试通过Miller Rabin素数检查来检查Mersenne数,以找到可能的Mersenne素数。

If mersennes is the infinite list of Mersenne numbers, I would like to do something like: 如果mersennes是Mersenne数的无限列表,我想做些类似的事情:

probableMersennePrimes :: IO [Integer]
probableMersennePrimes = [ n | n <- mersennes, primeMillerRabin n ]

Where primeMillerRabin :: Integer -> IO Bool . 其中primeMillerRabin :: Integer -> IO Bool

As a side question, should the type be IO [Integer] or [IO Integer] ? 作为附带问题,类型应该是IO [Integer]还是[IO Integer]

You can stream the infinite list of numbers using ListT from pipes : 您可以使用pipes ListT流式传输数字的无限列表:

import Control.Monad
import Data.Monoid
import Pipes

mersennes :: [Integer]
mersennes = undefined

primeMillerRabin :: Integer -> IO Bool
primeMillerRabin = undefined

probableMersennePrimes :: ListT IO Integer
probableMersennePrimes = do
    n        <- Select (each mersennes)
    continue <- lift (primeMillerRabin n)
    guard continue
    return n

main = runListT $ do
    n <- probableMersennePrimes
    lift (print n)
    mempty

This works even if the list of mersennes is infinite and it will stream results as you compute them. 即使mersennes列表是无限的,它也可以工作,并且在您计算结果时将流式传输结果。

If you change primeMillerRabin to be of type RandomGen g => Integer -> State g Bool , you can do this with filterM . 如果将primeMillerRabin更改为RandomGen g => Integer -> State g Bool ,则可以使用filterM进行此filterM

probableMersennePrimes :: RandomGen g => g -> [Integer]
probableMersennePrimes = evalState $ filterM primeMillerRabin mersennes

By using evalState , we abandon the final state of the filterM primeMillerRabin mersennes computation, so we cannot be strict in it. 通过使用evalState ,我们放弃了filterM primeMillerRabin mersennes计算的最终状态,因此我们对此并不严格。 But this is a good thing, as the final state will only be available after we reach process the end of the mersennes list, which is infinite, and has no end. 但这是一件好事,因为最终状态只有在到达进程的mersennes列表的末尾mersennes ,该列表是无限的,并且没有结束。

This allows the computation to lazily produce elements of probableMersennePrimes gen . 这允许计算延迟生成probableMersennePrimes gen元素。

You can't do it while it returns an IO action, because of how the random number generator works. 由于随机数生成器的工作原理,您无法在返回IO操作时执行此操作。 The IO computation needs to know what the end state is so it can generate another random number after that computation, so it has to loop endlessly, looking for the end of the endless list. IO计算需要知道结束状态是什么,以便可以在计算之后生成另一个随机数,因此它必须无限循环,以寻找无限列表的末尾。

But don't just believe me, try it out: 但是,不要只是相信我,可以尝试一下:

module SO26307073 where
import Control.Monad.State
import System.Random

-- find how many times a factor divides a number
-- (p^s * d) `factorBy` p == (s,d) iff d `rem` p /= 0
factorBy :: Integral a => a -> a -> (Int,a)
factorBy n p = (length steps - 1, fst $ last steps)
  where steps = takeWhile ((==0) . snd) $ iterate (flip quotRem 2 . fst) (n, 0)

mersennes :: Num a => [a]
mersennes = [ 2^n - 1 | n <- [2..] ]

type RandomRM m = (Integer, Integer) -> m Integer

primeMillerRabinWith :: Monad m => RandomRM m ->  Integer -> m Bool
primeMillerRabinWith randomRM n = do
  let nMinus1 = n-1
      (s,d) = nMinus1 `factorBy` 2
  liftM (all id) . replicateM 10 $ do
    a <- randomRM (2, nMinus1)
    let x = (a^d) `mod` n
    let xs = take s $ iterate (\x -> (x^2) `mod` n) x
    return $ x == 1 || any (== nMinus1) xs

probableMersennePrimesWith :: Monad m => RandomRM m -> m [Integer]
probableMersennePrimesWith randomRM = filterM (primeMillerRabinWith randomRM) mersennes

probableMersennePrimesPure :: RandomGen g => g -> [Integer]
probableMersennePrimesPure = evalState . probableMersennePrimesWith $ state . randomR

probableMersennePrimesIO :: IO [Integer]
probableMersennePrimesIO = probableMersennePrimesWith $ randomRIO

Note that probableMersennePrimesIO and probableMersennePrimesPure just use different ways to draw randoms. 请注意, probableMersennePrimesIOprobableMersennePrimesPure仅使用不同的方式绘制随机数。

Popping over to ghci, we can see that the pure version works, while the IO version just hangs: 弹出ghci,我们可以看到纯版本可以正常工作,而IO版本只是挂起:

λ import Control.Applicative
λ import System.Random
λ :l SO26307073
[1 of 1] Compiling SO26307073      ( SO26307073.hs, interpreted )
Ok, modules loaded: SO26307073.
λ take 0 . probableMersennePrimesPure <$> newStdGen
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
Loading package old-locale-1.0.0.6 ... linking ... done.
Loading package time-1.4.2 ... linking ... done.
Loading package random-1.0.1.1 ... linking ... done.
Loading package transformers-0.4.1.0 ... linking ... done.
Loading package mtl-2.2.1 ... linking ... done.
[]
λ take 5 . probableMersennePrimesPure <$> newStdGen
[3,7,31,127,8191]
λ take 5 <$> probableMersennePrimesIO 
^CInterrupted.

All other answers are great. 所有其他答案都很好。 However, I think the simplest approach to this problem is lazy IO and it's worth considering. 但是,我认为解决此问题的最简单方法是惰性IO,值得考虑。 I also believe that in this special case lazy IO is harmless since there are no system resources involved (file handles etc.) 我还认为,在这种特殊情况下,由于不涉及系统资源(文件句柄等),因此惰性IO是无害的。

You just need to redefine a special filterM for lazy IO. 您只需要为惰性IO重新定义特殊的filterM

import System.IO.Unsafe (unsafeInterleaveIO)

filterMIO :: (a -> IO Bool) -> [a] -> IO [a]
filterMIO p = go
  where
    go []     = return []
    go (x:xs) = do
      xs' <- unsafeInterleaveIO (go xs)
      b   <- p x
      return $ if b then (x:xs') else xs'

probableMersennePrimes :: IO [Integer]
probableMersennePrimes = filteMIO primeMillerRabin mersennes

Note that this will just work fine with infinite lists. 请注意,这将对无限列表有效。 That's why we've used lazy IO, actually! 这就是为什么我们实际上使用了惰性IO的原因!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM