Haskell RNG和状态

时间:2014-07-01 05:18:24

标签: haskell random functional-programming state monads

作为一个学习Haskell的Java人,我开始习惯于思考所有事情的新方式,但我花了半天的时间尝试用简单的RNG实现某些东西并且无处可去。在Java中,我可以创建一个静态RNG并使用Classname.random.nextInt(10)调用它,它将满足以下条件:

  1. 我不必保留对RNG的引用,我可以将其称为ad-hoc(即使是在循环内部或递归函数中)
  2. 每次调用时都会生成一个新的随机数
  3. 每次执行项目时都会生成一组新的随机数
  4. 到目前为止,在Haskell中,我面临着经典的程序员困境 - 我可以拥有2/3。我还在学习并且对Monads完全不了解,除了他们可能能够在这里帮助我。

    我最近的尝试是这样的:

    getRn :: (RandomGen g) => Int -> Int -> Rand g Int
    getRn lo hi= getRandomR (lo,hi)
    

    - 编辑:修剪我的问题,使其不再啰嗦,取而代之的是摘要,然后我最终做了什么:

    在创建了一堆随机城市(针对TSP)后,我使用函数createEdges对其进行了操作,该函数占用了一个城市并将其连接到其他城市:M.mapWithKey (\x y -> (x,(createEdges y [1..3] makeCountry)))

    问题:

    我想用随机的东西替换[1..3]。即我想在纯代码上映射随机性(IO)。这对我来说并没有造成混乱(看到人们试图在下面回答我,以便很好地理解我的困惑)。事实上,我甚至不确定我是否正确解释了问题。

    我遇到了这种类型的错误:Couldn't match expected type [Int] with actual type IO [Int]

    解决方案:

    因此,在发现我想要做的事情在功能环境中根本上是错误的之后,我决定改变我的方法。我没有生成城市列表然后应用随机性来连接它们,而是创建了一个[[Int]],其中每个内部列表代表随机边。从而在过程开始时创建我的随机性,而不是试图在纯代码上映射随机性。

    (我把最后的结果作为我自己的答案发布了,但是我不会让我接受我自己的答案。一旦它完成,我已经达到了这个门槛我会回来并接受)

4 个答案:

答案 0 :(得分:2)

如果您愿意,可以使用随机数而不需要任何monad或IO。 所有你必须知道的是,由于存在状态(随机数发生器的内部状态),你必须接受这个状态。

在我看来,最简单的框架是Sytem.Random

使用此getRn功能可能如下所示:

getRn :: (RandomGen g) => Int -> Int -> g -> (Int, g)
getRn lo hi g = randomR (lo,hi) g

在这里,您可以将g视为我上面提到的 - 你把它放进去,你得到另一个这样的回复(在ghci中):

