Генерируйте случайную строку во время компиляции или выполнения и используйте ее в остальной части программы
Как лучше всего это сделать? unsafePerformIO
? Шаблон Хаскелл? Что-то еще? Я никогда не использовал ни один из них, поэтому я не знаю многих деталей их использования.
Обратите внимание, что программа будет компилироваться каждый раз, когда она запускается, поэтому не имеет значения, генерирую ли я строку во время компиляции или во время выполнения. Мне также нужно использовать эту строку в тоннах мест по всему коду, поэтому я не могу сделать это "правильным" способом и сделать это действием ввода-вывода, что потребует слишком много других действий. код, который будет введен в монаду ИО.
5 ответов:
Использование
unsafeperformIO
в данном конкретном случае, по-видимому, хорошо, поскольку в документации говорится:Чтобы это было безопасно, вычисление IO должно быть свободным от побочных эффектов и не зависит от окружающей среды.
Мы не беспокоимся о порядке
newStdGen
.import System.Random import System.IO.Unsafe randomStr :: String randomStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen main = do putStrLn randomStr putStrLn randomStr
Я бы не рекомендовал использовать
unsafePerformIO
. Я предполагаю, что отчет Хаскелла не утверждает, что постоянная функция запоминается, поэтому может случиться, чтоrandStringUnsafe :: String randStringUnsafe = unsafePerformIO $ liftM (take 10 . randomRs ('a','z')) newStdGen
Даст вам разные результаты для разных вызовов! С GHC это, скорее всего, будет записано, но без гарантий. Например, что делать, если компилятор инлайнирует функцию? (GHC, вероятно, достаточно умен, чтобы не делать этого, но опять же, никаких гарантий ...). И например
randNumUnsafe :: (Random a, Num a) => [a] randNumUnsafe = unsafePerformIO $ liftM (take 10 . randomRs (0, 9)) newStdGen
Определенно даст вам разные результаты каждый время называется.
Я бы предпочел пойти с шаблоном Хаскелла. Это, возможно, немного сложнее, но безопасно. В одном модуле мы определяем
{-# LANGUAGE TemplateHaskell #-} module RandomTH where import Control.Monad import System.Random import Language.Haskell.TH -- A standard function generating random strings. randString :: IO String randString = liftM (take 10 . randomRs ('a','z')) newStdGen -- .. lifted to Q randStringQ :: Q String randStringQ = runIO randString -- .. lifted to an Q Exp randStringExp :: Q Exp randStringExp = randStringQ >>= litE . stringL -- | Declares a constant `String` function with a given name -- that returns a random string generated on compile time. randStringD :: String -> DecsQ randStringD fname = liftM (: []) $ funD (mkName fname) [clause [] (normalB randStringExp) []]
(Возможно,
randStringD
можно было бы написать более читаемым способом - если у вас есть идея, пожалуйста, отредактируйте ее или прокомментируйте.)Тогда в другом модуле мы можем использовать его для объявления постоянной функции с заданным именем:
{-# LANGUAGE TemplateHaskell #-} $(randStringD "randStr") main = do putStrLn randStr putStrLn randStr
Возможно, было бы проще ответить на этот вопрос, если бы мы знали больше об окружающем контексте, но подход, который я бы выбрал, состоял бы в том, чтобы передать строку везде, где это необходимо, и создать ее один раз в
main
. Таким образом:Вероятно, именно так я и поступил бы.import Control.Monad import System.Random -- Some arbitrary functions f :: String -> Int -> Int -> Int f rstr x y = length rstr * x * y -- This one doesn't depend on the random string g :: Int -> Int g x = x*x h :: String -> String -> Int h rstr str = sum . map fromEnum $ zipWith min rstr str main :: IO () main = do rstr <- randomString putStr "The result is: " print $ f rstr (g 17) (h rstr "other string") randomString :: IO String randomString = flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
С другой стороны, если у вас есть много этих функций, вы можете потенциально найти его громоздким, чтобы передать
rstr
во все из них. Чтобы абстрагировать это, вы можете использоватьReader
монаду ; значения типаReader r a
- или, в более общем смысле, значения типаMonadReader r m => m a
- способныask
для значения типаr
, которое передается один раз, на верхнем уровне. Это дало бы вам:{-# LANGUAGE FlexibleContexts #-} import Control.Applicative import Control.Monad.Reader import System.Random f :: MonadReader String m => Int -> Int -> m Int f x y = do rstr <- ask return $ length rstr * x * y g :: Int -> Int g x = x*x h :: MonadReader String m => String -> m Int h str = do rstr <- ask return . sum . map fromEnum $ zipWith min rstr str main :: IO () main = do rstr <- randomString putStr "The result is: " print $ runReader (f (g 17) =<< h "other string") rstr randomString :: IO String randomString = flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
(на самом деле, поскольку
(r ->)
является экземпляромMonadReader r
, функции выше можно рассматривать как имеющие Типf :: Int -> Int -> String -> Int
и т. д., и вы можете оставить вызовrunReader
(и удалитьFlexibleContexts
) - построенное вами монадическое вычисление будет просто иметь типString -> Int
. Но я, вероятно, не стал бы беспокоиться.)Еще один подход, что, вероятно, является ненужным использованием языковых расширений (я определенно предпочитаю два подхода выше), было бы использовать неявный параметр , который является переменной, которая передается динамически и отражается в типе (что-то вроде ограничения
MonadReader String m
). Это выглядело бы так:{-# LANGUAGE ImplicitParams #-} import Control.Monad import System.Random f :: (?rstr :: String) => Int -> Int -> Int f x y = length ?rstr * x * y g :: Int -> Int g x = x*x h :: (?rstr :: String) => String -> Int h str = sum . map fromEnum $ zipWith min ?rstr str main :: IO () main = do rstr <- randomString let ?rstr = rstr putStr "The result is: " print $ f (g 17) (h "other string") randomString :: IO String randomString = flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
Теперь. Я должен признать, что вы может делать такого рода вещи на самом высоком уровне. Существует стандартный хак, который позволяет использовать
Тем не менее, вот как использовать два подхода, которые вы упомянули,unsafePerformIO
для получения верхнего уровняIORef
s, например экземпляр; и шаблон Haskell позволит вам выполнить действие ввода-вывода один раз, во время компиляции, и внедрить результат. Но я бы избегал обоих этих подходов. Почему? Ну, в принципе, есть некоторые дебаты о том, означает ли "чистый" "определяется точно синтаксисом/не изменяется при любом запуске программы" (интерпретация, которую я бы предпочел), или это означает "не изменяется при этом запуске программы."В качестве одного из примеров проблем, которые это вызвало: пакетHashable
, в одном точка, переключенная с фиксированной соли на случайную соль. Это вызвалошум на Reddit и внесло ошибки в ранее работавший код. Пакет отклонился назад, и теперь позволяет пользователям выбирать это поведение через переменную окружения, по умолчанию устанавливая чистоту между запусками.unsafePerformIO
и шаблон Haskell, чтобы получить случайные данные верхнего уровня-вместе с why, отдельно от проблем, связанных с в перерывах между пробежками я бы не стал использовать эти техники. (Это единственные два метода для этого, которые я могу придумать.)
Хак
unsafePerformIO
, как его называют, очень хрупок; он опирается на некоторые оптимизации, которые не выполняются, и, как правило, не является популярным подходом. Делать это таким образом будет выглядеть так:import Control.Monad import System.Random import System.IO.Unsafe unsafeConstantRandomString :: String unsafeConstantRandomString = unsafePerformIO $ flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32) {-# NOINLINE unsafeConstantRandomString #-}
Серьезно, хотя, посмотрите, как много слово
unsafe
используется в приведенном выше коде? Это потому, что использованиеunsafePerformIO
воля укусить вас, если вы действительно не знаете, что делаете, и возможно, даже тогда. Даже когдаunsafePerformIO
не кусает вас напрямую, не меньше, чем авторы GHC сказали бы, что , вероятно, не стоит использовать для этого (см. раздел "преступление не платит"). Не делай этого.- Использование шаблона Хаскелла для этого похоже на использование ядерной боеголовки, чтобы убить комара. Уродливая ядерная боеголовка в придачу. Такой подход будет выглядеть следующим образом: следующее:
Обратите внимание также, что в версии шаблона Haskell вы не можете абстрагировать функцию создания случайных строк в отдельное значение{-# LANGUAGE TemplateHaskell #-} import Control.Monad import System.Random import Language.Haskell.TH thConstantRandomString :: String thConstantRandomString = $(fmap (LitE . StringL) . runIO $ flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32))
randomString :: IO String
в том же модуле, или вы столкнетесь с ограничением этапа . Это безопасно, хотя, в отличие от взломаunsafePerformIO
; по крайней мере, безопасно по модулю упомянутых выше опасений о чистоте между запусками.
Генерация случайного числа в
IO
не означает, что нижестоящие функции должны использоватьIO
.Вот пример чистой функции, которая зависит от значения типа
A
:f :: A -> B
... и вот действие
IO
, которое порождаетA
:io :: IO A
Мне не нужно изменять
f
, чтобы использоватьIO
. Вместо этого я используюfmap
:fmap f io :: IO B
Это именно та проблема, которую функторы должны решать: поднимать морфизмы над обернутыми значениями так, чтобы морфизмы не нуждаются в модификации.
import System.Random main = do gen <- newStdGen let str = take 10 $ randomRs ('a','z') gen putStrLn str putStrLn $ (reverse . (take 3)) str
При этом генерируется строка длиной в десять символов, состоящая только из строчных букв. Этот код находится в IO монаде, но str чист, он может быть передан чистым функциям. Вы не можете получить что-то случайное без монады ИО. Вы могли бы сделать unsafePerformIO, но я действительно не понимаю, почему. Вы можете передать значение str, если всегда хотите получить одно и то же. Если вы посмотрите на последнюю строку моего кода, Вы увидите, что у меня есть чистая функция, которая работает со строкой, но поскольку я хочу ее видеть, я вызываю
putStrLn
который возвращает пустое действие ввода-вывода.EDIT: или это может быть место для монады читателя