从编译器错误消息来看,问题似乎是由于您的随机数生成器包含在 IO monad 中,因为您使用了函数 newStdGen。
您可以改用函数mkStdGen 来获得解包的生成器。该函数将一个 Int 类型的参数作为生成器的种子。
例如,这段代码编译:
randomize :: (Random a, Num a) => Int -> Tree a -> Tree a
randomize seed tree = evalState ( mapRandom (randomR (0,1)) tree) (mkStdGen seed) where
mapRandom f (Leaf a) = Leaf <$> state f
mapRandom f (Unary t1) = Unary <$> mapRandom f t1
mapRandom f (Binary t1 t2) = Binary <$> mapRandom f t1 <*> mapRandom f t2
mapRandom f (Ternary t1 t2 t3) = Ternary <$> mapRandom f t1
<*> mapRandom f t2 <*> mapRandom f t3
作为奖励,您可以随意复制相同的随机数序列(通过再次传递相同的种子),这是newStdGen 不提供的。
函数newStdGen 使用系统时钟来生成种子,因此需要涉及 IO monad。
附录:
如何避免重复遍历树的代码
上面的函数randomize 有效。然而,它并不完全令人满意:它涉及树遍历的算法,它决定使用哪个范围的值,以及哪种类型的随机数生成器。所以它似乎在计算机编程中被称为Single Responsibility Principle (SRP) 的东西失败了。
有一天可能需要不同的范围。此外,您可以假设Threefish 随机数生成器比标准 Haskell 具有更好的统计特性,并希望使用它的Haskell implementation。
阻力最小的路径是克隆randomize 的代码并将mkStdGen 替换为mkTFGen。但是此时,树遍历代码会重复。并且有许多潜在的重复。我们应该找到更好的方法。
一般问题是使用有状态映射生成初始树的新版本。到目前为止,我们忽略了初始树中的 值,但这只是针对随机输出树的特殊情况。
通常,所需转换函数的类型签名必须是:
statefulTreeMap :: (a -> s -> (b,s)) -> s -> Tree a -> (Tree b, s)
其中s 是状态 的类型。在randomize 的情况下,状态只是随机数生成器的(当前状态)。
您可以轻松地手动编写statefulTreeMap 的代码,使用如下子句:
statefulTreeMap step st0 (Binary tra1 tra2) =
let (trb1, st1) = statefulTreeMap step st0 tra1
(trb2, st2) = statefulTreeMap step st1 tra2
in (Binary trb1 trb2 , st2)
但这并不是最Haskellish的方式。
事实证明,这与mapAccumL 库函数非常相似。 Haskell 语言库使mapAccumL 可用于属于Traversable 类型类的任何实体。请注意,@amalloy 在其中一个 cmets 中提到了 Traversable 类型类。
所以我们可以尝试让我们的Tree 类型为Traversable 的实例,然后使用函数mapAccumL。
这可以通过显式提供函数traverse的代码来完成:
instance Traversable Tree where
traverse fn (Unary ta) = Unary <$> (traverse fn ta)
traverse fn (Binary ta tb) = Binary <$> (traverse fn ta) <*> (traverse fn tb)
traverse fn (Ternary ta tb tc) = Ternary <$> (traverse fn ta) <*> (traverse fn tb) <*> (traverse fn tc)
traverse fn (Leaf a) = Leaf <$> fn a
但这甚至没有必要。相反,可以召唤编译器重炮(至少在足够近的版本中),并通过启用DeriveTraversable 语言扩展来让它生成traverse 的Tree 版本:
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
import qualified Data.Tuple as T
import qualified Data.Traversable as TR
import System.Random
import qualified System.Random.TF as TF
import Control.Monad.State
data Tree a = Unary (Tree a) | Binary (Tree a) (Tree a) |
Ternary (Tree a) (Tree a) (Tree a) | Leaf a
deriving (Eq, Show, Functor, Foldable, Traversable)
然后我们可以通过在我们刚刚免费获得的mapAccumL 函数周围放置一些管道代码来获得目标statefulTreeMap 函数的通用版本:
-- general solution for any Traversable entity:
statefulMap :: Traversable tr => (a -> s -> (b,s)) -> s -> tr a -> (tr b, s)
statefulMap step st0 tra =
let fn = \s y -> T.swap (step y s)
p = TR.mapAccumL fn st0 tra -- works in reverse if using mapAccumR
in
T.swap p
我们可以立即将其专门用于Tree 对象:
statefulTreeMap :: (a -> s -> (b,s)) -> s -> Tree a -> (Tree b, s)
statefulTreeMap = statefulMap
至此我们几乎完成了。我们现在可以通过提供更多管道代码来编写多个版本的randomize:
-- generic random tree generation, with range and generator as external parameters:
randomize2 :: (RandomGen gt, Random b, Num b) => (b,b) -> gt -> Tree a -> Tree b
randomize2 range gen tra =
let step = (\a g -> randomR range g) -- leftmost parameter ignored
in fst $ statefulTreeMap step gen tra -- drop final state of rng
-- version taking just a seed, with output range and generator type both hardwired:
randomize3 :: (Random b, Num b) => Int -> Tree a -> Tree b
randomize3 seed tra = let rng = TF.mkTFGen seed
range = (0,9)
in randomize2 range rng tra
测试代码:
main = do
let seed = 4243
rng0 = TF.mkTFGen seed
tr1 = Ternary (Ternary (Leaf 1) (Leaf 2) (Leaf 3))
(Leaf (4::Integer))
(Binary (Leaf 12) (Leaf 13))
tr11 = (randomize seed tr1) :: Tree Integer
tr12 = (randomize2 (0,9) rng0 tr1) :: Tree Integer
tr13 = (randomize3 seed tr1) :: Tree Integer
putStrLn $ "tr1 = " ++ (show tr1) ++ "\n"
putStrLn $ "tr11 = " ++ (show tr11)
putStrLn $ "tr12 = " ++ (show tr12)
putStrLn $ "tr13 = " ++ (show tr13)
putStrLn $ "tr11 == tr12 = " ++ (show (tr11 == tr12))
putStrLn $ "tr11 == tr13 = " ++ (show (tr11 == tr13))
程序输出:
tr1 = Ternary (Ternary (Leaf 1) (Leaf 2) (Leaf 3)) (Leaf 4) (Binary (Leaf 12) (Leaf 13))
tr11 = Ternary (Ternary (Leaf 9) (Leaf 6) (Leaf 0)) (Leaf 3) (Binary (Leaf 2) (Leaf 6))
tr12 = Ternary (Ternary (Leaf 9) (Leaf 6) (Leaf 0)) (Leaf 3) (Binary (Leaf 2) (Leaf 6))
tr13 = Ternary (Ternary (Leaf 9) (Leaf 6) (Leaf 0)) (Leaf 3) (Binary (Leaf 2) (Leaf 6))
tr11 == tr12 = True
tr11 == tr13 = True
因此,事实上,我们消除了对任何显式树遍历代码的需求。
旁注:
当然,statefulTreeMap 函数可以用于与伪随机无关的任务。例如,我们可能希望为 Tree 对象的元素赋予连续的数字:
enumerate :: Tree a -> Tree (a, Int)
enumerate = fst . (statefulTreeMap (\a rs -> ((a, head rs), tail rs)) [0..])
在ghci下测试:
λ>
λ> enumerate tr1
Ternary (Ternary (Leaf (1,0)) (Leaf (2,1)) (Leaf (3,2))) (Leaf (4,3)) (Binary (Leaf (12,4)) (Leaf (13,5)))
λ>