Search code examples
haskellbson

Binary Typing Problem


I am trying to work with the Haskell Bson and I want to save and load them. The saving seems to be no problem, but I get a typing error with the Binary.get functions.

Here's my code:

{-# LANGUAGE GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances #-}
module Database.Axiom where

import Data.Bson (Document, Field)
import Data.Bson.Binary (putDocument, getDocument)
import Data.Binary as B (Binary(..), decodeFile, encodeFile)
import Control.Monad (liftM2)

instance Binary Document where
    put = putDocument
    get = getDocument

data Collection = Collection {
        collectionName :: ByteString,
        collectionDocs :: [Document]
    }

instance Binary Collection where
    put (Collection name docs) = B.put name >> B.put docs
    get = liftM2 Collection B.get B.get -- < Here is the type error

Which leads to this error:

Database/Axiom.hs:24:39:
    Overlapping instances for Binary [Field]
      arising from a use of `B.get'
    Matching instances:
      instance Binary a => Binary [a] -- Defined in Data.Binary
      instance Binary Document -- Defined at Database/Axiom.hs:13:10-24
    In the third argument of `liftM2', namely `B.get'
    In the expression: liftM2 Collection B.get B.get
    In an equation for `get': get = liftM2 Collection B.get B.get

The problem is that Document is merely a synonym of [Field]. But I need an instance for Binary Document, as there are no functions to serialize a single Field. And moreover, BSON does not export any instances for Binary Field, so I am completely confused why this error happens in the first place.

I tried it with strict type declaration and then to use a self made get method, but a get :: [Document] only works nicely when there's a get :: Document method.

So, anyone can help me, perhaps?


Solution

  • Altough a little subjective, I think the cleanest and most robust way to fix is is to add a newtype for Document. Something like:

    import Control.Applicative ((<$>))
    import Data.ByteString (ByteString)
    import Data.Bson (Document, Field)
    import Data.Bson.Binary (putDocument, getDocument)
    import Data.Binary as B (Binary(..), decodeFile, encodeFile)
    import Control.Monad (liftM2)
    
    newtype CollectionDoc = CollectionDoc {unCollectionDoc :: Document}
    
    instance Binary CollectionDoc where
        put = putDocument . unCollectionDoc
        get = CollectionDoc <$> getDocument
    
    data Collection = Collection {
            collectionName :: ByteString,
            collectionDocs :: [CollectionDoc]
        }
    
    instance Binary Collection where
        put (Collection name docs) = B.put name >> B.put docs
        get = liftM2 Collection B.get B.get
    

    should work. In addition, newtype wrappers are completely optimized away, so there is no overhead at runtime.