2012-02-26 11 views
5

con curiosidad ¿cómo volver a escribir la siguiente función para ser llamado una sola vez durante la vida del programa?Memoized IO function?

getHeader :: FilePath -> IO String 
getHeader fn = readFile fn >>= return . take 13 

La función anterior se llama varias veces desde varias funciones. Cómo prevenir la reapertura del archivo si se llama a la función con el mismo parámetro, es decir. nombre del archivo ?

Respuesta

7

les animo a buscar una solución más funcional , por ejemplo, cargando los encabezados que necesitas por adelantado y pasándolos en alguna estructura de datos como, por ejemplo, Map. Si no es conveniente pasarlo explícitamente, puede usar un transformador de mónada Reader o State para manejarlo por usted.

Dicho esto, puede lograrlo de la manera que desea utilizando usando unsafePerformIO para crear una referencia global mutable para mantener su estructura de datos.

import Control.Concurrent.MVar 
import qualified Data.Map as Map 
import System.IO.Unsafe (unsafePerformIO) 

memo :: MVar (Map.Map FilePath String) 
memo = unsafePerformIO (newMVar Map.empty) 
{-# NOINLINE memo #-} 

getHeader :: FilePath -> IO String 
getHeader fn = modifyMVar memo $ \m -> do 
    case Map.lookup fn m of 
    Just header -> return (m, header) 
    Nothing  -> do header <- take 13 `fmap` readFile fn 
         return (Map.insert fn header m, header) 

He utilizado un MVar aquí para la seguridad hilo. Si no lo necesita, puede salirse con la suya usando un IORef.

Además, tenga en cuenta NOINLINE pragma en memo para asegurarse de que la referencia solo se crea una vez. Sin esto, el compilador puede alinearlo en getHeader, proporcionándole una nueva referencia cada vez.

+0

Gracias. Si quisiera evitar la forma de riesgo insegura para que 'nota' devuelva una acción IO, ¿seguirá funcionando? Supongo que ahora se llamará cada vez que se deba evaluar. –

+1

@DavidUnric: No, el memoization no funcionaría entonces, ya que se obtendría un nuevo vacío 'Map' cada vez, por lo que sería cargar el texto del archivo cada vez. Podrías crear el 'MVar' en un lugar y luego pasarlo, pero también podrías pasar el' Map' directamente. – hammar

+1

hammar> Thx para la explicación. Reformé el código para que el encabezado ahora pase de la primera función IO justo antes de que se use. Aunque voy a marcar Your asnwer ya que muestra otro enfoque del que no tenía conocimiento. –

4

Lo más sencillo es que acaba de llamar una vez al comienzo de main y pasar el String resultante en torno a todas las otras funciones que lo necesitan:

main = do 
    header <- getHeader 
    bigOldThingOne header 
    bigOldThingTwo header 
+0

Gracias.Sabía de esta manera, pero me resulta un poco incómodo, porque el encabezado debe pasarse a todas las funciones encadenadas, incluso si no se les aplica este parámetro. –

+1

@DavidUnric: Deberías mirar las mónadas de los lectores. Ellos resuelven exactamente ese problema. – hammar

2

No debe usar unsafePerformIO para resolver esto. La forma correcta de hacer exactamente lo que describes es crear un IORef que contenga un Quiz que inicialmente contenga Nada. A continuación, crea una función IO que verifica el valor y realiza el cálculo si es Nothing y almacena el resultado como Just. Si encuentra un Just, reutiliza el valor.

Todo esto requiere pasar la referencia de IORef, que es tan engorroso como pasar alrededor de la cadena, por lo que todo el mundo recomienda directamente pasar la cadena, ya sea de forma explícita o implícita utilizando la mónada de Reader.

Hay muy pocos usos legítimos para inseguroPerformIO y este no es uno de ellos. No sigas ese camino, de lo contrario te encontrarás luchando contra Haskell cuando sigue haciendo cosas inesperadas. Cada solución que usa UnsafePerformIO como un "truco ingenioso" siempre termina catastróficamente (y eso incluye readFile).

Nota al margen - se puede simplificar su función getHeader:

getHeader path = fmap (take 13) (readFile path) 

O

getHeader path = take 13 <$> readFile path 
4

Usted puede utilizar monad-memo paquete para envolver cualquier mónada en MemoT transformador. La tabla de notas se pasará implícitamente a través de sus funciones monádicas. A continuación, utilice startEvalMemoT para convertir mónada memoized en ordinarios IO:

{-# LANGUAGE NoMonomorphismRestriction #-} 

import Control.Monad.Memo 

getHeader :: FilePath -> IO String 
getHeader fn = readFile fn >>= return . take 13 

-- | 'memoized' version of getHeader 
getHeaderm :: FilePath -> MemoT String String IO String 
getHeaderm fn = memo (lift . getHeader) fn 

-- | 'memoized' version of Prelude.print 
printm a = memo (lift . print) a 

-- | This will not print the last "Hello" 
test = do 
    printm "Hello" 
    printm "World" 
    printm "Hello" 

main :: IO() 
main = startEvalMemoT test