Это может быть легче ответить на этот вопрос, если бы мы знали больше об окружающем контексте, но подход, который я бы хотел бы передать строку везде это было необходимо, и создать его один раз в 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, чтобы получить случайные данные на верхнем уровне вместе с тем, почему, отдельно от проблем между чистотой между запусками, я бы не использовал эти методы. (Это единственные два метода для делать это, что я могу думать.)
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 (см. Раздел «Преступление не платит»). Не делайте этого.
Использование шаблона 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
; по крайней мере, безопасно по модулю опасений относительно чистоты между путями, упомянутых выше.
Да, но это гарантирует, что 'randomStr' не изменится внутри программы? – Drew
Это не изменится. Случайное значение Str является значением (а не функцией). Поскольку haskell ленив, это значение будет генерироваться при первом использовании, а затем всегда будет одинаковым. – Ankur
OK. Этот ответ http://stackoverflow.com/a/12721453/595605 предполагает, что может быть гипотетическая оптимизация компилятора, которая заставляет его перерасчитать. Я знаю, что на практике это не изменится, но это не то же самое, что гарантия от компилятора, что он не изменится. – Drew