0
votes

Say I have a record where one value is an MVar:

data SomeRecord = SomeRecord { frobs :: MVar Integer }

and I want to be able to encode/decode it from JSON using Aeson. When encoding, the MVar would be unwrapped and the raw value encoded, and the reverse would happen on decoding.

It would be nice to be able to just write a FromJSON instance that returns an IO (Maybe SomeRecord) and a ToJSON instance that returns an IO ByteString, but since the Parser monad isn't an instance of MonadIO I don't think that's possible.

So far I've resorted to writing functions to translate between the MVar-encumbered record and a nearly identical record without the MVar type, and then encoding/decoding that.

I've tried to find some way to keep the MVar out of my record in the first place. It seems like that would be ideal. But supposing that I can't do that for whatever reason, is there an easier way to handle the JSON encoding/decoding?

EDIT:

I wonder if I'm asking the wrong question. Maybe my whole approach is incorrect. What I'm trying to do is allow a bunch of connected clients (each on a different thread) to add/edit/delete a list of objects. Here's what the types look like:

-- the data type for each "room"
data Instance = Instance 
    { iName    :: T.Text
    , iObjects :: M.HashMap T.Text (MVar Store)
    ...
    }

-- the data type for a particular object in the room that can be changed
data Store = Store
    { sObject :: A.Value
    ...
    }

Each "room" has an Instance that holds that rooms objects. The Instance itself is in an MVar to synchronize additions/deletions of the iObjects hashmap, and each individual store is in an MVar so that the whole data structure doesn't have to block while an individual object is updated.

So an update operation proceeds like this:

  • readMVar on the instance to get at the iObjects hash
  • M.lookup a particular store
  • modifyMVar on the store to do the update

Is there a more idiomatic haskell approach than using nested MVars like this? Ideally some way that kept the MVars away from the data so persisting the whole structure would be simple.

2

2 Answers

5
votes

No, you have a concurrency primitive buried in a pure data structure. That's always going to be a bit awkward to handle. You're smuggling side effects into whatever tries to touch your MVar.

Try parameterizing the data type by the container for the Integer. E.g.

data T a = T { frobs :: c Integer }

then you can work on it instantiated to T MVar, and for streaming, unwrap, then operate on it as a T One, where data One a = One a.

0
votes

The FromJSON and ToJSON type classes are really only thee to provide convenience when writing parsing/printing code. If we didn't have them, we'd still be able to write parsers and printers, they just would require more boilerplate. As you have discovered, these type classes are not formulated in a way that works with impure values. So basically, you're stuck with the other approach and the extra boilerplate that comes with it.

There are a few options for making it better. You could copy FromJSON and ToJSON and modify them to support parsers and printers that use monadic IO. If you're using this pattern a lot, that might be worth it. Alternatively, you could use unsafePerformIO, although that seems like a really bad idea...maybe not even worth mentioning since it could easily cause big problems if you don't know exactly what you're doing.

And of course, like you've mentioned, you could try to make your structure pure. That seems like the best approach to me. You could have another structure that has the MVars and then a function that populates the pure structure from the impure one. In that way, the pure structure functions as a path to easier JSON parsing/printing for the mutable structure.