纯函数如何做IO? [英] How can a pure function do IO?

查看:103
本文介绍了纯函数如何做IO?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近了解到 MonadRandom 库。它提供了一个名为 getRandomR 的函数,它的类型签名是:

  getRandomR ::(MonadRandom m,Random a)=> (a,a) - > ma 

显然,您可以编写一个函数,使用 getRandomR 哪些类型的签名不包含任何关于 IO 的内容。

  computeSomething :: MonadRandom m => Int  - > m Int 
computeSomething a = getRandomR(0,a)

根据调用者的不同,将填写 m 实例。如果它是从 IO 上下文运行,那么这个函数将是不纯的。

所以,问题是:如何能函数,不要求做 IO 实际上做 IO ?如何判断这个 computeSomething 函数是纯的还是不纯的?

解决方案

函数 getRandomR 不是在执行 IO 。一旦你拥有一个种子,就不需要做 IO 来产生随机数。在 MonadRandom 中的 Rand Monad使用种子进行初始化,该种子可以是您提供用于测试目的的种子,也可以是一个使用 evalRandIO 从IO中提取。 Rand Monad可以在不执行 IO 操作的情况下通过利用系统中公开的纯函数执行此操作.Random 来自随机包,例如随机 randomR 。每个函数都有一个生成器 g ,并返回一个新的生成器和一个所需类型的随机值。在内部, Rand Monad实际上就是 State Monad,其状态是生成器 g



然而,重要的是要注意 IO Monad是一个 MonadRandom 的实例,其中不是使用纯态函数,而是使用普通的 IO 函数,如 randomIO 。您可以交替使用 IO Rand ,但后者会更有效率(不必执行每次系统调用),你可以用一个已知的值为种子进行测试,以获得可重复的结果。

所以要回答你的问题


如何判断这个 computeSomething 函数是纯的还是不纯的?


对于 computeSomething 的这个定义,它既不是纯粹的也不是不纯的,直到 MonadRandom 已解决。如果我们把纯作为不是IO而不纯作为IO(不是完全准确,但近似值为),那么 computeSomething 在某些情况下可能是纯粹的,而在其他情况下可能是纯粹的,就像函数 liftM2 :: Monad m => (a1→a2→r)→> m a1 - > m a2 - > mr 可用于 IO Monad或 Maybe [] Monads。换句话说:

  liftM2(+)(Just 1)(Just 2)

总是会返回 Just 3 ,所以它可以被视为 pure ,而

  liftM2(++)getLine getLine 

不会总是返回相同的东西。尽管 MonadRandom 的每个预定义实例都被认为是不纯的( RandT Rand 有内部状态,所以它们在技术上是不纯的),你可以为自己的数据类型提供一个 MonadRandom 的实例,它总是返回相同的值,当 getRandom 或其他 MonadRandom 函数被调用。出于这个原因,我会说 MonadRandom 不是固有的纯粹或不纯净的。






也许有些代码会帮助解释它(简化,我跳过 RandT 转换器):

  import Control.Monad.State 
将合格的System.Random导入为R

class MonadRandom m其中
getRandom :: Random a = > m a
getRandoms :: Random a => m [a]
getRandomR :: Random a => (a,a) - > m a
getRandomRs :: Random a => (a,a) - > m [a]

- 不是真正的定义,MonadRandom库定义了一个RandT
- Monad变换器,其中Rand ga = RandT g Identity a,带有
- newtype RandT gma = RandT(StateT gma),但我想要
- 为这个例子保持简单。
newtype Rand ga = Rand {unRand :: State ga}

实例Monad(Rand g)其中
- 实现在此不相关

实例RandomGen g => MonadRandom(Rand g)其中
getRandom = state R.random
getRandoms = sequence $ repeat getRandom
getRandomR range = state(R.randomR range)
getRandomRs range = sequence $ repeat $ getRandomR范围

实例MonadRandom IO其中
getRandom = R.randomIO
getRandoms = sequence $ repeat getRandom
getRandomR范围= R.randomRIO范围
getRandomRs range = sequence $ repeat $ getRandomR range

所以当我们有一个函数时

  computeSomething :: MonadRandom m => Int  - > m Int 
computeSomething high = getRandomR(0,high)

然后我们可以使用它作为

  main :: IO()
main = do
i< - computeSomething 10
putStrLn $0到10之间的随机数:++ show i

或者

  main :: IO()
main = do
- evalRandIO使用getStdGen并将生成器传递给您
i < - evalRandIO $ computeSomething 10
putStrLn $0到10之间的随机数:++ show i

或者如果您想使用已知的生成器来测试:

  main :: IO ()
