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.)
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 unsafePerformIO
sarà 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.
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.
Sì, ma garantisce che 'randomStr' non cambierà all'interno del programma? – Drew
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
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