packages feed

ten-0.1.0.0: src/Data/Ten/Ap.hs

-- Copyright 2018-2021 Google LLC
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--      http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

-- | Provides a field wrapper type to make @Generic1@ work with @Functor10@ etc.
--
-- GHC can't derive 'GHC.Generics.Generic1' instances for types that apply
-- their type parameter to a constant type (e.g. @data Thing f = Thing (f
-- Int)@, but it can handle the equivalent type when the application is hidden
-- under a newtype: @data Thing f = Thing (Ap10 Int f)@.  So, by wrapping each
-- field in this newtype and providing the appropriate instances, we can use
-- Generics to derive instances for the whole hierarchy of
-- 'Data.Ten.Functor.Functor10' and related classes.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.Ten.Ap
         ( -- * Field Wrapper
           Ap10(..)
           -- * Instances
           -- $ap10_instances
         ) where

import Data.Kind (Constraint, Type)
import GHC.Generics (Generic)

import Control.DeepSeq (NFData)
import Data.Default.Class (Default(..))
import Data.Hashable (Hashable(..))
import Data.Portray (Portray(..))
import Data.Portray.Diff (Diff)
import Data.Wrapped (Wrapped(..))

-- | A 'Data.Ten.Functor.Functor10' made by applying the argument to some type.
newtype Ap10 (a :: k) (f :: k -> Type) = Ap10 { unAp10 :: f a }
  deriving Generic

-- $ap10_instances
--
-- [Note: Ap10 instances]
--
-- Since @Ap10 a f@ is a newtype over @f a@, it can adopt any instance that
-- @f a@ has, e.g. @Eq (f a) => Eq (Ap10 a f)@.  This doesn't play very nicely
-- with inference of derived instance contexts, though: if you say @deriving
-- Eq@ on a type with an @f@ type parameter with an @Ap10 T f@ field, GHC will
-- complain about the missing instance @Eq (f T)@ rather than adding it to the
-- context.  However, if we can arrange for this to be expressed as a
-- Haskell98-looking constraint of the form @C f@, GHC will be willing to add
-- that to the inferred context.
--
-- We can do this by adding a new class @EqAp f@ with the instance we really
-- want as a superclass, and using that as the context of 'Ap10'\'s @Eq@
-- instance.  Now when trying to solve @Eq (Ap10 T f)@, GHC will simplify to
-- @(EqAp f, EqCtx f T)@.  However, if we have just a catch-all instance for
-- @EqAp@, GHC will simplify it further to the instance context of that
-- instance, which would bring us back to a constraint GHC won't add to the
-- context, @forall a. Eq a => Eq (f a)@.  We have to prevent GHC from doing
-- that simplification, which we can achieve by overlapping it with some other
-- instance, so that GHC can't choose the catch-all instance without knowing
-- more about @f@.  To avoid weird behavior from the overlap, we make an
-- otherwise-unused type 'Decoy' to carry the instance.
--
-- Finally, because @Ap10@ is poly-kinded, if we used @Eq@ directly as the
-- context of that quantified constraint, we'd be saying that @Ap10@ can only
-- be @Eq@ when its hidden kind parameter is @Type@.  Instead, we generalize it
-- to an associated type family 'EqCtx'.  This might be e.g.
-- 'GHC.TypeNats.KnownNat' for 'GHC.TypeNats.Nat's, or simply nothing for
-- phantom type parameters.  I'm not yet sure how to approach the instances for
-- other kinds -- for instance, should we provide stock ones, or expect users
-- to write kind-level newtypes and provide their own instances?
--
-- This trickery is applied to all the instances of Ap10.  In particular this
-- means @deriving (Eq, Ord, Read, Show, Default, NFData)@ and
-- @deriving (Portray, Diff) via Wrapped Generic T@ will all work.

newtype Decoy a = Decoy ()
  deriving stock (Eq, Ord, Read, Show, Generic)
  deriving newtype (Default, NFData, Hashable)
  deriving (Diff, Portray) via Wrapped Generic (Decoy a)

-- See [Note: Ap10 instances]
class (forall a. PortrayCtx f a => Portray (f a))
   => PortrayAp (f :: k -> Type) where
  type PortrayCtx f :: k -> Constraint

instance (forall a. Portray a => Portray (f a)) => PortrayAp f where
  type PortrayCtx f = Portray

instance {-# OVERLAPS #-} PortrayAp (Decoy :: Type -> Type) where
  type PortrayCtx Decoy = Portray

deriving newtype instance (PortrayCtx f a, PortrayAp f) => Portray (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. DiffCtx f a => Diff (f a)) => DiffAp (f :: k -> Type) where
  type DiffCtx f :: k -> Constraint

instance (forall a. Diff a => Diff (f a)) => DiffAp f where
  type DiffCtx f = Diff

instance {-# OVERLAPS #-} DiffAp (Decoy :: Type -> Type) where
  type DiffCtx Decoy = Diff

deriving newtype instance (DiffCtx f a, DiffAp f) => Diff (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. EqCtx f a => Eq (f a)) => EqAp (f :: k -> Type) where
  type EqCtx f :: k -> Constraint

instance (forall a. Eq a => Eq (f a)) => EqAp f where
  type EqCtx f = Eq

instance {-# OVERLAPS #-} EqAp (Decoy :: Type -> Type) where
  type EqCtx Decoy = Eq

deriving newtype instance (EqCtx f a, EqAp f) => Eq (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. OrdCtx f a => Ord (f a)) => OrdAp (f :: k -> Type) where
  type OrdCtx f :: k -> Constraint

instance (forall a. Ord a => Ord (f a)) => OrdAp f where
  type OrdCtx f = Ord

instance {-# OVERLAPS #-} OrdAp (Decoy :: Type -> Type) where
  type OrdCtx Decoy = Ord

deriving newtype
  instance (OrdCtx f a, OrdAp f, EqCtx f a, EqAp f) => Ord (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. ReadCtx f a => Read (f a)) => ReadAp (f :: k -> Type) where
  type ReadCtx f :: k -> Constraint

instance (forall a. Read a => Read (f a)) => ReadAp f where
  type ReadCtx f = Read

instance {-# OVERLAPS #-} ReadAp (Decoy :: Type -> Type) where
  type ReadCtx Decoy = Read

deriving newtype instance (ReadCtx f a, ReadAp f) => Read (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. ShowCtx f a => Show (f a)) => ShowAp (f :: k -> Type) where
  type ShowCtx f :: k -> Constraint

instance (forall a. Show a => Show (f a)) => ShowAp f where
  type ShowCtx f = Show

instance {-# OVERLAPS #-} ShowAp (Decoy :: Type -> Type) where
  type ShowCtx Decoy = Show

deriving newtype instance (ShowCtx f a, ShowAp f) => Show (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. DefaultCtx f a => Default (f a)) => DefaultAp (f :: k -> Type) where
  type DefaultCtx f :: k -> Constraint

instance (forall a. Default a => Default (f a)) => DefaultAp f where
  type DefaultCtx f = Default

instance {-# OVERLAPS #-} DefaultAp (Decoy :: Type -> Type) where
  type DefaultCtx Decoy = Default

deriving newtype instance (DefaultCtx f a, DefaultAp f) => Default (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. NFDataCtx f a => NFData (f a)) => NFDataAp (f :: k -> Type) where
  type NFDataCtx f :: k -> Constraint

instance (forall a. NFData a => NFData (f a)) => NFDataAp f where
  type NFDataCtx f = NFData

instance {-# OVERLAPS #-} NFDataAp (Decoy :: Type -> Type) where
  type NFDataCtx Decoy = NFData

deriving newtype instance (NFDataCtx f a, NFDataAp f) => NFData (Ap10 a f)

-- See [Note: Ap10 instances]
class (forall a. HashableCtx f a => Hashable (f a)) => HashableAp (f :: k -> Type) where
  type HashableCtx f :: k -> Constraint

instance (forall a. Hashable a => Hashable (f a)) => HashableAp f where
  type HashableCtx f = Hashable

instance {-# OVERLAPS #-} HashableAp (Decoy :: Type -> Type) where
  type HashableCtx Decoy = Hashable

deriving newtype instance (HashableCtx f a, HashableAp f) => Hashable (Ap10 a f)