main = do
let myGen = R.mkStdGen 12345
i = evalRand(computeSomething 10)myGen
putStrLn $0到10之间的一个随机数:++ show在最后一种情况下,它会每次打印相同的数字,从而产生一个随机过程确定性和p URE。这使您能够通过提供显式种子来重新运行生成随机数的实验,或者可以传入系统的随机生成器一次,也可以直接使用 IO 在每次调用时获得一个新的随机生成器。所有这些都是可能的,而不必改变一行代码,而不是在 main 中调用它的方式, computeSomething 在这3次使用中不会改变。


I recently learned about the MonadRandom library. It gives you a function called getRandomR and its type signature is:

getRandomR :: (MonadRandom m, Random a) => (a, a) -> m a

Apparently, you can write a function that uses getRandomR who type signature doesn't contain anything about IO.

computeSomething :: MonadRandom m => Int -> m Int
computeSomething a = getRandomR (0, a)

Depending on the caller, the m instance will be filled out. If it's run from an IO context, the function will be impure.

So, the question: how can a function that doesn't claim to do IO actually do IO? How can one tell if this computeSomething function will be pure or impure?

解决方案

The function getRandomR is not doing IO. It is not required to do IO to generate random numbers once you have a seed. The Rand Monad in MonadRandom is initialized with a seed, that can either be one that you provide for testing purposes or one pulled from IO using evalRandIO. The Rand Monad can do this without performing IO actions by leveraging the pure functions exposed in System.Random from the random package, such as random and randomR. Each of these functions takes a generator g and returns a new generator and a random value of the desired type. Internally, the Rand Monad is really just the State Monad, and its state is the generator g.

However, it is important to note that the IO Monad is an instance of MonadRandom, where instead of using the pure state functions, it uses the normal IO functions like randomIO. You can use IO and Rand interchangeably, but the latter will be a bit more efficient (doesn't have to perform a system call each time) and you can seed it with a known value for testing purposes to get repeatable results.

So to answer your question

How can one tell if this computeSomething function will be pure or impure?

For this definition of computeSomething, it is neither pure or impure until the instance for MonadRandom is resolved. If we take "pure" to be "not IO" and "impure" to be "IO" (which is not entirely accurate, but a close approximation), then computeSomething can be pure in some instances and impure in others, just as the function liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r can be used on the IO Monad or on the Maybe or [] Monads. In other words:

liftM2 (+) (Just 1) (Just 2)

Will always return Just 3, so it can be considered pure, while

liftM2 (++) getLine getLine

Will not always return the same thing. While each predefined instance for MonadRandom would be considered impure (RandT and Rand have internal state, so they're technically impure), you could provide your own data type with an instance of MonadRandom for which it always returns the same value when getRandom or the other MonadRandom functions are called. For this reason, I would say that MonadRandom is not inherently pure or impure.


Maybe some code will help explain it (simplified, I'm skipping the RandT transformer):

import Control.Monad.State
import qualified System.Random as R

class MonadRandom m where
    getRandom   :: Random a => m a
    getRandoms  :: Random a => m [a]
    getRandomR  :: Random a => (a, a) -> m a
    getRandomRs :: Random a => (a, a) -> m [a]

-- Not the real definition, the MonadRandom library defines a RandT
-- Monad transformer where Rand g a = RandT g Identity a, with
-- newtype RandT g m a = RandT (StateT g m a), but I'm trying to
-- keep things simple for this example.
newtype Rand g a = Rand { unRand :: State g a }

instance Monad (Rand g) where
    -- Implementation isn't relevant here

instance RandomGen g => MonadRandom (Rand g) where
    getRandom = state R.random
    getRandoms = sequence $ repeat getRandom
    getRandomR range = state (R.randomR range)
    getRandomRs range = sequence $ repeat $ getRandomR range

instance MonadRandom IO where
    getRandom = R.randomIO
    getRandoms = sequence $ repeat getRandom
    getRandomR range = R.randomRIO range
    getRandomRs range = sequence $ repeat $ getRandomR range

So when we have a function

computeSomething  :: MonadRandom m => Int -> m Int
computeSomething high = getRandomR (0, high)

Then we can use it as

main :: IO ()
main = do
    i <- computeSomething 10
    putStrLn $ "A random number between 0 and 10: " ++ show i

Or

main :: IO ()
main = do
    -- evalRandIO uses getStdGen and passes the generator in for you
    i <- evalRandIO $ computeSomething 10
    putStrLn $ "A random number between 0 and 10: " ++ show i

Or if you wanted to use a known generator to test with:

main :: IO ()
main = do
    let myGen = R.mkStdGen 12345
        i = evalRand (computeSomething 10) myGen
    putStrLn $ "A random number between 0 and 10: " ++ show i

In the last case, it will print the same number every time, making a "random" process deterministic and pure. This gives you the ability to re-run experiments that generate random numbers by providing it an explicit seed, or you can pass in the system's random generator once, or you can use straight IO to get a new random generator with each call. All of this is possible without having to change a line of code other than how it's called in main, the definition of computeSomething doesn't change between those 3 uses.

这篇关于纯函数如何做IO?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