2013-07-06 9 views
8

Was wäre der beste Weg, dies zu tun? unsafePerformIO? Vorlage Haskell? Etwas anderes? Ich habe noch nie eines von beiden benutzt, daher kenne ich nicht viele Einzelheiten ihrer Verwendung.Generieren Sie eine zufällige Zeichenfolge zur Kompilierzeit oder Laufzeit und verwenden Sie sie im Rest des Programms

Beachten Sie, dass das Programm jedes Mal kompiliert wird, wenn es ausgeführt wird, so spielt es keine Rolle, wenn ich die Zeichenfolge zur Kompilierzeit oder Laufzeit generieren. Ich muss diese Zeichenkette auch in Tonnen von Stellen im Code verwenden, so dass ich es nicht wirklich "richtig" machen kann und dass es eine IO-Aktion wäre, die viel zu viel anderen Code benötigt, um in die IO-Monade eingefügt zu werden .

Antwort

4

Mit unsafeperformIO in diesem speziellen Fall scheint in Ordnung zu sein, wie die Dokumentation sagt:

Für diese sicher zu sein, die IO-Berechnung sollte frei von Nebenwirkungen und unabhängig von seiner Umgebung sein.

Wir sind nicht besorgt über die Reihenfolge 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

Ja, aber garantiert das, dass 'randomStr' sich nicht innerhalb des Programms ändert? – Drew

+0

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

+5

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

1

Dadurch wird eine Zeichenfolge mit zehn Zeichen Länge und nur Kleinbuchstaben generiert. Dieser Code ist in der IO-Monade aber str ist rein, es kann an reine Funktionen übergeben werden. Ohne die IO-Monade können Sie nichts Zufälliges bekommen. Du könntest einen unsicheren PerformIO machen, aber ich verstehe nicht warum. Sie können den Wert str übergeben, wenn Sie immer den gleichen möchten. Wenn Sie sich die letzte Zeile meines Codes anschauen, können Sie sehen, dass ich eine reine Funktion habe, die auf der Zeichenkette arbeitet, aber da ich sie sehen möchte, rufe ich putStrLn auf, was eine leere IO-Aktion zurückgibt.

EDIT: Oder kann dies der richtige Ort für den Reader Monad sein

+0

Das Problem dabei ist, dass "str" ​​wird mit jedem Lauf von Main, die für mich nicht funktionieren wird. Stellen Sie sich vor, wenn main eine unäre Funktion wäre und als Parameter an eine andere Funktion übergeben wurde, und diese Funktion wendet eine Reihe von Werten auf main an, um einige Eigenschaften davon abzuleiten. Damit habe ich es hier zu tun. – Drew

+0

Wenn Sie eine zufällige Zeichenfolge zum Zeitpunkt der Kompilierung erzeugen wollen und das Programm jedes Mal kompiliert wird, wenn Sie es ausführen, wie unterscheidet es sich dann davon, es zur Kompilierzeit oder zur Laufzeit zu generieren? main ist keine Funktion, die Sie übergeben, dies ist der Einstiegspunkt des Programmeinstiegs. Ich denke, Ihre einzige Option ist Vorlage Haskell, die ich unsicher bin, wie zu tun, in der Staat oder IO Monad überall oder unsafeIO. – DiegoNolan

+0

Ja, in diesem Fall ist main der Einstiegspunkt, aber in meinem Fall wird der Wert in einer Funktion verwendet, die herumgereicht, analysiert und usw. wird. Wenn ich die Definition der Funktion nicht in main verschiebe, kann ich 'nicht verwenden str' – Drew

8

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.)

  1. 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.

  2. 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.

10

Ich würde nicht empfehlen, unsafePerformIO zu verwenden. Ich denke, die Haskell Bericht gibt nicht an, dass eine konstante Funktion memoized ist so, dass

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

geben Ihnen unterschiedliche Ergebnisse für unterschiedliche Anrufe passieren kann! Mit GHC wird es höchstwahrscheinlich gemerkt, aber ohne Garantien. Zum Beispiel, was passiert, wenn der Compiler die Funktion einfügt? (GHC ist wahrscheinlich schlau genug, es nicht zu tun, aber wieder, keine Garantien ...).Und zum Beispiel

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

wird definitiv geben Sie verschiedene Ergebnisse jedes Mal, wenn es heißt.


Ich würde lieber mit Vorlage Haskell gehen. Es ist vielleicht ein bisschen komplizierter, aber sicher. In einem Modul definieren wir

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

(Vielleicht randStringD in eine besser lesbare Art und Weise geschrieben werden könnte -. Wenn Sie eine Idee haben, können Sie es oder Kommentar bearbeiten)

Dann in einem anderen Modul wir es verwenden können zu erklären, eine konstante Funktion mit einem bestimmten Namen: bedeutet

{-# LANGUAGE TemplateHaskell #-} 

$(randStringD "randStr") 

main = do 
    putStrLn randStr 
    putStrLn randStr 
4

Generieren eine Zufallszahl in IO nicht, dass nachgeschaltete Funktionen IO verwenden.

Hier ist ein Beispiel reine Funktion, die A auf einen Wert vom Typ abhängt:

f :: A -> B 

... und hier ist eine IO Aktion, die ein A erzeugt:

io :: IO A 

ich nicht muss f ändern, um IO zu verwenden. Stattdessen verwende ich fmap:

fmap f io :: IO B 

Dies ist genau die Art von Problem, das functors angeblich lösen: morphisms über gewickelte Werte, so dass die morphisms Heben nicht geändert werden muß.