either-n-0.1.0.0: src/Data/Lens/Injection/Generic/Internal.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- UndecidableInstances is needed because finding the nth constructor computes
-- with type families in instance contexts: GCount (f :+: g) adds the counts of
-- its branches, and the instances for :+: compare and subtract that count.
-- Resolution terminates: GCount recurses only on the smaller branches f and g
-- of f :+: g, and every GInjection, GInjectionSum and GProduct constraint in an
-- instance context is on a component of the instance head (the inside of an
-- M1, a branch of a :+:, or a factor of a :*:), so each step of resolution is
-- on a smaller representation type. Rep, CmpNat, + and - are evaluated by GHC,
-- and IsLT and AssertZero are not recursive.
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wall #-}
{- | The generic machinery behind 'Data.Lens.Injection.Generic.injection'.
This module is internal: its contents may change in any release, without a
major version bump. Use "Data.Lens.Injection.Generic" instead.
-}
module Data.Lens.Injection.Generic.Internal (
GInjection (..),
GInjectionSum (..),
GProduct (..),
GCount,
IsLT,
AssertZero,
) where
import Control.Lens (Prism, iso, prism, withPrism)
import Data.Bifunctor (first)
import Data.Kind (Constraint, Type)
import Data.Proxy (Proxy (..))
import GHC.Generics (C, C1, D, D1, Generic (..), K1 (..), M1 (..), S, U1 (..), (:*:) (..), (:+:) (..))
import GHC.TypeLits (CmpNat, ErrorMessage (..), Nat, TypeError, type (+), type (-))
-- | The number of constructors in a generic sum.
type family GCount (f :: Type -> Type) :: Nat where
GCount (f :+: g) = GCount f + GCount g
GCount _ = 1
-- | Whether an 'Ordering' is 'LT'.
type family IsLT (o :: Ordering) :: Bool where
IsLT 'LT = 'True
IsLT _ = 'False
-- | The index has reached a constructor, so it must be 0, otherwise it was out of range.
type family AssertZero (n :: Nat) :: Constraint where
AssertZero 0 = ()
AssertZero n = TypeError ('Text "Injection index out of range: it is " ':<>: 'ShowType n ':<>: 'Text " past the last constructor")
-- | A prism to the @n@th constructor of a generic representation.
class GInjection (n :: Nat) (s :: Type -> Type) (t :: Type -> Type) a b where
gInjection :: proxy n -> Prism (s x) (t x) a b
instance (GInjection n s t a b) => GInjection n (M1 D m s) (M1 D m' t) a b where
gInjection n =
iso unM1 M1 . gInjection n
{-# INLINE gInjection #-}
instance (GInjectionSum (IsLT (CmpNat n (GCount f))) n f g f' g' a b) => GInjection n (f :+: g) (f' :+: g') a b where
gInjection =
gInjectionSum (Proxy :: Proxy (IsLT (CmpNat n (GCount f))))
{-# INLINE gInjection #-}
-- | A constructor with no fields.
instance (AssertZero n, a ~ (), b ~ ()) => GInjection n (M1 C m U1) (M1 C m' U1) a b where
gInjection _ =
iso (const ()) (const (M1 U1))
{-# INLINE gInjection #-}
-- | A constructor with one field.
instance (AssertZero n, x ~ a, y ~ b) => GInjection n (M1 C m (M1 S ms (K1 i x))) (M1 C m' (M1 S ms' (K1 i' y))) a b where
gInjection _ =
iso (unK1 . unM1 . unM1) (M1 . M1 . K1)
{-# INLINE gInjection #-}
-- | A constructor with more than one field.
instance
( AssertZero n
, Generic a
, Generic b
, Rep a ~ D1 da (C1 ca p)
, Rep b ~ D1 db (C1 cb q)
, GProduct (f :*: g) p
, GProduct (f' :*: g') q
) =>
GInjection n (M1 C m (f :*: g)) (M1 C m' (f' :*: g')) a b
where
gInjection _ =
iso
(\(M1 x) -> to (M1 (M1 (gProductTo x))))
(\b -> case from b of M1 (M1 y) -> M1 (gProductFrom y))
{-# INLINE gInjection #-}
{- | Descend into the left (@'True@) or right (@'False@) of a generic sum.
The other side of the sum is unchanged.
-}
class GInjectionSum (left :: Bool) (n :: Nat) f g f' g' a b where
gInjectionSum :: proxy left -> proxy' n -> Prism ((f :+: g) x) ((f' :+: g') x) a b
instance (GInjection n f f' a b, g ~ g') => GInjectionSum 'True n f g f' g' a b where
gInjectionSum _ n =
withPrism (gInjection n :: Prism (f x) (f' x) a b) $ \bt sta ->
prism (L1 . bt) $ \case
L1 x -> first L1 (sta x)
R1 y -> Left (R1 y)
{-# INLINE gInjectionSum #-}
instance (GInjection (n - GCount f) g g' a b, f ~ f') => GInjectionSum 'False n f g f' g' a b where
gInjectionSum _ _ =
withPrism (gInjection (Proxy :: Proxy (n - GCount f)) :: Prism (g x) (g' x) a b) $ \bt sta ->
prism (R1 . bt) $ \case
L1 x -> Left (L1 x)
R1 y -> first R1 (sta y)
{-# INLINE gInjectionSum #-}
-- | Convert between generic products with the same fields, ignoring metadata.
class GProduct p q where
gProductTo :: p x -> q x
gProductFrom :: q x -> p x
instance (GProduct f f', GProduct g g') => GProduct (f :*: g) (f' :*: g') where
gProductTo (f :*: g) =
gProductTo f :*: gProductTo g
{-# INLINE gProductTo #-}
gProductFrom (f :*: g) =
gProductFrom f :*: gProductFrom g
{-# INLINE gProductFrom #-}
instance (x ~ y) => GProduct (M1 S m (K1 i x)) (M1 S m' (K1 i' y)) where
gProductTo (M1 (K1 x)) =
M1 (K1 x)
{-# INLINE gProductTo #-}
gProductFrom (M1 (K1 y)) =
M1 (K1 y)
{-# INLINE gProductFrom #-}