Nicolas Henin
Nicolas Henin

Reputation: 3334

looking for a cached function call mechanism

I'm calling a database (EventStore) that recommend using the same connection for the entire life span of your app. I want to implement a cached call for that, but the only thing I'm finding is memoization caching in that way (lib io-memoize) :

import Database.EventStore
import System.IO.Memoize

getCachedEventStoreConnection :: Settings -> ConnectionType -> IO (IO (Connection))
getCachedEventStoreConnection settings connectionType = once $ connect settings connectionType

What I would like is more a signature like that :

getCachedEventStoreConnection :: Settings -> ConnectionType -> IO Connection

otherwise I'm obliged to keep that IO (IO (Connection)) as a "global fct" that I'm passing everywhere which is bad for modularity...

Upvotes: 2

Views: 93

Answers (1)

Daniel Wagner
Daniel Wagner

Reputation: 152707

otherwise I'm obliged to keep that IO (IO (Connection)) as a "global fct" that I'm passing everywhere which is bad for modularity.

Unfortunately, caching calls doesn't help eliminate function arguments: instead of passing around the result of a cached call, you must pass around the cache instead. There's no getting around it; part of the hair shirt you wear when choosing Haskell is that all the data a function wants to use must be made explicit in its type, so if part of your application needs a database Connection, there's nothing for it but to pass a Connection to that part of your application (and, by extension, all its callers).

There is some sugar like ReaderT you can sprinkle around to make things a bit more convenient, making it appear as though you're not passing around function arguments, but at the end of the day that's exactly what they're doing under the hood.

However, I reject your claim that this is bad for modularity. If you did have an implicit cache, this would break modularity: you would not be able to lift that function out of this application into a library and use it in many applications without also lifting the cache out. That is, the cache and any operations that use it become coupled -- one must lift them all or none, the exact opposite of modularity.* If the database connection is a function argument instead of an implicit cache, it can be lifted independently of lifting the chunk of code that creates the connection once at app startup.

* And suppose you do lift out all the operations and the implicit cache into a library. Now two downstream libraries depend on and use yours; do you get two caches, that must be separately initialized and therefore is maybe less efficient, or do you get one shared cache which therefore bleeds effects from the one library into the other and therefore is maybe less correct? A difficult choice -- one that should have to be made carefully and explicitly by the downstream users, not by the library with the cache in it.

Upvotes: 3

Related Questions