# Haskell's missing mutable reference type

**URL:** <https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248>\
**Category:** Links\
**Created:** [June 9, 2026, 9:32am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248 "2026-06-09T09:32:56Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 9, 2026, 9:32am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/1 "2026-06-09T09:32:56Z")

</div>

Whilst looking at the [Scoped thread-locals](https://github.com/ghc-proposals/ghc-proposals/pull/751) GHC proposal I realised that Haskell is missing a mutable reference type that some other languges have (and people have tried to encode in Haskell), so I wrote it up:

- [haskells-missing-mutable-ref](https://h2.jaguarpaw.co.uk/posts/haskells-missing-mutable-ref/)

---

<div class="post-metadata">

**Author:** ![prophet](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/prophet/32/5031_2.png) [@prophet](https://discourse.haskell.org/u/prophet)\
**Post date:** [June 9, 2026, 11:32am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/2 "2026-06-09T11:32:09Z")

</div>

So, I’m guessing the use case here is that you have a computation that already somehow captures the `IOScopedRef`, as in

```hs
do
    ref <- newIOScopedRef 0
    let printCurrent = do
              current <- readIOScopedRef ref
              print current
    modifyScopedIORef ref (+1) do
         ...
         printCurrent -- implicitly uses the captured 'ref'

```

(otherwise I’m not sure how this would be different from just using a regular variable and shadowing it)

Do you have an example where this is useful to have?

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 9, 2026, 11:46am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/3 "2026-06-09T11:46:50Z")

</div>

> [@prophet](#):
>
> I’m guessing the use case here is that you have a computation that already somehow captures the `IOScopedRef`

Right.

> [@prophet](#):
>
> Do you have an example where this is useful to have?

“[A logging library](https://h2.jaguarpaw.co.uk/posts/haskells-missing-mutable-ref/#a-logging-library)” from the article is supposed to provide such an example. Is there something I could do to make that clearer? (Also feel free to skip forward two articles in the series to “Fork-fragile reader-like operations in Haskell” to see more.)

---

<div class="post-metadata">

**Author:** ![prophet](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/prophet/32/5031_2.png) [@prophet](https://discourse.haskell.org/u/prophet)\
**Post date:** [June 9, 2026, 12:06pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/4 "2026-06-09T12:06:25Z")

</div>

Unless I’m totally misreading something, the logging example _doesn’t_ capture the logger. Everything that accesses it also takes it as a parameter. So it’s really equivalent to

```hs
loggerExample :: IO ()
loggerExample = do
  let logger = newLogger 0
  logMsg logger 1 "Getting user"
  user <- getUser
  logMsg logger 1 ("Is VIP: " <> show (isVip user))
  let modification = if isVip user then (+ 10) else id
  d <- do
    -- Instead of overriding it with a continuation, we can just shadow the variable
    -- (the (<-) and pure are necessary since regular lets are recursive and don't allow shadowing)
    logger <- pure (modifySeverity logger modification)
    logMsg logger 0 "Getting data"
    getData user
  writeData d
  logMsg logger 0 "Done"

```

and then I don’t see the advantage of passing this additional reference around if all you’re doing with it is modifying it in a lexically scoped way like you already can with regular variables

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 9, 2026, 12:27pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/5 "2026-06-09T12:27:07Z")

</div>

> [@prophet](#):
>
> the logging example _doesn’t_ capture the logger

Well, the definition of `Logger` does not use `IOScopedRef` directly. `Logger` could be defined in a module that doesn’t even know `IOScopedRef` exists. The `IOScopedRef` _is_ captured in the closures that are stored in the `Logger` value, isn’t it? If not, what do you mean by “capture”?

> [@prophet](#):
>
> So it’s really equivalent to

Sure, if you inline and use shadowing you can obtain similar behaviour. But what about when you can’t inline everything, because you’re trying to combine code from two independent libraries?

> I don’t see the advantage of passing this additional reference around if all you’re doing with it is modifying it in a lexically scoped way like you already can with regular variables

OK, then tell me how I could obtain the same behaviour using a lexically-scoped variable but without `IOScopedRef`.

---

<div class="post-metadata">

**Author:** ![prophet](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/prophet/32/5031_2.png) [@prophet](https://discourse.haskell.org/u/prophet)\
**Post date:** [June 9, 2026, 12:39pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/6 "2026-06-09T12:39:49Z")

</div>

Oops, i did misread the example slightly sorry ^^ You’re using the `IOScopedRef` to implement `modifySeverity` not to modify the `Logger` value directly.

My point still stands though. Everything that (indirectly) uses this `IOScopedRef` needs to go through the `logger` variable anyway so you might as well use it to carry the state in a lexically scoped way.

> OK, then tell me how I could obtain the same behaviour using a lexically-scoped variable but without `IOScopedRef`.

```hs
data Logger = Logger {
    logImpl :: Severity -> String -> IO (),
    baseSeverity :: Severity
}

logMsg :: Logger -> Severity -> String -> IO ()
logMsg logger severity msg = logger.logImpl (logger.baseSeverity + severity) msg

newLogger :: Severity -> Logger
newLogger baseSeverity = Logger { baseSeverity, logImpl = \severity message -> putStrLn ... }

modifySeverity :: Logger -> (Severity -> Severity) -> Logger
modifySeverity logger f = logger { baseSeverity = f logger.baseSeverity }

loggerExample :: IO ()
loggerExample = do
  let logger = newLogger 0
  logMsg logger 1 "Getting user"
  user <- getUser
  logMsg logger 1 ("Is VIP: " <> show (isVip user))
  let modification = if isVip user then (+ 10) else id
  d <- do
    logger <- pure (modifySeverity logger modification)
    logMsg logger 0 "Getting data"
    getData user
  writeData d
  logMsg logger 0 "Done"

```

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 9, 2026, 1:06pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/7 "2026-06-09T13:06:41Z")

</div>

OK, I think I probably understand what you mean. You mean that if you’re passing the logger around everywhere you may as well replace all instances of

```haskell
modifySeverity logger f $ do
  ...

```

with

```haskell
logger <- modifySeverity logger f
...

```

and so you’re asking for an example where you _can’t_ do that transformation. Sure. First, notice that you can only do that transformation if you can see the call to `modifySeverity`. So the answer is that you can’t do that transformation anywhere that `modifySeverity` is being used in a way that the caller can’t see. For example, suppose I have a function from another library with this fype

```haskell
libFun ::
  (String -> IO ()) ->
  (forall r. Int -> IO r -> IO r) ->
  IO T

```

Then if I have a `logger :: Logger` implemented with an `IOScopedRef` I can call `libFun` like

```haskell
libFun
  (\s -> logMsg logger 0 s)
  (\i body -> modifySeverity (const i) body)

```

There’s no way I can do that with lexical scoping. `libFun` doesn’t know that `modifySeverity` might be used within it! I’m guessing that’s what you meant by “having a computation that already somehow captures the `IOScopedRef`”?

* * *

> [@prophet](#):
>
> Everything that (indirectly) uses this `IOScopedRef` needs to go through the `logger` variable anyway so you might as well use it to carry the state in a lexically scoped way.

Yeah, I think didn’t explain well what I meant. There are two subtly different questions:

1. Can you get something with the same API as `Logger` but without using `IOScopedRef`? (i.e. `modifySeverity` takes an explicit body)

2. Can you get something with a _different_ API to `Logger` that does the same job in some cases? (i.e. `modifySeverity` doesn’t take a body, rather the modification is observable in the continuation)

The difference is important because only something with the _same_ API (i.e. `modifySeverity` takes a body) can be used with `libFun`.

Your original question was

> Do you have an example where this is useful to have?

My claim is that `Logger` is an API that is unachievable without `IOScopedRef`. That is, my `loggerExample` cannot be written _as written_ without `IOScopedRef`. Your original point was: but it can be written in a different way without `IOScopedRef`. That’s a fair point. I can say “the API itself is useful” but you could always come back and ask _why_ it’s more useful than the lexical scoping way. In which case, the response would be that the lexical scoping way is not sufficient to integrate with `libFun`. (Then someone might say "well `libFun` should be different, which may be so, but I’d like to know how.) I’ll see if I can think of a way of making this clear in the article.

* * *

EDIT: Oh, now I’ve remembered what I originally meant, but I forgot the exact code in my article, so what I said didn’t quite make sense. What I meant was that _this_ is an example of the sort you’re looking for, I think:

```haskell
loggerExample :: IO ()
loggerExample = withStdoutLogger 0 useLogger

useLogger :: Logger -> IO ()
useLogger = \logger -> do
  logMsg logger 1 "Getting user"
  user <- getUser
  logMsg logger 1 ("Is VIP: " <> show (isVip user))
  let modification = if isVip user then (+ 10) else id
  d <- modifySeverity logger modification $ do
    logMsg logger 0 "Getting data"
    getData user
  writeData d
  logMsg logger 0 "Done"

```

Specifically, suppose the definition of `Logger` and `useLogger` are given to me; I can’t change them. How can I instantiate `Logger` so that it has the behaviour I want? I don’t think I can unless I have `IOScopedRef`.

EDIT: OK, I split off `writeUserData` from the original example, so at least _that_ is a function I can’t call the way I want without `IOScopedRef`.

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 9, 2026, 4:51pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/8 "2026-06-09T16:51:34Z")

</div>

Okay, I had to spend 40 minutes figuring out what this is, and I’m still not sure.

Am I correct to understand that the problem you’re looking to solve is that of a user-definable implicit global context, which can be adjusted locally, e.g.

```haskell
overriding :: Contextual a -> (a -> a) -> (forall b. IO b -> IO b) -- deeply magical

contextual x :: Contextual Int -- still magical
contextual x = 5

foo = do
  a <- getContextual x
  print a

--- Library user doesn't get to implement anything above this line

-- Prints number 7
main = overriding x (+2) foo

```

I assumed if a feature like this were to exist it’d be on the compiler to collect all the “contextual” references upfront. This wouldn’t be mutable from user perspective, so I wouldn’t bring up `IORef` at all when talking about it.

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 9, 2026, 5:37pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/9 "2026-06-09T17:37:30Z")

</div>

Thanks for the feedback!

> [@BurningWitness](#):
>
> Okay, I had to spend 40 minutes figuring out what this is, and I’m still not sure.

Maybe the reference implementation in the next article (linked at the top of the current one) will help?

> [@BurningWitness](#):
>
> Am I correct to understand that the problem you’re looking to solve is that of a user-definable implicit global context, which can be adjusted locally,

Yes.

> [@BurningWitness](#):
>
> I assumed if a feature like this were to exist it’d be on the compiler to collect all the “contextual” references upfront.

I’m not sure what that means. Can you elaborate?

> [@BurningWitness](#):
>
> ```haskell
> contextual x :: Contextual Int -- still magical
> contextual x = 5
> 
> ```

FWIW, this is not allowed in my API. I suppose an API that allows it may be reasonable nonetheless.

> [@BurningWitness](#):
>
> This wouldn’t be mutable from user perspective, so I wouldn’t bring up `IORef` at all when talking about it.

Why is it not “mutable from the user perspective”? The value of `x` “mutates” here, doesn’t it?

```haskell
a <- getContextual x
print a
overriding x (+2) $ do
  b <- getContextual x
  print b

```

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 9, 2026, 6:53pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/10 "2026-06-09T18:53:36Z")

</div>

> [@tomjaguarpaw](#):
>
> Why is it not “mutable from the user perspective”? The value of `x` “mutates” here, doesn’t it?

It’s mutating if you look at the flow imperatively, but that would be an implementation detail. I instead think of `x` as a stack of changes over the initial value; no matter what I do, upon reaching the end of an `overriding` function `x` will be exactly the same as it was immediately before entering it.

In this regard it’s no more a mutation than `let y = (+2) 5`.

> [@tomjaguarpaw](#):
>
> I’m not sure what “collecting all the references upfront” means. Can you elaborate?

My naive assumption is that a feature like this would be implemented by having every worker thread carry around a table of these kinds of references, and the size of the table would be determined at compilation time.

Every `contextual` declaration would then be a reservation for a seat at the table.

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 9, 2026, 8:57pm UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/11 "2026-06-09T20:57:22Z")

</div>

> [@BurningWitness](#):
>
> It’s mutating if you look at the flow imperatively, but that would be an implementation detail.

That’s a fine point of view. I don’t have a strong view on what “mutable” should mean, but I think it’s interesting to know that the fact that `IORef` is implemented by actually mutating the value stored in a piece of memory is also an implementation detail. According to [The Key Monad](https://dl.acm.org/doi/epdf/10.1145/3241625.2976008) it also has a completely pure implementation.

> [@BurningWitness](#):
>
> My naive assumption is that a feature like this would be implemented by having every worker thread carry around a table of these kinds of references, and the size of the table would be determined at compilation time.

Ah right, the size doesn’t have to be determined up front. It can grow dynamically.

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 10, 2026, 5:47am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/12 "2026-06-10T05:47:56Z")

</div>

> [@tomjaguarpaw](#):
>
> It can grow dynamically.

Yes, but that would require a completely different implementation. No matter how you slice it these references will have to live in CPU cache, and a static table is the most compact solution to that.

Much like with [[RFC] Mutable records as a GHC extension](https://discourse.haskell.org/t/rfc-mutable-records-as-a-ghc-extension/13886), I view a simple solution that fits the existing model as infinitely more preferable to both the status quo and the “dependent types are just a week away” line of approach.

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 10, 2026, 6:40am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/13 "2026-06-10T06:40:15Z")

</div>

I’m not sure exactly what implementation you’re thinking of, but a table which grows dynamically is essential to implement [`withIOScopedRef`](https://h2.jaguarpaw.co.uk/posts/haskells-missing-mutable-ref/#the-api), which allows references to be allocated dynamically. However, that variable-size table can be implemented as a `Vault` inside a fixed-size table: [A reference implementation of IOScopedRef](https://discourse.haskell.org/t/a-reference-implementation-of-ioscopedref/14253)

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 10, 2026, 6:54am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/14 "2026-06-10T06:54:15Z")

</div>

> [@tomjaguarpaw](#):
>
> which allows references to be allocated dynamically

And that’s a feature you’ll have to argue for.

Per my earlier example the piece of code we’re looking to allow for is

```haskell
main = overriding x (+2) foo

```

such that `overriding x` modifies the behavior of `foo` _without_ passing `x` to `foo`.

Your implementation uses `ReaderT Vault`, which, as others have pointed out, is isomorphic to passing `Vault` to every single function, i.e.

```haskell
main vault = overriding vault x (+2) $ foo vault

```

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 10, 2026, 7:00am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/15 "2026-06-10T07:00:46Z")

</div>

I’m still not quite sure what you mean. Yes, `withIOScopedRef` in particular, and `IOScopedRef` in general, is a feature I am arguing for! Part of its essential behaviour is to allow references to be allocated dynamically, just like an essential feature of `IORef` is that they can be allocated dynamically.

But suppose I had your `contextual`. I could define

```haskell
contextual vault :: Contextual Vault
contextual vault = Vault.empty

```

to reconcile my interface with your implementation, couldn’t I?

(This is essentially what [A reference implementation of IOScopedRef](https://discourse.haskell.org/t/a-reference-implementation-of-ioscopedref/14253/1) suggests: _one_ global contextual variable, which is a vault.)

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 10, 2026, 7:12am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/16 "2026-06-10T07:12:51Z")

</div>

If the value is dynamically allocated, then how do you use it inside a function without passing it? And if you’re passing a value, why not determine the wanted behavior based on passed value instead? The following is obviously not valid Haskell:

```haskell
module Foo (foo) where

withIOScopedRef 5 $ \x ->
  let foo = _ x

```

The point here is that `x` is not a _value_, it’s a place. `foo` expects a value to be there when it executes, user knows they can augment it.

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 10, 2026, 7:35am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/17 "2026-06-10T07:35:46Z")

</div>

I think I see: you are making the claim that dynamically allocating such references is not useful?

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 10, 2026, 7:47am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/18 "2026-06-10T07:47:10Z")

</div>

Slightly stronger, I don’t see how you can implement that without breaking some fundamental part of the language (I think referential transparency, but I don’t have the background to concisely describe it).

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [June 10, 2026, 7:51am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/19 "2026-06-10T07:51:24Z")

</div>

> [@BurningWitness](#):
>
> I don’t see how you can implement that without breaking some fundamental part of the language

Well, I explained how I can get my API from yours.

And another potential implementation is explained here: [A reference implementation of IOScopedRef](https://discourse.haskell.org/t/a-reference-implementation-of-ioscopedref/14253/1)

---

<div class="post-metadata">

**Author:** ![BurningWitness](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/burningwitness/32/3665_2.png) [@BurningWitness](https://discourse.haskell.org/u/BurningWitness)\
**Post date:** [June 10, 2026, 8:14am UTC](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248/20 "2026-06-10T08:14:07Z")

</div>

> [@tomjaguarpaw](#):
>
> Well, I explained how I can get my API from yours.

Yes, and I explained that I have no idea what “reading from the `vault`” would look like. I’m essentially getting

```haskell
contextual vault :: Contextual Vault

foo :: IO ()
foo = do
  a <- _ vault
  print a

---

main =
  withScopedIORef vault 5 $ \x ->
    overriding x (+2) $
      foo

```

with the questions:

- How is `foo` expected to access `x` without breaking type safety? Passing a key is not an answer, that’s still data.

- What happens if `main = foo`?

I guess you could solve both of these if `IO` is an effect that carries `x` at the type level, but that would require proper type-level programming implemented first.

[Next page](https://discourse.haskell.org/t/haskells-missing-mutable-reference-type/14248.md?page=2)
