5

I have the following function in Haskell:

memdb = -- load the contents of a database into memory as a Map

And then I have the following line:

map (\x -> memdb ! x) values

I would like memdb to generate the Map only once, instead of on every iteration of map. I could do it using something like this:

make_memdb = -- equivalent to memdb in previous example
memdb <- make_memdb
map (\x -> memdb ! x) values

But this would mean that I would have to pass memdb in to every function that uses it. Is there any way I can:

a. avoid recalculating memdb each time it is called OR

b. save the value produced in make_memdb as a constant so I can avoid passing it in to every function that uses it?

Vlad the Impala
  • 15,572
  • 16
  • 81
  • 124
  • possible duplicate of [How do you make a generic memoize function in Haskell?](http://stackoverflow.com/questions/141650/how-do-you-make-a-generic-memoize-function-in-haskell) – Craig Stuntz Aug 26 '11 at 21:03
  • @Craig Stuntz: I had read through that question before posting mine. How would those answers help me achieve a. or b. ? – Vlad the Impala Aug 26 '11 at 21:25
  • @Craig: Despite the title, this question isn't really about memoization in the usual sense of the term. It's not a duplicate of that question. – hammar Aug 26 '11 at 21:54

3 Answers3

12

As your map comes from a database, that means it cannot be a constant as it can be different between runs of your application.

But this would mean that I would have to pass memdb in to every function that uses it.

Yes, but there are tools to make this less bad than it sounds. In particular, this sounds like a perfect use case for the the reader monad!

Reader monads are commonly used when you have some value, like a configuration, that you want to load at the start of your program and then be able to access it around your program without having to explicitly pass it around all the time. Here's a short example of how you would use it:

main = do
    memdb <- make_memdb -- Get the memdb from the database once and for all
    runReaderT foo memdb

foo = do
    memdb <- ask -- Grab the memdb. Will not reload from the database
    liftIO $ putStrLn "Hello, world" -- IO actions have to be lifted
    -- [...]

See also:

hammar
  • 138,522
  • 17
  • 304
  • 385
1

You seem to want to get the memdb via IO as a way of avoiding passing more parameters, correct? You then ask if you can either (A) define memdb, implying it will be a top-level function, without the overhead of loading data from the DB or (B) if you can save the loaded data structure with global scope.

Both of these are doable with an IORef and unsafePerformIO to define a top-level mutable global variable. I DO NOT SUGGEST YOU DO THIS. It is clumsy and annoying to refactor. That said, I'll show you how anyway:

Assuming you have a function:

make_memdb :: IO (Map K V)

You can declare a top-level mutable variable:

import Data.Map as M
import Data.IORef

mRef :: IORef (Map K V)
mRef = unsafePerformIO $ newIORef M.empty
{-# NOINLINE mRef #-}

main = do
    m <- make_memdb
    writeIORef mRef m
    ... do  stuff using mRef ...

stuffUsingMRef ... = do
    memdb <- readIORef
    let vs = map (memdb !) values
    return vs

Notice that your functions will forever live in IO. This is because you need IO in order to read the global mutable variable in which you placed memdb. If you don't like this, and you don't like passing parameters, then learn the state monad! I'm sure another answer will discuss that, which is the correct solution.

Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
0

You can, but you shouldn't. See also other people's opinions on a similar question.

Community
  • 1
  • 1
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380