如何阻止随机性在Haskell中渗透到我的代码中?

如何阻止随机性在Haskell中渗透到我的代码中?,haskell,random,monads,Haskell,Random,Monads,我正在尝试实现下面的算法,具体如下 从平坦地形开始,将所有高度值初始化为零 在地形上或附近拾取一个随机点,以及一个随机半径 在预定的最小值和最大值之间。精心挑选 该最小值和最大值将使地形变得崎岖不平、崎岖不平或平坦不平 滚动 在以点为中心的地形上升起一座小山,具有给定的坡度 半径 返回步骤2,根据需要重复多次。号码 选择的迭代次数将影响地形的外观 然而,当我到达必须在地形上选择随机点的点时,我开始挣扎。这个随机点被包装在一个IO单子中,然后传递给我的函数链 我可以在某一点切断IO吗?如果可以,我

我正在尝试实现下面的算法,具体如下

从平坦地形开始,将所有高度值初始化为零

在地形上或附近拾取一个随机点,以及一个随机半径 在预定的最小值和最大值之间。精心挑选 该最小值和最大值将使地形变得崎岖不平、崎岖不平或平坦不平 滚动

在以点为中心的地形上升起一座小山,具有给定的坡度 半径

返回步骤2,根据需要重复多次。号码 选择的迭代次数将影响地形的外观

然而,当我到达必须在地形上选择随机点的点时,我开始挣扎。这个随机点被包装在一个IO单子中,然后传递给我的函数链

我可以在某一点切断IO吗?如果可以,我如何找到该点

下面是我的坏代码。我将非常感谢任何关于改进它/阻止随机性影响一切的建议

type Point = (GLfloat, GLfloat, GLfloat)
type Terrain = [Point]

flatTerrain :: Double -> Double -> Double -> Double -> Terrain
flatTerrain width length height spacing =
    [(realToFrac x, realToFrac y, realToFrac z)
         | x <- [-width,-1+spacing..width], y <- [height], z <- [-length,-1+spacing..length]]

hill :: Terrain -> Terrain
hill terrain = hill' terrain 100
               where hill' terrain 0 = terrain
                     hill' terrain iterations = do
                       raised <- raise terrain
                       hill' (raise terrain) (iterations - 1)
                     raise terrain = do
                       point <- pick terrain
                       map (raisePoint 0.1 point) terrain
                     raisePoint r (cx,cy,cz) (px,py,pz) = 
                         (px, r^2 - ((cx - px)^2 + (cz - pz)^2), pz)

pick :: [a] -> IO a
pick xs = randomRIO (0, (length xs - 1)) >>= return . (xs !!)

不,你逃不过爱娥。也许你可以先做所有的随机性,然后重写你的函数,把随机性作为一个参数;如果没有,,您可以使用MonadRandom或类似工具来跟踪随机种子,也可以将所有内容放入IO。

该算法表示,您需要迭代,并在每次迭代中选择一个随机数并更新地形,该地形可被视为生成随机点列表,并使用该列表更新地形,即迭代生成随机数==随机数列表

因此,您可以执行以下操作:

selectRandomPoints :: [Points] -> Int -> IO [Points] -- generate Int times random points
updateTerrain :: Terrain -> [Points] -> Terrain

-- somewhere in IO
do
  pts <- selectRandomPoints allPts iterationCount
  let newTerrain = updateTerrain t pts   

haskell最有用的特性之一是知道函数是基于其类型确定的——这使测试更容易。出于这个原因,我将尽可能地限制随机性,并用随机变量包装核心非随机函数。使用MonadRandom类型类很容易做到这一点,这是在haskell中编写需要随机值的代码的最佳方法

为了好玩,我写了一个希尔生成器的控制台版本。它非常基本,有很多硬编码常量。但是,它确实提供了一个非常酷的ascii地形生成器:

注意,在我的解中,所有的计算都是孤立的,纯的,非随机函数。由于结果具有确定性,因此可以很容易地对其进行测试。IO单子中发生的事件尽可能少


你所说的预付款是指通过main?@sdadas,它是在main中还是在其他功能中都是无关紧要的;重要的是,在使用随机性进行任何计算之前,选择所有随机数或进行所有随机洗牌。我不太明白-如何防止这些计算函数变成IO?它们将在IO中,而不是在核心算法中。例如,在主函数或某些初始化函数的开头,您可以根据需要选择任意多个随机数,然后将它们传递给纯函数以生成/更新点或地形。公平地说,在任何语言中都无法阻止随机性影响一切,因为算法基于随机性。让你感到奇怪的是,Haskell实际上显示了哪些比特受到随机性的影响,而其他语言则没有。当然,正如Ankur所建议的,您可以将算法改为依赖于数字列表,然后在应用程序中从随机源获取这些数字。IO可以在某一点被切断。但是你从IO开始,直到截止点在IO。你不需要将代码的底层放在IO中,然后在向外工作时在某个时候将其切断,你可以做相反的事情:底层是纯函数,通常通过获取参数和返回结果,而不是直接执行IO来获取这些值或对结果做些什么,在某种程度上,当你向外工作的时候,你实际上把参数连接到了真实的IO上。你很棒,这也很棒。
import Control.Monad
import Control.Monad.Random
import Data.List
import Data.Function (on)

