Haskell - want global variable from file that doesn't change

317 views Asked by At
type Anagrams = Map String [String]

buildAnagrams :: IO Anagrams
buildAnagrams = do
          list <- readCSV "mydict.csv"
          return $ foldr f Map.empty list
            where
             f :: String -> Anagrams -> Anagrams
             f s = Map.insertWith (++) (sort s) [s]

I have this function that build a map to lookup anagrams from a dictionary file which does not change. I want to have the map as a global variable as it needs to be used by other functions. At the moment the functions are using unsafePerformIO on buildAnagrams but I am aware that is is not recommended. The whole program is also very slow as it is building the map multiple times. There must be a better way to do this?

1

There are 1 answers

4
chi On BEST ANSWER

If you want to keep the anagram data in an external file, you need IO to read it. So, you have a few options:

  • passing around the anagram data as an extra argument to all your functions
  • pass the same data using an implicit argument
  • wrap everything in a Reader monad (or add a ReaderT transformer)
  • use unsafePerformIO, with the usual caveats

About unsafePerformIO: if you're absolutely sure that the file you read is not going to change, this is one of its "safe" use cases. While not really recommended, or elegant, it could solve this problem. To avoid re-reading the data multiple times, you should use

{-# NOINLINE allAnagrams #-}
allAnagrams :: Anagrams
allAnagrams = unsafePermformIO buildAnagrams

so that the external file will be read exactly once.

Another option would be to include your external file in your Haskell source. This can be done in a few ways, including

  • plain metaprogramming: you write a program which takes your external file and convert it to a .hs file which declares a large string vector
  • Template Haskell: the same effect as metaprogramming, but a bit more elegant