Haskell's missing mutable reference type

I still must be missing something, or not explaining myself well. foo is not expected to access x if x is not in scope in the body of foo. Passing a key is exactly what is expected. I don’t understand why that’s not an answer.

I don’t understand what that means.

I’m definitely explaining myself badly then. I am certainly suggesting nothing of the sort.

I believe you are proposing this interface to “contextual variables” which you believe is safe and implementable:

v :: Contextual t
contextual v = e :: t

getContextual :: Contextual t -> IO t

overriding :: Contextual t -> (t -> t) -> IO r -> IO r

If so, then I can implement what I want as follows

type IOScopedRef a = Vault.Key a

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

withIOScopedRef :: a -> (IOScopedRef a -> IO r) -> IO r
withIOScopedRef a body = do
  key <- Vault.newKey
  overriding vault (Vault.insert key a) $ do
    body key

readIOScopedRef :: IOScopedRef a -> IO a
readIOScopedRef key =
  fmap (fromJust . Vault.lookup key) getContextual vault

modifyIOScopedRef ::
  (a -> a) -> IOScopedRef a -> IO r -> IO r
modifyIOScopedRef f key body =
  overriding vault (Vault.adjust f) key body

If your “contextual” API exists then my IOScopedRef can be layered on top. Seems straightforward. And it’s roughly what I explain at A reference implementation of IOScopedRef. What remains to be resolved?

The question then is how to implement foo in terms of these three functions. This implies

foo :: IOScopedRef a -> IO ()
foo ref = do
  a <- readIOScopedRef ref
  print a

main =
  withScopedIORef 5 $ \ref ->
    modifyIOScopedRef (+2) ref $
      foo ref

which can be reduced to

foo :: Show a => a -> IO ()
foo = print

main = do
  let ref = 5
  foo $ (+2) ref

which is thread-safe and exception-tolerant in exactly the way you expect.

1 Like

I agree with you, but what are you using that argument to conclude?

The conclusion would be that dynamic allocation of these kinds of references is unnecessary, as the language being pure already solves for the wanted behavior.

1 Like

If it’s unnecessary then what is the alternative way that I can instantiate the Logger argument to writeUserData to get the behaviour I want, in haskells-missing-mutable-ref?

It’s not a “you can” question, the person writing the library either provides you with the functions to do a given thing or they don’t.

In current Haskell you need to augment the Logger and pass the modified value to all relevant functions.

With references the library would declare an implicit configuration reference and you’d augment that for all functions invoked within a context.

1 Like

OK, so are we agreed, then, that if IOScopedRef existed then I could instantiate the Logger argument to writeUserData to get the behaviour I desire? And that with only the primitives provided by Haskell as it currently stands, the Logger argument to writeUserData cannot be instantiated in a way that gives me the behaviour that I desire. Instead Logger or writeUserData or both would need to be changed?

I don’t know what “with references” means.

Yes, though I would object to this feature being implemented like that.

As in with either contextual values I outlined above or IOScopedRef.

1 Like

Sorry, what feature? Do you mean I shouldn’t use IOScopedRef (if it existed) to get the behaviour I desire, or IOScopedRef shouldn’t be implemented? In either case, what do you object to?

With “references”, the libraries providing Logger and writeUserData need not know anything about the existence of “references”. Only the caller need use them.

Not exactly, my point is that tacking dynamic allocations onto this feature is at best unnecessary and at worst would pollute the language with a second way to do something it already excels at.

A static reference table is the “missing” thing in the language as I see it.

I don’t think this has anything to do with whether the references are static or dynamic, you can wrap the library either way.

1 Like

I don’t understand. What is “this feature”? Your “contextual”?

OK, fine, and if that missing thing is added, I’ll add the API that I want on top. What’s wrong with that?

I agree. But you said “With references the library would declare an implicit configuration” (my emphasis). I’m pointing out it’s not the library that declares the implicit configuration, it’s the caller.

Yes, worker threads carrying tables of references with table size defined at compilation time.

Would this make for a good point of abstraction though? I’d expect a given library to need several internal values be adjustable, not some interface type like Logger.

Consider something simpler with no extra inputs whatsoever, like adjustable numeric precision or getArgs.

1 Like

Ah, well now it’s a question of API design, an area where I suspect, based on this comment, we will disagree, but not really the point of the original article. Happy to continue in a fresh thread if you create one. Thanks for the discussion so far!