packages feed

generic-labels-0.1.0.1: src/Data/Generic/Labels/Internal.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

{-|
Module: Data.Generic.Labels.Internal

Internal module containing the generics machinery to provide the instances
exported by this library, using 'Data.Generics.Product.Internal.GLens.GLens'.

__Warnings__:

  * contains an incoherent instance for 'GAdapt' which is used to
    prioritise built-in record field names over explicitly labelled types;
  * contains an orphan overlapping instance for @generic-lens@'s 'Data.Generics.Product.Internal.GLens.GLens'
    typeclass, which is used to additionally unwrap through labelled types.

-}

module Data.Generic.Labels.Internal
  ( GAdapt(..) )
  where

-- base
import Data.Kind
  ( Constraint, Type )
import GHC.Generics
import GHC.TypeLits
  ( Symbol )

-- generic-lens-core
import Data.Generics.Product.Internal.GLens
  ( Eval, GLens(..), GLens', TyFun )
import Data.Generics.Internal.Profunctor.Lens
  ( view )
import Data.Generics.Internal.Profunctor.Iso 
  ( Iso, iso, kIso )

-- generic-labels
import Data.Label
  ( (:=)(..), Label(..) )
import Data.Generic.Labels.Internal.Errors
  ( AdaptLabelMessage )

--------------------------------------------------------------------------------
-- Generics machinery for 'Adapt'.

-- | Generic version of 'Data.Generic.Labels.Adapt'.
type GAdapt :: ( Type -> Type ) -> ( Type -> Type ) -> ( Type -> Type ) -> Constraint
class GAdapt args opt all where
  gAdapt :: args p -> opt p -> all p

instance ( GAdapt args opt all1, GAdapt args opt all2 ) => GAdapt args opt ( all1 :*: all2 ) where
  gAdapt args opt = gAdapt args opt :*: gAdapt args opt

instance GAdapt args opt all => GAdapt args opt ( C1 c all ) where
  gAdapt args opt = M1 $ gAdapt args opt

instance GAdapt args opt all => GAdapt args opt ( D1 c all ) where
  gAdapt args opt = M1 $ gAdapt args opt

-- | This instance is INCOHERENT because we assume that no type variable (say @all0@)
-- will later be instantiated to a labelled type @lbl := all@.
--
-- The end result is that, when we have both a built-in Haskell record field name
-- as well as an explicit label, we prioritise the built-in record field name over the label.
instance {-# INCOHERENT #-}
         ( GLens' ( HasTotalLabelPSym lbl ) ( args :*: opts ) all )
      => GAdapt args opts ( M1 m meta ( Rec0 ( lbl := all ) ) )
      where
  gAdapt args opt = M1 . K1 . ( Label @lbl := ) $ view ( glens @( HasTotalLabelPSym lbl ) ) ( args :*: opt )

instance ( GLens' ( HasTotalLabelPSym lbl ) ( args :*: opts ) all )
      => GAdapt args opts ( S1 ( MetaSel ( Just lbl ) p f b ) ( Rec0 all ) )
      where
  gAdapt args opt = M1 . K1 $ view ( glens @( HasTotalLabelPSym lbl ) ) ( args :*: opt )

--------------------------------------------------------------------------------
-- Generic lens machinery.

type And :: Maybe a -> Maybe a -> Maybe a
type family m1 `And` m2 where
  Just a `And` Just a = Just a
  _      `And` _      = Nothing

type Or :: Maybe a -> Maybe a -> Maybe a
type family m1 `Or` m2 where
  Just a `Or` _ = Just a
  _      `Or` b = b

type HasTotalLabelP :: Symbol -> ( Type -> Type ) -> Maybe Type
type family HasTotalLabelP lbl f where
  HasTotalLabelP lbl ( S1 ( MetaSel ( Just lbl ) _ _ _ ) ( Rec0 ty ) ) = 
    Just ty
  HasTotalLabelP lbl ( S1 ( MetaSel ( Just lbl' ) _ _ _ ) _ ) = 
    Nothing
  HasTotalLabelP lbl ( S1 _ ( K1 _ ( lbl := ty ) ) ) =
    Just ty
  HasTotalLabelP lbl ( S1 _ ( K1 _ ( lbl' := _ ) ) ) =
    Nothing
  HasTotalLabelP lbl ( l :*: r ) =
    HasTotalLabelP lbl l `Or` HasTotalLabelP lbl r
  HasTotalLabelP lbl ( l :+: r ) =
    HasTotalLabelP lbl l `And` HasTotalLabelP lbl r
  HasTotalLabelP lbl ( S1 _ _ ) =
    Nothing
  HasTotalLabelP lbl ( C1 _ f ) =
    HasTotalLabelP lbl f
  HasTotalLabelP lbl ( D1 _ f ) =
    HasTotalLabelP lbl f
  HasTotalLabelP lbl ( K1 _ _ ) =
    Nothing
  HasTotalLabelP lbl U1 =
    Nothing
  HasTotalLabelP lbl V1 =
    Nothing

type HasTotalLabelPSym :: Symbol -> TyFun ( Type -> Type ) ( Maybe Type )
data HasTotalLabelPSym lbl f mbTy
type instance Eval ( HasTotalLabelPSym lbl ) f = HasTotalLabelP lbl f

class LabelIso mbLbl1 mbLbl2 s t a b | mbLbl1 s -> a, mbLbl2 t -> b where
  lblIso :: Iso s t a b
instance
  ( AdaptLabelMessage lbl ( Just a1 ) Nothing b1
  , a1 ~ a, b1 ~ b
  ) => LabelIso ( Just lbl ) ( Just lbl ) ( lbl := a1 ) ( lbl := b1 ) a b where
  lblIso = iso ( \ ( _ := a ) -> a ) ( Label @lbl := )
  {-# INLINE lblIso #-}
instance LabelIso Nothing Nothing a b a b where
  lblIso = id
  {-# INLINE lblIso #-}

type GetLabel :: Type -> Maybe Symbol
type family GetLabel ty where
 GetLabel ( lbl := _ ) = Just lbl
 GetLabel _            = Nothing

instance {-# OVERLAPPABLE #-} LabelIso ( GetLabel a' ) ( GetLabel b' ) a' b' a b
      => GLens pred ( K1 r a' ) ( K1 r b' ) a b where
  glens = kIso . lblIso @( GetLabel a' ) @( GetLabel b' )
  {-# INLINE glens #-}