Мне нужно преобразовать список IO [Float]
в [Float]
. Я получаю объект IO [Float]
из следующей функции:
probs :: Int -> IO [Float]
probs 0 = return []
probs n = do
p <- getStdRandom random
ps <- probs (n-1)
return (p:ps)
Я понимаю, что результатом этой функции является список типа [IO Float], а не список чисел. Это список действий ввода-вывода, генерирующих числа. Ввод-вывод еще не был выполнен, поэтому в моем случае генератор случайных чисел фактически не сгенерировал числа. Я хочу сгенерировать каждое случайное число из содержимого этого списка, чтобы получить список [Float]
.
Мне нужно это, чтобы вычислить количество чисел в каждом квартиле результата (чтобы проверить распределение среди случайных чисел):
calcQuartile :: [Float] -> [Float] -> [Int]
calcQuartile randomList (x1:x2:rest) = length(filter (\x -> x>=x1 && x<x2) randomList):calcQuartile randomList (x2:rest)
calcQuartile x y = []
Я использую следующий код для запуска этой функции, которая не работает:
calcQuartile (probs x) [0,0.25..1]
Ошибка, которую я получаю:
• Couldn't match expected type ‘[Float]’
with actual type ‘IO [Float]’
• In the first argument of ‘calcQuartile’, namely ‘(probs x)’
In the expression: calcQuartile (probs x) [0, 0.25 .. 1]
In an equation for ‘getAmountInQuartile’:
getAmountInQuartile x = calcQuartile (probs x) [0, 0.25 .. 1]
На практике вы просто напишете (`calcQuartile`[0, 1/4 .. 1]) <$> probs x
. (Назначение обратных кавычек - частично применить к правильному аргументу. Было бы проще, если бы вы сначала определили calcQuartile
с перевернутыми аргументами, что упростило бы приложение до calcQuartile [0, 1/4 .. 1] <$> probs x
.) Альтернатива: calcQuartile <$> probs x <*> pure [0, 1/4 .. 1]
.
Кстати, для более сложных вещей со случайными числами есть специальные монады, так что вы можете вообще не делать все в IO
. hackage.haskell.org/package/random-fu-0.2.7.0/docs/…
@leftaroundabout Я должен сказать, что не считаю свой вопрос дубликатом связанных вопросов, поскольку в моем вопросе явно указано, что он касается IO список и имеет дело со значениями случайный. Я надеялся на какое-то решение, которое конкретно касается списков монад (способы fold
вместо монадического списка (возможно, foldM
?) Или map
или filter
или что-то еще). То, что ответ на этот вопрос теперь аналогичен ответам на другой вопрос, не означает, что я ожидал найти аналогичные ответы.
Обновлено: как отмечалось в стиле @freestyle, это решение работает, но может привести к непредсказуемым результатам:
I imported the module
System.IO.Unsafe
:import System.IO.Unsafe
Next, I can replace my code by the following:
calcQuartile (unsafePerformIO(probs x)) [0,0.25..1]
Лучшее решение, как отмечает @Willem Van Onsem, следующее:
probs x >>= \y -> return (calcQuartile y [0, 0.25..1])
Примечательно, однако, что это возвращает IO [Int]
вместо [Int]
. монадическое связывание>>=
связывает два нечистых действия вместе (то есть в данном случае действия ввода-вывода) и делает из них одно действие IO
. То же самое можно записать и в нотации do
:
do
y <- probs x
return $ calcQuartile y [0, 0.25 .. 1]
В этом конкретном случае вторым действием (calcQuartile
) на самом деле является чистый (таким образом, return
, который обертывает чистое значение как действие ввода-вывода без побочных эффектов), поэтому вам вообще не нужен экземпляр монады, и вы можете вместо этого просто используйте IO
как функтор:
fmap (\y -> calcQuartile y [0, 0.25..1]) $ probs x
или
(`calcQuartile`[0, 0.25..1]) <$> probs x
Да, работает, но как это работает? Можете ли вы предсказать и объяснить результат: let {a = calcQuartile (unsafePerformIO(probs x)) [0,0.25..1]; b = calcQuartile (unsafePerformIO(probs x)) [0,0.25..1]} in a == b
@freestyle Но разве это не все при работе со случайными числами? Случайные результаты не совсем предсказуемы по своей природе. Как вы могли бы предложить этот вариант использования более "чистым" способом?
@SimonBaars Если вы включите оптимизацию, код фристайла внезапно вернет True
, а не False
.
Это явно нет безопасное использование unsafePerformIO
. Как говорит фристайл, это нарушает ссылочную прозрачность: если вы хотите проверить несколько разных образцов из одного и того же дистрибутива, вы ожидаете всегда получать немного разные результаты, но unsafePerformIO (probs x)
семантически является просто константой. Как правило, никогда не используйте unsafePerformIO
. (В тех немногих случаях, когда это действительно уместно, вы должны быть на 100% уверены и не должны задавать по этому поводу вопросов.) Определенно не используйте его, если вы еще не очень хорошо разбираетесь в монадах.
Всем: хотя я сам проголосовал против этого ответа, не стоит перебарщивать. Это хорошо продуманный пост, не нужно ничего наказывать. Просто он содержит раствор, которого следует избегать.
@leftaroundabout Извините ... Я не знал о непредсказуемых результатах. Я отредактировал свой ответ, чтобы люди, которые задают этот вопрос, не могли прийти к неверным практическим решениям.
@leftaroundabout Большое спасибо за подробный ответ :-).
Идея состоит в том, что нет разворачивает значения
IO
. Есть некоторые функцииunsafePerformIO
и его друзей, но это может привести к серьезным проблемам. Один пишет чистые функции, и вы можете, например, затем связать две функции вместе, например, сprobs x >>= \y -> return (calcQuartile y [0, 0.25..1])