> let init = mkStdGen 11
> let (myNr, nextGen) = getRn 1 6 init
> myNr
6
> let (myNr, nextGen') = getRn 1 6 nextGen
> myNr
4

我认为你可以从使用这个开始 - 将gen线程绕过,然后当你得到所有monad的东西回来并使它更容易写/读时。

我不知道您的数据的定义,但这是一个使用此技术的简单示例:

module StackOQuestion where

import System.Random

getRn :: (RandomGen g) => Int -> Int -> g -> (Int, g)
getRn lo hi = randomR (lo,hi)

getRnList :: (RandomGen g) => (g -> (a, g)) -> Int -> g -> ([a], g)
getRnList f n g
  | n <= 0    = ([], g)
  | otherwise = let (ls, g') = getRnList f (n-1) g
                    (a, g'') = f g'
                in  (a:ls, g'')

type City = (Int, Int)

randomCity :: (RandomGen g) => g -> (City, g)
randomCity g =
    let (f, g')  = getRn 1 6 g
        (s, g'') = getRn 1 6 g'
    in  ((f, s), g'')

randomCities :: (RandomGen g) => (Int, Int) -> g -> ([City], g)
randomCities (minC, maxC) g = 
  let (count, g') = getRn minC maxC g 
  in getRnList randomCity count g'

你可以这样测试:

> let init = mkStdGen 23
> randomCities (2,6) init
([(4,3),(1,2)],394128088 652912057)

正如您所看到的,这会创建两个 Cities (此处仅表示为整数对) - 对于init的其他值,您将获得其他答案。

如果你在这看起来正确,你可以看到 state-monad 已经开始了(g -> ('a, g)部分);)

PS:mkStdGen有点像你从Java和co中知道的随机初始化(你通常把系统时钟的滴答计数放入的部分) - 我选择11因为它很快输入;) - 当然,如果你坚持使用11,你将永远得到相同的数字 - 所以你需要用IO中的东西来初始化它 - 但你可以把这个包推到main并保持纯净,否则如果你只是通过然后g左右

答案 1 :(得分:2)

编辑:经过更多阅读和朋友的额外帮助后,我终于将其缩减为此解决方案。但是我会在答案中保留原来的解决方案,以防同样的方法帮助像我这样的新手(这也是我学习过程的重要部分)。

-- Use a unique random generator (replace <$> newStdGen with mkStdGen 123 for testing)
generateTemplate = createCitiesWeighted <$> newStdGen

-- create random edges (with weight as pair) by taking a random sized sample of randoms
multiTakePair :: [Int] -> [Int] -> [Int] -> [[(Int,Int)]]
multiTakePair ws (l:ls) is = (zip chunka chunkb) : multiTakePair remaindera ls remainderb
    where
        (chunkb,remainderb) = splitAt l is
        (chunka,remaindera) = splitAt l ws

-- pure version of utilizing multitake by passing around an RNG using "split"
createCitiesWeighted :: StdGen -> [[(Int,Int)]]
createCitiesWeighted gen = take count result
    where
        (count,g1) = randomR (15,20) gen
        (g2,g3) = split g1
        cs = randomRs (0, count - 2) g1
        es = randomRs (3,7) g2
        ws = randomRs (1,10) g3
        result = multiTakePair ws es cs

原始解决方案-----

除了@ user2407038的深刻见解外,我的解决方案非常依赖于我从这两个问题中读到的内容:

(注意:我遇到了一个问题,我无法弄清楚如何随机化每个城市会有多少边缘,@ AnrewC提供了一个非常好的响应,不仅回答了这个问题,而且还大量减少了多余的代码)

module TspRandom (
    generateCityTemplate
) where

import Control.Monad (liftM, liftM2) -- promote a pure function to a monad


-- @AndrewC's suggestion
multiTake :: [Int] -> [Int] -> [[Int]]
multiTake (l:ls) is = chunk : multiTake ls remainder
    where (chunk,remainder) = splitAt l is

-- Create a list [[Int]] where each inner int is of a random size (3-7)
-- The values inside each inner list max out at 19 (total - 1)
createCities = liftM (take 20) $ liftM2 multiTake (getRandomRs (3,7)) (getRandomRs (0, 19))

-- Run the generator
generateCityTemplate = do
    putStrLn "Calculating # Cities"
    x <- createCities
    print x
    return ()

答案 2 :(得分:2)

我想说如果你想使用随机数,最简单的方法就是使用像Control.Monad.Random这样的实用程序库。

更具教育性,工作密集度的途径是学会编写自己的monad。首先,您想了解State monad,并对此感到满意。我认为学习this older question(免责声明:我在那里有答案)可能是研究这个问题的一个很好的起点。我要采取的下一步是能够自己编写State monad。

在那之后,我将尝试的下一个练习是为随机数生成写一个“实用程序”monad。通过“实用程序”monad我的意思是一个monad,它基本上使用API​​重新打包标准State monad,这使得该特定任务更容易。这就是Control.Monad.Random包的实现方式:

-- | A monad transformer which adds a random number generator to an
-- existing monad.
newtype RandT g m a = RandT (StateT g m a)

他们的RandT monad实际上只是一个newtype定义,重用了StateT并添加了一些实用函数,以便您可以专注于使用随机数而不是而不是国家monad本身。因此,对于本练习,您基本上使用您想要的API设计随机数生成monad,然后使用StateRandom库来实现它。

答案 3 :(得分:0)

状态monad实际上非常简单。它只是从状态到值和新状态的函数,或者:

data State s a = State {getState :: s -> (s, a)}

事实上,这正是Rand monad所在。没有必要了解State使用Rand的机制。您不应该评估Rand内的IO,只需使用您用于do的相同IO表示法直接使用它。 do符号适用于任何 monad。

createCities :: Rand StdGen Int 
createCities = getRn minCities maxCities

x :: Cities -> X
x = ...

func :: Rand StdGen X
func = do 
  cities <- createCities
  return (x cities)

-- also valid
func = cities <$> createCities
func = createCities >>= return . x

你不能像写下来一样写getConnections。您必须执行以下操作:

getConnections :: City -> Country -> Rand StdGen [Int]
getConnections c country = do 
  edgeCount <- createEdgeCount 
  fromIndecies [] edgeCount (citiesExcludeSelf c country)

任何调用getConnections的函数都必须返回Rand StdGen x类型的值。只有在编写完整个算法并希望运行它之后,才能摆脱它。

然后,您可以使用evalRandIO func运行结果,或者,如果您想测试某些算法,并且希望在每次测试时为其提供相同的输入,则可以使用evalRand func (mkStdGen 12345),其中12345或任何其他数字,是您的种子价值。