Haskell's missing mutable reference type

Thanks! I did wonder whether there was a writer equivalent.

I’m not quite reading why you want to do this over reading the value from the IORef just before passing that value as a parameter to your local thread? If you want a new IORef, you can just create another one in the local thread and return/discard it.


main :: IO ()
main = do 
  ref1 <- newIORef "hello"
  -- do stuff with ref1

  val <- readIORef ref1
  concurrently_ -- alternatively, use concurrently here and bind results of interest such as the newrefs or their values.
    ( do
       ref2 <- newIORef val
       -- Do stuff with ref2
       someFunctionOnRef2Val <$> readIORef ref2
       
    )
    (
       ref3 <- newIORef val
       -- Do stuff with ref3
       modifyIORef ref3 (const "Second thread")
    )
  

It solves the issues of:

  1. The reference stays unmodified (unless you explicitly aim to modify ref1) which some greps/Ctrl+Fs will find
  2. The threads can work independently on their own scoped references.

I don’t know, to me it just seems a lot safer to me to have a concrete value I can pass into my thread, rather than build in a race condition on which thread can read the reference first. And while this proposal allows you to work on the same ref, it does feel like it breaks referential transparency a little bit…

It feels similar to a problem I encountered when building a VM to solve the synacor challenge: I initially implemented my opcode functions to take in STRefs for the parameters to the opcode and got a weird bug whenever I had to input text into the machine. I was only able to fix it by instead passing the values from reading those references directly to the functions of interest.

i.e.

type Addr = Word16
type Data = Word16
getArgN :: ST s VM -> Addr -> ST s Data

op2 :: STRef s VM -> (STRef s Data -> STRef s Data -> ST s b) -> ST s b
op2 vmRef impl = do
  arg0 <- newSTRef =<< getArgN vmRef 1 
  arg1 <- newSTRef =<< getArgN vmRef 2 
  advancePC vmRef 3  
  impl arg0 arg1
  

vs.

op2 :: STRef s VM -> (Data -> Data -> ST s b) -> ST s b
op2 vmRef impl = do
  arg0 <- getArgN vmRef 1
  arg1 <- getArgN vmRef 2
  advancePC vmRef 3
  impl arg0 arg1

I mean, I probably made some fatal error in my implementation in the first case, but the second case fixed all my issues entirely.

1 Like

That’s a similar transformation to the one proposed by @prophet and @BurningWitness in this thread. My response is this: Haskell's missing mutable reference type - #7 by tomjaguarpaw.

Basically, your transformation works when you can see the refs that you need to clone. If you can’t, you don’t know that you need to clone them!

I changed the concurrency example to try to make this a bit clearer. Have a look at writeUserDataConcurrently. How would you write it using your proposed transformation? I bet you can’t!

Since the same idea has come up three times now in slightly different forms it’s clearly an important one to address. Can you think of a way I can make it more obvious, or do you have any further questions about it I can help resolve?

Ah I see the problem now. Essentially, there is some invisible stateful reference that I have write-only access to and I want to be able to set it to use a different value on different threads. I can’t lie, that’s a headache-inducing setup lol.

My first instinct is just to change the API so Severity is something I can set locally or pass to functions directly for each Logger I want to use but according to the thread, the purpose of this to enable something that wasn’t possible before so fair enough.

My second instinct is that you’d have to make another call to withStdoutLoggerwith a new initial value in each thread and use those local Loggers but then I’m creating a new logger every thread and trashing it when it’s done which is certainly wasteful and could be blocked if resources cannot be shared.

It’s a bit like that C-design problem of returning a malloc’d pointer vs taking a pointer to be filled as an argument: it forces the caller to make a decision on how the memory should be handled when interacting with this function.

So yeah, I can see why this could be useful but I’d also be very reticent to use any library that forced me to use this design pattern over just letting me take copies of useful values when I need them.

2 Likes