2013-07-06 11 views
8

Quale sarebbe il modo migliore per farlo? unsafePerformIO? Modello Haskell? Qualcos'altro? Non ho mai usato nessuno di questi, quindi non conosco molti dettagli sul loro utilizzo.Generare una stringa casuale in fase di compilazione o di esecuzione e utilizzarla nel resto del programma

Si noti che il programma verrà compilato ogni volta che viene eseguito, quindi non importa se si genera la stringa in fase di compilazione o di esecuzione. Ho anche bisogno di usare questa stringa in tonnellate di posti in tutto il codice, quindi non posso davvero farlo nel modo "corretto" e fare in modo che sia un'azione IO, che richiederebbe troppo troppo altro codice per essere inserito nella monade IO .

risposta

4

Utilizzando unsafeperformIO in questo caso particolare sembra andare bene come la documentazione dice:

Per questo per essere sicuri, il calcolo IO dovrebbe essere privo di effetti collaterali e indipendente dal suo ambiente.

Non siamo preoccupati per l'ordine di 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

Sì, ma garantisce che 'randomStr' non cambierà all'interno del programma? – Drew

+0

Non cambierà. RandomStr è un valore (non una funzione). Poichè haskell è pigro, questo valore verrà generato quando lo utilizzi per la prima volta e poi sarà sempre lo stesso – Ankur

+5

OK. La risposta http://stackoverflow.com/a/12721453/595605 suggerisce che potrebbe esserci un'ipotetica ottimizzazione del compilatore che ne provoca la ricomposizione. So che in pratica non cambierà, ma non è la stessa garanzia del compilatore che non cambierà. – Drew

1
import System.Random 

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

    putStrLn str 

    putStrLn $ (reverse . (take 3)) str 

Questo genera una stringa di dieci caratteri con solo lettere minuscole. Questo codice è nella monade IO ma str è puro può essere passato a pure funzioni. Non puoi ottenere qualcosa di casuale senza IO Monade. Potresti fare un non sicuro aggiornamento, ma non vedo davvero perché. Puoi passare il valore di str in giro se vuoi sempre lo stesso. Se guardi l'ultima riga del mio codice, puoi vedere che ho una funzione pura che opera sulla stringa, ma poiché voglio vederla, chiamo putStrLn che restituisce un'azione IO vuota.

EDIT: O questo potrebbe essere il posto per il lettore di Monade

+0

Il problema con questo è che 'str' cambierà con ogni esecuzione di main che non funzionerà per me. Immaginate se main fosse una funzione unaria e sia stata passata come parametro a un'altra funzione, e quella funzione applica una serie di valori a main per dedurne alcune proprietà. Questo è quello che sto trattando qui. – Drew

+0

Se vuoi generare una stringa casuale in fase di compilazione e il programma verrà compilato ogni volta che lo esegui, allora come è diverso rispetto a generarlo in fase di compilazione o di esecuzione? main non è una funzione che si passa, questo è il punto di ingresso del punto di ingresso del programma. Penso che la tua unica opzione sia il modello haskell, che non sono sicuro di come fare, essere nello stato o monade IO ovunque, o non sicuro. – DiegoNolan

+0

Sì, in questo caso main è il punto di ingresso ma nel mio caso il valore è utilizzato in una funzione passata, analizzata e così via. A meno che non sposti la definizione della funzione in main, non posso usare ' str' – Drew

8

Potrebbe essere più facile rispondere a questa domanda se sapessimo di più sul contesto circostante, ma l'approccio vorrei prendere sarebbe quello di passare nella stringa in tutto il mondo è stato necessario, e creare una volta in main. Quindi:

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) 

Questo è probabilmente quello che vorrei fare.

D'altra parte, se si dispone di molte di queste funzioni, è potenzialmente possibile trovarlo ingombrante per passare rstr in tutte. Per astrarre questo, è possibile utilizzare the Reader monad; valori di tipo Reader r a - o, più in generale, valori di tipo MonadReader r m => m a: sono in grado di immettere ask per un valore di tipo r, che viene passato una volta, al livello superiore. Ciò darebbe:

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

