(Отредактировано) Как получить случайное число в Хаскеле без ввода-вывода
Я хочу иметь функцию, которая возвращает разные stdGen
в каждом звонке без IO. Я пытался использовать unsafePerformIO
, как следующий код.
import System.IO.Unsafe
import System.Random
myStdGen :: StdGen
myStdGen = unsafePerformIO getStdGen
Но когда я пытаюсь позвонить myStdGen
в ghci я всегда получаю одно и то же значение. Я злоупотребил unsafePerformIO
? Или есть другие способы достичь моей цели?
РЕДАКТИРОВАТЬ Извините, я думаю, что я должен описать свой вопрос более точно.
На самом деле, я реализую вариант структуры данных Treap, для которой требуется специальная операция "слияния". Он полагается на некоторую случайность, чтобы гарантировать ожидаемую сложность времени амортизированного O(log n).
Я пытался использовать пару как (Tree, StdGen)
сохранить генератор случайных чисел для каждого трепа. Вставляя новые данные в треп, я бы использовал random
дать случайное значение новому узлу, а затем обновить мой генератор. Но я столкнулся с проблемой. У меня есть функция под названием empty
который вернет пустой треп, и я использовал функцию myStdGen
выше, чтобы получить генератор случайных чисел для этого трепа. Однако, если у меня есть два пустых трепа, их StdGen
будет таким же. Поэтому после того, как я вставил данные в оба трепа и когда я хочу их объединить, их случайное значение также будет одинаковым. Поэтому я потерял случайность, на которую полагаюсь.
Вот почему я хотел бы иметь как-то "глобальный" генератор случайных чисел, который дает разные StdGen
для каждого вызова, так что каждый пустой треп может иметь разные StdGen
,
3 ответа
Это не очень хорошее применение unsafePerformIO
,
Причина, по которой вы постоянно видите одно и то же число в GHCi, заключается в том, что сам GHCi не знает, что значение является нечистым, и поэтому запоминает значение с момента последнего вызова. Вы можете вводить команды ввода-вывода в верхний уровень GHCi, так что вы ожидаете увидеть другое значение, если вы просто наберете getStdGen
, Однако это тоже не сработает из-за непонятной части работы GHCi, связанной с не возвращением выражений верхнего уровня. Вы можете включить это с :set +r
:
> :set +r
> getStdGen
2144783736 1
> getStdGen
1026741422 1
Обратите внимание, что ваша нечистая функция, притворяющаяся чистой, все равно не будет работать.
> myStdGen
480142475 1
> myStdGen
480142475 1
> myStdGen
480142475 1
Вы действительно не хотите идти по этому пути. unsafePerformIO
не должен использоваться таким образом, и при этом это не хорошая идея вообще. Есть способы получить то, что вы хотели (например, unsafePerformIO randomIO :: Int
) но они не приведут вас к хорошим вещам. Вместо этого вы должны выполнять вычисления, основанные на случайных числах внутри случайной монады, и выполнять их в монаде IO.
Обновить
Я вижу из твоего обновления, почему ты хотел этого в первую очередь.
В этом ответе есть много интересных мыслей по проблеме случайности внутри иначе ссылочно-прозрачных функций.
Несмотря на то, что некоторые люди выступают за использование unsafePerformIO
в этом случае это все еще плохая идея по ряду причин, которые изложены в различных частях этой страницы. В конце концов, если функция зависит от источника случайности, лучше указать ее в своем типе, и самый простой способ сделать это - поместить ее в случайную монаду. Это все еще чистая функция, только та, которая принимает генератор при вызове. Вы можете предоставить этот генератор, запросив случайный в главном IO
рутина.
Хороший пример того, как использовать случайную монаду, можно найти здесь.
Я оскорблял
unsafePerformIO
Черт возьми, да! "Отличительными чертами чистой функции" являются
- Нет побочных эффектов
- Относительно прозрачный, т. Е. Каждое последующее вычисление результата должно давать одно и то же.
На самом деле есть способ достичь своей "цели", но идея просто ошибочна.
myStdGen :: () -> StdGen
myStdGen () = unsafePerformIO getStdGen
Поскольку это (бесполезный) вызов функции вместо CAF, он оценит IO
действие на каждый звонок отдельно.
Тем не менее, я думаю, что компилятор в значительной степени свободен, чтобы оптимизировать это, так что определенно не полагайтесь на это.
РЕДАКТИРОВАТЬ при попытке я заметил, что getStdGen
сам по себе всегда дает один и тот же генератор при использовании в данном процессе, поэтому даже если в приведенном выше примере используются более разумные типы, он не будет работать.
Обратите внимание, что правильное использование псевдослучайности в алгоритмах и т. Д.Не требует ввода-вывода везде - например, вы можете вручную заполнить ваш
StdGen
, но затем правильно распространять это с split
и т. д. Гораздо более приятный способ справиться с этим - с помощью монады случайности. Программа в целом будет всегда давать один и тот же результат, но внутренне иметь все разные случайные числа, необходимые для полезной работы.В качестве альтернативы, вы можете получить генератор из IO, но все же написать свой алгоритм в чистой случайной монаде, а неIO
,
Есть другой способ получить "случайность" в полностью чистом алгоритме: требуется, чтобы вход былHashable
! Затем вы можете эффективно использовать любой аргумент функции в качестве случайного начального числа. Это немного странное решение, но оно может работать для вашего приложения Treap (хотя я считаю, что некоторые люди больше не будут классифицировать его как Treap, а как особый вид hashmap).
Да, вы злоупотребили unsafePerformIO
, Есть очень мало веских причин для использования unsafePerformIO
Например, при взаимодействии с библиотекой C, а также при реализации нескольких основных библиотек (я думаю, ST
будучи одним из них). Короче говоря, не используйте unsafePerformIO
если вы действительно не уверены в том, что делаете. Он не предназначен для генерации случайных чисел.
Напомним, что функции в Haskell должны быть чистыми, то есть они зависят только от их входных данных. Это означает, что у вас может быть чистая функция, которая генерирует "случайное" число, но это число зависит от генератора случайных чисел, который вы передаете ему, вы можете сделать что-то вроде
myStdGen :: StdGen
myStdGen = mkStdGen 42
Тогда вы могли бы сделать
randomInt :: StdGen -> (Int, StdGen)
randomInt g = random
Но тогда вы должны использовать новый StdGen
вернулись из этой функции, двигаясь вперед, или вы всегда будете получать один и тот же вывод из randomInt
,
Так что вы можете быть удивлены, как вы генерируете случайные числа, не прибегая к IO
? Ответ State
монада. Это похоже на
newtype State s a = State { runState :: s -> (a, s) }
И его экземпляр монады выглядит как
instance Monad (State s) where
return a = State $ \s -> (a, s)
(State m) >>= f = State $ \s -> let (a, newState) = m s
(State g) = f a
in g newState
С первого взгляда это немного сбивает с толку, но, по сути, все, что делает монада состояний - это сложная композиция функций. Смотрите LYAH для более подробного объяснения. Здесь важно отметить, что тип s
не меняется между шагами, просто a
параметр может измениться.
Вы заметите, что s -> (a, s)
выглядит очень похоже на нашу функцию StdGen -> (Int, StdGen)
, с s ~ StdGen
а также a ~ Int
, Это означает, что если бы мы сделали
randomIntS :: State StdGen Int
randomIntS = State randomInt
Тогда мы могли бы сделать
twoRandInts :: State StdGen (Int, Int)
twoRandInts = do
a <- randomIntS
b <- randomIntS
return (a, b)
Затем его можно запустить, указав начальное состояние:
main = do
g <- getStdGen
print $ runState twoRandInts g
StdGen
все еще выходит из IO
, но тогда вся логика сама происходит внутри государственной монады чисто.