Случайное число в Haskell
Я пытаюсь получить случайное число в Haskell. (Который я в настоящее время участвую и не получаю Monads или IO и т.д.), Проблема заключается в функциях в System.Random, все возвращают IO Int, которые я тогда не могу использовать в остальной части моего кода, который использует Int и Float.
Цель состоит в том, чтобы выбрать пару из списка, где первая из пары представляет собой поплавок, представляющий вероятность. Поэтому мой план состоял в том, чтобы использовать случайное число, чтобы выбрать пару на основе ее вероятности.
Ответы
Ответ 1
Это общий барьер для новых программистов Haskell. Вы хотите избежать ввода-вывода, и для определения оптимального пути требуется некоторое время. Учебник Learn You A Haskell имеет хорошее объяснение одного способа генерации случайных чисел с использованием государственной монады, но его все еще нужно высевать с помощью getStdGen
или newStdGen
, которые находятся в IO.
Для простых случаев вы можете просто сделать что-то вроде
myPureFunction :: Float -> Float
myPureFunction x = 2 * x
main :: IO ()
main = do
-- num :: Float
num <- randomIO :: IO Float
-- This "extracts" the float from IO Float and binds it to the name num
print $ myPureFunction num
Итак, вы видите, вы можете получить свое случайное число в main
, а затем передать это значение чистой функции, которая выполняет обработку.
Вы можете спросить себя, почему все это работает для генерации случайных чисел в Haskell. Существует множество веских причин, большинство из которых связаны с системой типов. Поскольку генерация случайных чисел требует изменения состояния StdGen в операционной системе, она должна находиться внутри IO
, иначе вы можете иметь чистую функцию, которая дает вам разные результаты каждый раз.
Представьте себе этот надуманный сценарий:
myConstant :: Int
myConstant = unsafePerformIO randomIO
blowUpTheWorld :: IO ()
blowUpTheWorld = error "Firing all the nukes"
main :: IO ()
main = do
if even myConstant
then print "myConstant is even"
else blowUpTheWorld
Если вы запустили это несколько раз, скорее всего, вы закончите "стрельбу из всех ядер". Очевидно, это плохо. myConstant
должен быть, ну, постоянным, но каждый раз, когда вы запускаете программу, вы получите другое значение. Haskell хочет гарантировать, что чистая функция всегда будет возвращать одно и то же значение при тех же входах.
Это может раздражать прямо сейчас, но это мощный инструмент в наборе функциональных программистов.
Ответ 2
Здесь есть хорошие ответы, но я чувствовал, что более полный ответ будет очень просто показать, как получить и использовать случайные числа в Haskell, чтобы это имело смысл для настоящих программистов.
Сначала вам понадобится случайное семя:
import System.Random
newRand = randomIO :: IO Int
Поскольку newRand
имеет тип IO Int
, а не Int
, он не может использоваться как параметр функции. (Это сохраняет функции Haskell как чистые функции, которые всегда будут возвращать один и тот же результат на одном и том же входе.)
Мы можем, однако, просто ввести newRand
в GHCI и каждый раз получать уникальное случайное семя. Это возможно только потому, что newRand
имеет тип IO
и не является стандартной (неизменяемой) переменной или функцией.
*Main> newRand
-958036805781772734
Затем мы можем скопировать и вставить это начальное значение в функцию, которая создает для нас список случайных чисел. Если мы определим следующую функцию:
randomList :: Int -> [Double]
randomList seed = randoms (mkStdGen seed) :: [Double]
И вставьте заданное семя, когда функция запускается в GHCI:
*Main> take 10 randomList (-958036805781772734)
[0.3173710114340238,0.9038063995872138,0.26811089937893495,0.2091390866782773,0.6351036926797997,0.7343088946561198,0.7964520135357062,0.7536521528870826,0.4695927477527754,0.2940288797844678]
Обратите внимание, как мы получаем знакомые значения от 0 до 1 (исключая). Вместо того, чтобы генерировать новое случайное число на каждой итерации, как и на императивном языке, мы заранее генерируем список случайных чисел и используем голову хвоста списка при каждой последующей рекурсии. Пример:
pythagCheck :: [Double] -> [Double] -> [Int]
pythagCheck (x:xs) (y:ys)
| (a^2) + (b^2) == (c^2) = [a, b, c]
| otherwise = pythagCheck xs ys
where aplusb = ceiling (x * 666)
a = ceiling (y * (fromIntegral (aplusb - 1)))
b = aplusb - a
c = 1000 - a - b
Создание двух списков заблаговременно и подача их в качестве параметров позволяет нам искать (одну и ту же!) пифагорейскую тройку, где a + b + c = 1000. Вы, конечно, хотели бы использовать разные случайные семя для каждого списка:
*Main> newRand
3869386208656114178
*Main> newRand
-5497233178519884041
*Main> list1 = randomList 3869386208656114178
*Main> list2 = randomList (-5497233178519884041)
*Main> pythagCheck list1 list2
[200,375,425]
Ответ 3
Как уже говорилось, случайные числа не могут быть действительно чистыми значениями 1.
Однако на самом деле вам не нужно беспокоить вас. Просто посмотрите на это наоборот: на других языках просто нет такой вещи, как чистые ценности, она всегда заявляет о реальных вмешательствах, с которыми вы имеете дело. Хаскелл может это сделать и в монаде IO
. Вам не нужно знать, как именно это работает, просто подражайте тому, как это будет выглядеть на процедурном языке (здесь есть несколько подводных камней).
Прежде всего, вам нужен какой-то алгоритм, который не имеет никакого отношения к языку. Очевидным способом является накопление вероятностей в списке и использование результирующей ступенчатой функции в виде карты из [0, 1 [к вашим желаемым значениям.
probsListLookup :: [(Double, a)] -> Double -> a
probsListLookup pAssoc = look acc'dList
where acc'dList = scanl1 (\(pa,_) (pn,x) -> (pa+pn,x)) pAssoc
look ((pa, x) : pas) rval
| rval < pa = look pas rval
| otherwise = x
Обратите внимание, что это не очень хорошо справляется с недопустимыми входами (вероятности не суммируются до 1 и т.д.) и неэффективно, скремблируя O (n) через acc'dList
для каждого запрошенного значения 2. Еще важнее отметить, что это чистая функция! Как правило, рекомендуется использовать чистые функции как можно больше и только входить в IO
, когда это абсолютно необходимо. Как и сейчас: нам нужно получить одно значение Double
между 0 и 1. Легко!
main = do
lookupVal <- randomRIO (0, 1)
print $ probsListLookup [(0.1, 1), (0.2, 2), (0.3, 4), (0.4, 5)] lookupVal
1 По крайней мере, не такой базовый тип, как Int
; вы могли бы действительно делать "чистые вычисления" на всех вероятностных распределениях. Выполнение этого явно очень громоздко, но Haskell позволяет использовать конкретную монаду (или на самом деле comonads), чтобы сделать это так же просто, как и в Haskell IO (или на любом нечистом языке), но без опасностей ввода/вывода.
2 Вы можете улучшить это, например, с Data.Map
.
Ответ 4
Я не думаю, что эти ответы - вся картина. Для моих симуляций я генерирую случайные числа лениво и потребляю их строго в небольшом пространстве (1.1M на моем macbook).
Возможно, замечание о том, что случайные числа могут существовать только в монаде IO, относятся к действительно случайным числам, но для псевдослучайных чисел это не так, и обычно один хочет иметь возможность воспроизводить результаты. Вот пример:
module Main (
main
) where
import qualified Data.Vector.Unboxed as V
import Data.Random.Source.PureMT
import Data.Random
import Control.Monad.State
nItt :: Int
nItt = 1000000000
gridSize :: Int
gridSize = 10
testData :: Int -> V.Vector Double
testData m =
V.fromList $
evalState (replicateM m (sample (uniform (0 :: Double) 1.0)))
(pureMT 2)
test = V.foldl (+) 0 (testData nItt)
main = putStrLn $ show test
Ответ 5
Если вам нужна истинная случайность, вы не сможете использовать IO - звуки, подобные перетаскиванию, но это разделение является действительно важным аспектом Haskell. Однако вы можете получить полу-псевдослучайность, выбрав "семя" самостоятельно и используя чистые функции из System.Random, которые возвращают пару результатов и новое семя (например, "random" ).