packages feed

binary-generic-combinators-0.4.3.0: src/Data/Binary/DerivingVia.hs

{-# LANGUAGE TypeOperators, FlexibleContexts, UndecidableInstances #-}
{-# LANGUAGE Safe #-}

{-| Description : Deriving strategies for making 'GHC.Generics.Generic'-based 'Binary' instances more expressive.

This module defines some types to be used with @DerivingVia@ when deriving 'Binary' instances.
-}

module Data.Binary.DerivingVia
( Alternatively(..)
) where

import Control.Applicative
import Data.Binary
import GHC.Generics

-- | Try to deserialize each constructor of @a@ in order.
--
-- For sum types, stock 'Binary' writes (and expects to read) an integer denoting the index of the constructor.
-- This isn't always what's needed. In the following example, the constructor is uniquely identified by the marker byte,
-- and its index in the Haskell ADT is irrelevant:
--
-- > data JfifSegment
-- >   = App0Segment (MatchByte "app0 segment" 0xe0, JfifApp0)
-- >   | DqtSegment  (MatchByte "dqt segment"  0xdb, QuantTable)
-- >   | SofSegment  (MatchByte "sof segment"  0xc0, SofInfo)
-- >   | DhtSegment  (MatchByte "dht segment"  0xc4, HuffmanTable)
-- >   | DriSegment  (MatchByte "dri segment"  0xdd, RestartInterval)
-- >   | SosSegment  (MatchByte "sos segment"  0xda, SosImage)
-- >   | UnknownSegment RawSegment
-- >   deriving Generic
-- >   deriving Binary via Alternatively JfifSegment

newtype Alternatively a = Alternatively { getAlt :: a }

class GAltBinary grecord where
  gAltGet :: Get (grecord p)
  gAltPut :: grecord p -> Put

instance Binary grecord => GAltBinary (K1 i grecord) where
  gAltGet = K1 <$> get
  gAltPut = put . unK1

instance GAltBinary grecord => GAltBinary (M1 i t grecord) where
  gAltGet = M1 <$> gAltGet
  gAltPut = gAltPut . unM1

instance (GAltBinary l, GAltBinary r) => GAltBinary (l :*: r) where
  gAltGet = (:*:) <$> gAltGet <*> gAltGet
  gAltPut (l :*: r) = gAltPut l <> gAltPut r

instance (GAltBinary l, GAltBinary r) => GAltBinary (l :+: r) where
  gAltGet = L1 <$> gAltGet
        <|> R1 <$> gAltGet
  gAltPut (L1 l) = gAltPut l
  gAltPut (R1 r) = gAltPut r

instance (Generic a, GAltBinary (Rep a)) => Binary (Alternatively a) where
  get = Alternatively . to <$> gAltGet
  put = gAltPut . from . getAlt