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