memo-io: Memoize and global IO utilities.

[ bsd3, data, reflection ] [ Propose Tags ] [ Report a vulnerability ]

Memoize and global IO utilities. See README for more details.


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
global-io

Whether to include globalIO functionality

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 1.0.0, 1.0.0.1
Change log CHANGELOG.md
Dependencies base (<5), ghc-prim, hashable, unordered-containers [details]
License BSD-3-Clause
Author Brandon Chinn <brandonchinn178@gmail.com>
Maintainer Brandon Chinn <brandonchinn178@gmail.com>
Uploaded by brandonchinn178 at 2026-07-05T06:02:19Z
Category Data, Reflection
Home page https://github.com/brandonchinn178/memo-io#readme
Bug tracker https://github.com/brandonchinn178/memo-io/issues
Source repo head: git clone https://github.com/brandonchinn178/memo-io
Distributions
Downloads 0 total (0 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-07-05 [all 1 reports]

Readme for memo-io-1.0.0.1

[back to package description]

memo-io

GitHub Actions

This library contains two main functions:

  • memoIO :: IO a -> IO (IO a): Return an IO action that will memoize the result of the given action for all subsequent calls.
  • globalIO :: (Typeable a) => IO a -> IO a: Ensure the given IO action runs at most once.

This library also contains MemoIO.Global containing the following helpers for defining top-level global variables using globalIO. While global variables should usually be avoided, it's sometimes helpful in certain situations. NOINLINE is recommended in these cases for performance, but it's not required.

  • newIORef :: (Typeable a) => a -> IORef a
  • newMVar :: (Typeable a) => a -> MVar a
  • newEmptyMVar :: (Typeable a) => MVar a
  • newTVar :: (Typeable a) => a -> TVar a

This library has very minimal dependencies. If you want absolutely zero dependencies other than base, and only want to use memoIO, turn off the global-io flag.

Usage

import Data.IORef
import Data.Time (getCurrentTime)
import MemoIO (memoIO)
import qualified MemoIO.Global as Global

myRef :: IORef Int
myRef = Global.newIORef 0
{-# NOINLINE myRef #-}

main :: IO ()
main = do
  getCurrentTimeOnce <- memoIO getCurrentTime
  t1 <- getCurrentTimeOnce
  t2 <- getCurrentTimeOnce
  -- t1 == t2

  modifyIORef' myRef (+ 1)
  r <- readIORef myRef
  -- r == 1

FAQ

I don't want a dependency! How can I do this myself?

If you want to avoid a dependency for memoIO, you could simply reimplement it yourself. The source code in MemoIO.Internal.Memo only depends on base and can be vendored into your library.

To avoid a dependency for global variables, the key technique is to do the following:

import GHC.Exts (noinline)
import System.IO.Unsafe (unsafePerformIO)

myRef_caf, myRef :: IORef Int
myRef_caf = unsafePerformIO (newIORef 0)
myRef = noinline myRef_caf

The noinline function from GHC.Exts is specially designed to avoid inlining, which is a stronger guarantee than the NOINLINE pragma, which is best-effort and might not prevent some advanced optimizations GHC makes. But it does require breaking out a separate definition, so that the noinline myRef_caf expression itself could be inlined, while the unsafePerformIO ... expression won't.

If GHC < 9.0, import noinline from GHC.Prim from ghc-prim instead.

Global variables bad!

Agreed. In most cases, there are better techniques available. But in some cases, global variables is just the only way to provide the API one wants. If this is the case, you want a semantic guarantee that the compiler respects the global variable.

Some examples:

  • random uses a global variable for the global RNG (ref)
  • GHC itself uses a few global variables, for example caching GCC search directories (ref)

Why not just unsafePerformIO + NOINLINE?

The standard idiom for global variables has been:

myRef :: IORef Int
myRef = unsafePerformIO $ newIORef 0
{-# NOINLINE myRef #-}

This should work in most cases, but the NOINLINE pragma is best effort, and people have anecdotally shared that it doesn't prevent inlining in all cases. See also: I don't want a dependency! How can I do this myself?

  • global-variables is another package doing something similar, but it hasn't been updated since 2012, requires manually specifying a String identifier, and only exports the functions for defining global variables without the general machinery
  • Top level mutable state discussion in the Haskell Wiki
  • Rejected GHC proposal for adding language support