type Point = (Double, Double, Double)
type Terrain = [Point]

-- Non random code

flatTerrain :: Double -> Double -> Double -> Double -> Terrain
flatTerrain width length height spacing = [(realToFrac x, realToFrac y, realToFrac z)
         | x <- [-width,-width+spacing..width], y <- [height], z <- [-length,-length+spacing..length]]

-- simple terrain displayer, uses ascii to render the area.
-- assumes the terrain points are all separated by the same amount
showTerrain :: Terrain -> String
showTerrain terrain = unlines $ map (concat . map showPoint) pointsByZ where
  pointsByZ = groupBy ((==) `on` getZ) $ sortBy (compare `on` getZ) terrain
  getZ (_, _, z) = z
  getY (_, y, _) = y

  largest = getY $ maximumBy (compare `on` getY) terrain
  smallest = getY $ minimumBy (compare `on` getY) terrain
  atPC percent = (largest - smallest) * percent + smallest

  showPoint (_, y, _)
    | y < atPC (1/5) = " "
    | y < atPC (2/5) = "."
    | y < atPC (3/5) = "*"
    | y < atPC (4/5) = "^"
    | otherwise = "#"

addHill :: Double -- Radius of hill
        -> Point -- Position of hill
        -> Terrain -> Terrain
addHill radius point = map (raisePoint radius point) where
  raisePoint :: Double -> Point -> Point -> Point
  -- I had to add max py here, otherwise new hills destroyed the
  -- old hills with negative values.
  raisePoint r (cx,cy,cz) (px,py,pz) = (px, max py (r^2 - ((cx - px)^2 + (cz - pz)^2)), pz)

-- Some random variants. IO is an instance of MonadRandom, so these function can be run in IO. They
-- can also be run in any other monad that has a MonadRandom instance, so they are pretty flexible.

-- creates a random point. Note that the ranges are hardcoded - an improvement would
-- be to be able to specify them, either through parameters, or through reading from a Reader
-- monad or similar
randomPoint :: (MonadRandom m) => m Point
randomPoint = do
  x <- getRandomR (-30, 30)
  y <- getRandomR (0,10)
  z <- getRandomR (-30, 30)
  return (x, y, z)

addRandomHill :: (MonadRandom m) => Terrain -> m Terrain
addRandomHill terrain = do
  radius <- getRandomR (0, 8) -- hardcoded again
  position <- randomPoint
  return $ addHill radius position terrain

-- Add many random hills to the Terrain
addRandomHills :: (MonadRandom m) => Int -> Terrain -> m Terrain
addRandomHills count = foldr (>=>) return $ replicate count addRandomHill

-- testing code

test hillCount = do
  let terrain = flatTerrain 30 30 0 2
  withHills <- addRandomHills hillCount terrain
  -- let oneHill = addHill 8 (0, 3, 0) terrain
  -- putStrLn $ showTerrain oneHill
  putStrLn $ showTerrain withHills

main = test 200
... ..     ..*.  .***^^^***.   
... ...   .***.  .***^^^*^^*.  
... ..    .*^**......*^*^^^^.  
       .  .***.***.  ..*^^^*.  
      ....*^^***^*.   .^##^*.  
     ..*.*^^^*****.   .^###^..*
      .**^^^^.***...  .*^#^*.**
     .***^##^**..*^^*.*****..**
  ....***^^##^*.*^##^****.   ..
  .......*^###^.*###^****.     
.*********^###^**^##^***....   
*^^^*^##^^^^###^.^^^*. .****.. 
*^^^^####*^####^..**.  .******.
*^^^*####**^###*. ..   .*******
*^#^^^##^***^^*. ...........***
*^^^**^^*..*... ..*******...***
.***..*^^*...  ..*^^#^^^*......
  ...*^##^**.  .*^^#####*.     
    .*^##^**....**^^####*. .***
.. ..*^^^*...*...**^^###^* *^#^
..****^^*. .... ...**###^*.^###
..*******.**.  ..**^^^#^^..^###
 .*****..*^^* ..**^##^**...*^##
.^^^^....*^^*..*^^^##^* ..**^^^
*###^*. .*^**..^###^^^*...*****
^####*.*..*^^*.^###^**.....*.. 
*###^**^**^^^*.*###^. ..   .   
.^^^***^^^^#^*.**^^**.         
 .....***^##^**^^^*^^*.        
      .*^^##^*^##^^^^^.        
      .*^^^^*.^##^*^^*.