2013-07-06 3 views
8

Что было бы лучшим способом сделать это? unsafePerformIO? Шаблон Haskell? Что-то другое? Я никогда не использовал ни одного из них, поэтому я не знаю многих деталей их использования.Создайте случайную строку во время компиляции или времени выполнения и используйте ее в остальной части программы.

Обратите внимание, что программа будет скомпилирована каждый раз при ее запуске, поэтому не имеет значения, сгенерирую ли я строку во время компиляции или времени выполнения. Мне также нужно использовать эту строку в тоннах мест по всему коду, поэтому я не могу сделать это «правильным» способом и это будет действие IO, для чего потребуется слишком много другого кода для ввода в IO-монаду ,

ответ

4

Использование unsafeperformIO в данном конкретном случае, как представляется, будет хорошо, как говорится в документации:

Для этого, чтобы быть безопасным, вычисление И.О. должны быть свободны от побочных эффектов и независимо от окружающей среды.

Мы не беспокоимся о заказе newStdGen.

import System.Random 
import System.IO.Unsafe 

randomStr :: String 
randomStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen 

main = do 
    putStrLn randomStr 
    putStrLn randomStr 
+1

Да, но это гарантирует, что 'randomStr' не изменится внутри программы? – Drew

+0

Это не изменится. Случайное значение Str является значением (а не функцией). Поскольку haskell ленив, это значение будет генерироваться при первом использовании, а затем всегда будет одинаковым. – Ankur

+5

OK. Этот ответ http://stackoverflow.com/a/12721453/595605 предполагает, что может быть гипотетическая оптимизация компилятора, которая заставляет его перерасчитать. Я знаю, что на практике это не изменится, но это не то же самое, что гарантия от компилятора, что он не изменится. – Drew

1
import System.Random 

main = do 
    gen <- newStdGen 
    let str = take 10 $ randomRs ('a','z') gen 

    putStrLn str 

    putStrLn $ (reverse . (take 3)) str 

Это генерирует строку длиной десять символов с прописными буквами. Этот код находится в монаде IO, но str чист, он может быть передан чистым функциям. Вы не можете получить что-то случайное без IO Monad. Вы можете сделать unsafePerformIO, но я действительно не понимаю, почему. Вы можете передать значение str, если вы всегда хотите, чтобы он был одним и тем же. Если вы посмотрите на последнюю строку моего кода, вы увидите, что у меня есть чистая функция, которая работает с строкой, но поскольку я хочу ее увидеть, я вызываю putStrLn, который возвращает пустое действие ввода-вывода.

EDIT: Или это может быть место для чтения Монада

+0

Проблема в том, что 'str' будет меняться при каждом запуске main, который не будет работать для меня. Представьте себе, что main является унарной функцией и передан как параметр для другой функции, и эта функция применяет кучу значений к основному, чтобы вывести некоторые ее свойства. Вот что я имею в виду здесь. – Drew

+0

Если вы хотите сгенерировать случайную строку во время компиляции, и программа будет скомпилирована каждый раз, когда вы ее запускаете, то как она отличается от ее генерации во время компиляции или времени выполнения? main - это не функция, которую вы передаете, это точка входа точки входа в программу. Я думаю, что ваш единственный вариант - это шаблон haskell, который я не уверен, как это сделать, находясь в штате или монахе IO всюду, или unsafeIO. – DiegoNolan

+0

Да, в этом случае main является точкой входа, но в моем случае значение используется в функции, которая передается, анализируется и т. Д. Если я не переведу определение функции в main, я не могу использовать ' str' – Drew

8

Это может быть легче ответить на этот вопрос, если бы мы знали больше об окружающем контексте, но подход, который я бы хотел бы передать строку везде это было необходимо, и создать его один раз в 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 во всех них. Чтобы абстрагировать это, вы можете использовать the Reader monad; значения типа 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. Но я, вероятно, не стал бы беспокоиться.)

Еще один подход, который, вероятно, является ненужным использованием языковых расширений (я, конечно, предпочитаю два подхода выше), было бы использовать implicit parameter, который представляет собой переменную, проходящую через дин а также отражается в типе (вроде как ограничение 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, например; и Template Haskell позволит вам запускать операцию ввода-вывода один раз, во время компиляции и встраивать результат. Но я бы избегал обоих этих подходов. Зачем? Ну, в принципе, есть некоторые дебаты о том, означает ли «чистое» означает «точно определяемое синтаксисом/не изменяется ни по одному прогону программы» (интерпретация, которую я бы одобрил), либо это означает «не изменяется более чем » запуск программы. " В качестве одного из примеров этих проблем: the Hashable package, в какой-то момент, переключился с фиксированной соли на случайную соль. Это вызвало an uproar on Reddit и ввело ошибки в ранее работающий код. Пакет backpedaled, and now allows users to opt-in to this behavior through an environment variable, по умолчанию отличается чистотой между режимами.

Это означает, что вы можете использовать два подхода, которые вы упомянули, unsafePerformIO и Template Haskell, чтобы получить случайные данные на верхнем уровне вместе с тем, почему, отдельно от проблем между чистотой между запусками, я бы не использовал эти методы. (Это единственные два метода для делать это, что я могу думать.)

  1. The unsafePerformIO hack, как это называется, очень хрупок; он полагается на определенные оптимизации, которые не выполняются, и, как правило, не очень понравился. Делая это таким образом, будет выглядеть так:

    import Control.Monad 
    import System.Random 
    import System.IO.Unsafe 
    
    unsafeConstantRandomString :: String 
    unsafeConstantRandomString = unsafePerformIO $ 
        flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32) 
    {-# NOINLINE unsafeConstantRandomString #-} 
    

    Серьезно, хотя, посмотреть, сколько слово unsafe используется в коде выше? Это потому, что использование unsafePerformIOбудет укусить вас, если вы действительно не знаете, что делаете, и possibly even then. Даже если unsafePerformIO не укусит вас напрямую, не менее, чем авторы GHC скажут, что it's probably not worth using for this (см. Раздел «Преступление не платит»). Не делайте этого.

  2. Использование шаблона 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)) 
    

    Отметим также, что в версии шаблона Haskell, вы не можете абстрагировать функциональность случайных строк создания в отдельное значение randomString :: IO String в том же модуле, или вы будете бежать от stage restriction. Это безопасно, однако, в отличие от взлома unsafePerformIO; по крайней мере, безопасно по модулю опасений относительно чистоты между путями, упомянутых выше.

10

Я бы не рекомендовал использовать unsafePerformIO. Я предполагаю, что Отчет Haskell не утверждает, что постоянная функция memoized поэтому может случиться так, что

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 

определенно даст вам разные результаты каждый раз, когда он называется.


Я предпочел бы воспользоваться шаблоном Haskell. Возможно, это немного сложнее, но безопасно. В одном модуле мы определим

{-# 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 
4

генерации случайного числа в IO не означает, что вниз по течению функции должны использовать IO.

Вот пример чистая функция, которая зависит от значения типа A:

f :: A -> B 

... и вот это IO действие, которое генерирует A:

io :: IO A 

Я не необходимо изменить f на использование IO. Вместо этого я использую fmap:

fmap f io :: IO B 

Это именно то проблема, что функторы предполагается решить: Подъемно морфизмов над обернутых значений, так что морфизмы не должны быть изменены.