Es könnte einfacher sein, diese Frage zu beantworten, wenn wir mehr über den umgebenden Kontext wissen, aber der Ansatz, den ich wäre nehmen würde in der Folge passiert überall war es notwendig, und es einmal in main
erstellen. Also:
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)
Das ist wahrscheinlich, was ich tun würde.
Auf der anderen Seite, wenn Sie eine Menge dieser Funktionen haben, könnten Sie möglicherweise sperrig finden, in alle von ihnen zu übergeben. Um dies zu abstrahieren, können Sie the Reader
monad verwenden; Werte des Typs Reader r a
- oder allgemeiner Werte des Typs MonadReader r m => m a
- können ask
für einen Wert r
, der einmal übergeben wird, auf der obersten Ebene. Das würde Ihnen:
{-# 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)
(Eigentlich seit (r ->)
eine Instanz von MonadReader r
ist, über die Funktionen können als mit Typ f :: Int -> Int -> String -> Int
usw. betrachtet werden, und Sie können den Anruf zu runReader
verlassen (und entfernen FlexibleContexts
) - Die monadische Berechnung, die Sie erstellt haben, ist nur vom Typ String -> Int
. Aber ich würde wahrscheinlich nicht stören.)
Noch ein Ansatz, der wahrscheinlich eine unnötige Verwendung von Spracherweiterungen ist (ich bevorzuge sicherlich die beiden oben genannten Ansätze), wäre es, eine implicit parameter zu verwenden, die eine Variable ist, die dynami weitergegeben wird und spiegelt sich in der Art (Art der MonadReader String m
Einschränkung).Das würde so aussehen so:
{-# 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)
Jetzt. Ich muss zugeben, dass Sie diese Art von Dingen auf der obersten Ebene tun können. Es gibt einen Standard-Hack, der zum Beispiel die Verwendung von unsafePerformIO
ermöglicht, um IORef
zu erreichen; Mit Template Haskell können Sie eine E/A-Aktion einmal zur Kompilierungszeit ausführen und das Ergebnis einbetten. Aber ich würde beide Ansätze vermeiden. Warum? Nun, im Grunde gibt es eine Debatte darüber, ob "rein" bedeutet "genau bestimmt durch die Syntax/ändert sich nicht über einen Lauf des Programms" (eine Interpretation, die ich bevorzugen würde), oder es bedeutet "ändert sich nicht über dies Lauf des Programms. " Als ein Beispiel für die dadurch verursachten Probleme wurde the Hashable
package an einem Punkt von einem festen Salz zu einem zufälligen Salz umgeschaltet. Dies verursachte an uproar on Reddit und führte Fehler in zuvor funktionierenden Code ein. Das Paket wurde rückgekoppelt, and now allows users to opt-in to this behavior through an environment variable, standardmäßig auf die Reinheit zwischen den Durchläufen.
Dies ist, hier ist, wie Sie die beiden Ansätze, die Sie erwähnten, unsafePerformIO
und Template Haskell, um Top-Level-Zufallsdaten zu bekommen - zusammen mit warum, getrennt von den Bedenken über die zwischen-Run-Reinheit, würde ich nicht verwenden diese Techniken. (Dies sind die einzigen zwei Techniken, dies zu tun, die ich mir vorstellen kann.)
The unsafePerformIO
hack, wie es genannt wird, ist sehr zerbrechlich; Es beruht auf bestimmten Optimierungen, die nicht durchgeführt werden, und ist im Allgemeinen kein beliebter Ansatz. es auf diese Weise tun würde wie so aussehen:
import Control.Monad
import System.Random
import System.IO.Unsafe
unsafeConstantRandomString :: String
unsafeConstantRandomString = unsafePerformIO $
flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
{-# NOINLINE unsafeConstantRandomString #-}
Im Ernst, aber sieht, wie viel das Wort unsafe
in dem obigen Code verwendet wird? Das liegt daran, dass die Verwendung von unsafePerformIO
Sie beißen wird, wenn Sie nicht wirklich wissen, was Sie tun, und possibly even then. Selbst wenn unsafePerformIO
Sie nicht direkt beißt, würden nicht weniger als die Autoren von GHC sagen, dass it's probably not worth using for this (siehe Abschnitt mit dem Titel "Verbrechen zahlt nicht"). Tun Sie das nicht.
Verwenden von Vorlage Haskell für das ist wie mit einem Atomsprengkopf, um eine Mücke zu töten. Ein hässlicher nuklearer Sprengkopf. Dieser Ansatz würde aussehen wie die folgenden:
{-# 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))
Beachten Sie, dass auch in der Template Haskell-Version, kann man nicht abstrakt die Random-String-creation-Funktionalität in einen separaten Wert randomString :: IO String
im selben Modul, oder Sie werden mit der stage restriction in Konflikt geraten. Es ist jedoch sicher, im Gegensatz zu dem unsafePerformIO
Hack; zumindest, sicher modulo die oben erwähnten Bedenken hinsichtlich der Reinheit zwischen den Durchläufen.
Ja, aber garantiert das, dass 'randomStr' sich nicht innerhalb des Programms ändert? – Drew
Es wird sich nicht ändern. Der RandomStr ist ein Wert (keine Funktion). Da haskell träge ist, wird dieser Wert generiert, wenn Sie ihn zum ersten Mal verwenden und dann immer gleich sein. – Ankur
OK. Diese http://stackoverflow.com/a/12721453/595605 Antwort schlägt vor, dass es möglicherweise eine hypothetische Compiler-Optimierung gibt, die verursacht, dass es neu berechnet wird. Ich weiß, dass sich das in der Praxis nicht ändert, aber das ist nicht dasselbe wie eine Garantie des Compilers, dass es sich nicht ändert. – Drew