packages feed

generic-lens-0.5.0.0: src/Data/Generics/Internal/Families/Changing.hs

{-# LANGUAGE AllowAmbiguousTypes    #-}
{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE PolyKinds              #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE TypeOperators          #-}
{-# LANGUAGE UndecidableInstances   #-}

module Data.Generics.Internal.Families.Changing where

import GHC.TypeLits (Nat, type (+))

{-
  Note [Changing type parameters]
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  To get good type inference for type-changing lenses, we want to be able
  map the field's type back to the type argument it corresponds to. This way,
  when the field is changed, we know what the result type of the structure is
  going to be.

  However, for a given type @t@, its representation @Rep t@ forgets which types
  in the structure came from type variables, and which didn't. An @Int@ that
  results from the instantiation of the type paremeter and an @Int@ that was
  monomorphically specified in the structure are indistinguishable.

  The solution is to replace the type arguments in the type with unique
  proxies, like: @T a b@ -> @T (P 1 a) (P 0 b)@. This way, if looking up
  a field's type yields something of shape @P _ _@, we know it came from a type
  parameter, and also know which.

  If the field's type is a proxy, then its type is allowed to change, otherwise
  not. This also allows us to satisfy the functional dependency @s field b -> t@.
  If after doing the conversion on @s@, @field@'s type is @(P _ a), then @t@ is
  @s[b/a]@, otherwise @t ~ s@ and @b ~ a@.
-}
data P (i :: Nat) a

type Proxied t = Proxied' t 0

type family Proxied' (t :: k) (next :: Nat) :: k where
  Proxied' (t a :: k) next = (Proxied' t (next + 1)) (P next a)
  Proxied' t _ = t

type family UnProxied (t :: k) :: k where
  UnProxied (P _ a) = a
  UnProxied (t (P _ a) :: k) = UnProxied t a
  UnProxied t = t

type family Change (t :: k) (target :: Nat) (to :: j) :: k where
  Change (P target _) target to = (P target to)
  Change (t (P target _) :: k) target to = t (P target to)
  Change (t a :: k) target to = Change t target to a
  Change t _ _ = t

type family UnApply (a :: k) :: [*] where
  UnApply (f x) = x ': UnApply f
  UnApply x     = '[]

type family Unify a b :: [(*, *)] where
  Unify (P n a') a = '[ '(P n a', a)]
  Unify a b = Zip (UnApply a) (UnApply b)

type family PSub (subs :: [(*, *)]) :: [(Nat, *)] where
  PSub '[] = '[]
  PSub ('(P n _, b) ': xs) = '(n, b) ': PSub xs
  PSub (_ ': xs) = PSub xs

type family Zip (xs :: [k]) (ys :: [l]) :: [(k, l)] where
  Zip '[] '[] = '[]
  Zip (x ': xs) (y ': ys) = '(x, y) ': Zip xs ys

type family Infer (s :: *) (a' :: *) (a :: *) (w :: *) :: (*, *) where
  Infer s' a' a w
    = Infer' s' a' (PSub (Unify a' a)) w

type family Infer' (s :: *) (a' :: *) (subs :: [(Nat, k)]) w :: (*, *) where
  Infer' s' a' '[ '(p, _)] w
    = '(UnProxied (Change s' p w), UnProxied (Change a' p w))
  Infer' s' a' _ _
    = '(UnProxied s', UnProxied a')

type family PickTv (a :: k) (b :: k) :: * where
  PickTv (P _ _) b = b
  PickTv (f (P _ _)) (g b) = b
  PickTv (f a) (g b) = PickTv f g