Vi incoraggio a cercare una soluzione più funzionale ad esempio caricando le intestazioni di cui hai bisogno in anticipo e facendole passare in una struttura dati come ad esempio uno Map
. Se il passaggio esplicito è scomodo, è possibile utilizzare un trasformatore monad Reader
o State
per gestirlo direttamente.
Detto questo, è possibile eseguire ciò nel modo desiderato utilizzando unsafePerformIO
per creare un riferimento mutabile globale per contenere la struttura dei dati.
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)
Ho usato un MVar
qui per sicurezza thread. Se non ne hai bisogno, potresti essere in grado di scappare usando invece IORef
.
Inoltre, si noti il pragma NOINLINE
su memo
per garantire che il riferimento venga creato solo una volta. Senza questo, il compilatore potrebbe inserirlo in getHeader
, dandovi un nuovo riferimento ogni volta.
Grazie. Se desidero evitare unsafePerformIO in modo che 'memo' restituisca un'azione IO, funzionerà ancora? Immagino che ora verrà chiamato ogni volta che deve essere valutato. –
@DavidUnric: No, la memoizzazione non funzionerebbe allora, dal momento che si otterrebbe una nuova 'Map' vuota ogni volta, quindi dovresti caricare il testo dal file ogni volta. Potresti creare il 'MVar' in un posto e poi passarlo, ma potresti anche passare direttamente la' Mappa'. – hammar
hammar> Thx per la spiegazione. Ho rifattorizzato il codice in modo che l'intestazione venga ora passata dalla prima funzione IO appena prima che venga utilizzata. Anche se segnerò il tuo asnwer in quanto mostra un altro approccio di cui non ero a conoscenza. –