(In realtà, dal momento che (r ->) è un'istanza di MonadReader r, le funzioni di cui sopra possono essere visti come avere tipo f :: Int -> Int -> String -> Int, ecc, e si può lasciare fuori la chiamata a runReader (e rimuovere FlexibleContexts) - il calcolo monadico che hai costruito sarà solo di tipo String -> Int. Ma probabilmente non mi preoccuperei.)

Ancora un altro approccio, che è probabilmente un uso non necessario delle estensioni del linguaggio (preferisco certamente i due approcci sopra), sarebbe quello di utilizzare un implicit parameter, che è una variabile che viene passato intorno a dynami cally e si riflette nel tipo (una specie di vincolo MonadReader String m).Sarebbe guardare in questo modo:

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

Now. Devo ammettere che è possibile fare questo tipo di cose al livello più alto. C'è un hack standard che consente di utilizzare unsafePerformIO per ottenere IORef s di livello superiore, ad esempio; e Template Haskell ti consentirebbe di eseguire un'azione IO una volta, in fase di compilazione, e di incorporare il risultato. Ma eviterei entrambi questi approcci. Perché? Beh, fondamentalmente, c'è qualche discussione sul fatto che "puro" significhi "determinato esattamente dalla sintassi/non cambia rispetto a qualsiasi esecuzione del programma" (un'interpretazione che vorrei favorire), o significa "non cambia su esecuzione del programma. " Come esempio dei problemi che ciò ha causato: the Hashable package, a un certo punto, passati da un sale fisso a un sale casuale. Ciò ha causato an uproar on Reddit e ha introdotto bug nel codice precedentemente funzionante. Il pacchetto è stato backpedaled, and now allows users to opt-in to this behavior through an environment variable, per impostazione predefinita per purezza tra le diverse fasi.

Detto questo, ecco come utilizzare i due approcci che lei ha citato, unsafePerformIO e Template Haskell, per ottenere i dati-lungo casuale di alto livello con il motivo per cui, separati dalle preoccupazioni circa tra-corre la purezza, non vorrei usare queste tecniche. (Queste sono le uniche due tecniche per fare questo che mi viene in mente.)

  1. The unsafePerformIO hack, come si chiama, è molto fragile; si basa su certe ottimizzazioni che non vengono eseguite e generalmente non è un approccio ben voluto. Facendo in questo modo sarebbe simile modo:

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

    Scherzi a parte, però, vedere quanto la parola unsafe viene utilizzato nel codice di cui sopra? Questo perché utilizzando unsafePerformIOsarà mordere se non si sa veramente cosa si sta facendo, e possibly even then. Anche se unsafePerformIO non ti morde direttamente, non meno che gli autori di GHC direbbero che it's probably not worth using for this (vedi la sezione intitolata "Il crimine non paga"). Non farlo.

  2. Usare Template Haskell per questo è come usare una testata nucleare per uccidere un moscerino. Una brutta testata nucleare, per l'avvio. Tale approccio sarà simile alla seguente:

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

    Si noti inoltre che nella versione Modello Haskell, non si può astrarre le funzionalità random-string-creazione in un valore distinto randomString :: IO String nello stesso modulo, o ti eseguire afoul del stage restriction. È sicuro, tuttavia, a differenza della codifica unsafePerformIO; almeno, sicuro modulo le preoccupazioni sulla purezza tra-corsa di cui sopra.

10

Non mi consiglia di utilizzare unsafePerformIO. Credo che Il Rapporto Haskell non afferma che una funzione costante è memoized così può accadere che

randStringUnsafe :: String 
randStringUnsafe = unsafePerformIO $ liftM (take 10 . randomRs ('a','z')) newStdGen 

vi darà risultati diversi per le diverse chiamate! Con GHC è molto probabile che verrà memoized, ma senza garanzie. Ad esempio, cosa succede se il compilatore inline la funzione? (GHC è probabilmente abbastanza intelligente da non farlo, ma ancora una volta, nessuna garanzia ...).E per esempio

randNumUnsafe :: (Random a, Num a) => [a] 
randNumUnsafe = unsafePerformIO $ liftM (take 10 . randomRs (0, 9)) newStdGen 

sicuramente darà risultati diversi ogni volta che viene chiamato.


Preferirei andare con Template Haskell. È forse un po 'più complicato, ma al sicuro. In un modulo definiamo

{-# 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) []] 

(Forse randStringD potrebbe scrivere in modo più leggibile -. Se avete un'idea, si prega di modificarlo o un commento)

Poi, in un altro modulo possiamo usarlo per dichiarare una funzione costante con un nome:

{-# LANGUAGE TemplateHaskell #-} 

$(randStringD "randStr") 

main = do 
    putStrLn randStr 
    putStrLn randStr 
4

generazione di un numero casuale in IO non implica che le funzioni a valle devono utilizzare IO.

Ecco una funzione di puro esempio, che dipende da un valore di tipo A:

f :: A -> B 

... e qui è un IO un'azione che genera un A:

io :: IO A 

non lo faccio modificare f per utilizzare IO. Invece, io uso fmap:

fmap f io :: IO B 

Questo è esattamente il tipo di problema che funtori si suppone per risolvere: sollevamento morfismi sui valori avvolte in modo che i morfismi non hanno bisogno di essere modificato.