{-# language AllowAmbiguousTypes #-} {-# language DataKinds #-} {-# language FlexibleInstances #-} {-# language FunctionalDependencies #-} {-# language GADTs #-} {-# language LambdaCase #-} {-# language PolyKinds #-} {-# language ScopedTypeVariables #-} {-# language TypeApplications #-} {-# language TypeOperators #-} {-# language UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-| Description : Optics-based interface for @mu-schema@ terms This module provides instances of 'LabelOptic' to be used in conjunction with the @optics@ package. In particular, there are two kind of optics to access different parts of a 'Term': * With @#field@ you obtain the lens (that is, a getter and a setter) for the corresponding field in a record. * With @#choice@ you obtain the prism for the desired choice in an enumeration. You can use then 'review' to construct a term with the value. In addition, we provide a utility function 'record' to build a record out of the inner values. We intend the interface to be very simple, so this function is overloaded to take tuples of different size, with as many components as values in the schema type. -} module Mu.Schema.Optics ( -- * Build a term record, record1, enum , _U0, _Next, _U1, _U2, _U3 -- * Re-exported for convenience. , module Optics.Core ) where import Data.Functor.Identity import Data.Kind import Data.Map import Data.Proxy import GHC.TypeLits import Optics.Core import Mu.Schema instance {-# OVERLAPS #-} (FieldLabel Identity sch args fieldName r) => LabelOptic fieldName A_Lens (Term Identity sch ('DRecord name args)) (Term Identity sch ('DRecord name args)) r r where labelOptic = lens (\(TRecord r) -> runIdentity $ fieldLensGet (Proxy @fieldName) r) (\(TRecord r) x -> TRecord $ fieldLensSet (Proxy @fieldName) r (Identity x)) instance {-# OVERLAPPABLE #-} (FieldLabel w sch args fieldName r, t ~ w r) => LabelOptic fieldName A_Lens (Term w sch ('DRecord name args)) (Term w sch ('DRecord name args)) t t where labelOptic = lens (\(TRecord r) -> fieldLensGet (Proxy @fieldName) r) (\(TRecord r) x -> TRecord $ fieldLensSet (Proxy @fieldName) r x) record :: BuildRecord w sch args r => r -> Term w sch ('DRecord name args) record values = TRecord $ buildR values record1 :: BuildRecord1 w sch arg r => r -> Term w sch ('DRecord name '[arg]) record1 value = TRecord $ buildR1 value class BuildRecord1 (w :: Type -> Type) (sch :: Schema Symbol Symbol) (arg :: FieldDef Symbol Symbol) (r :: Type) | w sch arg -> r where buildR1 :: r -> NP (Field w sch) '[arg] instance {-# OVERLAPPABLE #-} (Functor w, TypeLabel w sch t1 r1) => BuildRecord1 w sch ('FieldDef x1 t1) (w r1) where buildR1 v = Field (typeLensSet <$> v) :* Nil instance {-# OVERLAPS #-} (TypeLabel Identity sch t1 r1) => BuildRecord1 Identity sch ('FieldDef x1 t1) r1 where buildR1 v = Field (typeLensSet <$> Identity v) :* Nil class BuildRecord (w :: Type -> Type) (sch :: Schema Symbol Symbol) (args :: [FieldDef Symbol Symbol]) (r :: Type) | w sch args -> r where buildR :: r -> NP (Field w sch) args instance BuildRecord w sch '[] () where buildR _ = Nil instance {-# OVERLAPPABLE #-} (Functor w, TypeLabel w sch t1 r1, TypeLabel w sch t2 r2) => BuildRecord w sch '[ 'FieldDef x1 t1, 'FieldDef x2 t2 ] (w r1, w r2) where buildR (v1, v2) = Field (typeLensSet <$> v1) :* Field (typeLensSet <$> v2) :* Nil instance {-# OVERLAPS #-} (TypeLabel Identity sch t1 r1, TypeLabel Identity sch t2 r2) => BuildRecord Identity sch '[ 'FieldDef x1 t1, 'FieldDef x2 t2 ] (r1, r2) where buildR (v1, v2) = Field (typeLensSet <$> Identity v1) :* Field (typeLensSet <$> Identity v2) :* Nil instance {-# OVERLAPPABLE #-} (Functor w, TypeLabel w sch t1 r1, TypeLabel w sch t2 r2, TypeLabel w sch t3 r3) => BuildRecord w sch '[ 'FieldDef x1 t1, 'FieldDef x2 t2, 'FieldDef x3 t3 ] (w r1, w r2, w r3) where buildR (v1, v2, v3) = Field (typeLensSet <$> v1) :* Field (typeLensSet <$> v2) :* Field (typeLensSet <$> v3) :* Nil instance {-# OVERLAPS #-} (TypeLabel Identity sch t1 r1, TypeLabel Identity sch t2 r2, TypeLabel Identity sch t3 r3) => BuildRecord Identity sch '[ 'FieldDef x1 t1, 'FieldDef x2 t2, 'FieldDef x3 t3 ] (r1, r2, r3) where buildR (v1, v2, v3) = Field (typeLensSet <$> Identity v1) :* Field (typeLensSet <$> Identity v2) :* Field (typeLensSet <$> Identity v3) :* Nil class FieldLabel (w :: Type -> Type) (sch :: Schema Symbol Symbol) (args :: [FieldDef Symbol Symbol]) (fieldName :: Symbol) (r :: Type) | w sch args fieldName -> r where fieldLensGet :: Proxy fieldName -> NP (Field w sch) args -> w r fieldLensSet :: Proxy fieldName -> NP (Field w sch) args -> w r -> NP (Field w sch) args {- Removed due to FunDeps instance TypeError ('Text "cannot find field " ':<>: 'ShowType f) => FieldLabel w sch '[] f t where fieldLensGet = error "this should never be run" fieldLensSet = error "this should never be run" -} instance {-# OVERLAPS #-} (Functor w, TypeLabel w sch t r) => FieldLabel w sch ('FieldDef f t ': rest) f r where fieldLensGet _ (Field x :* _) = typeLensGet <$> x fieldLensSet _ (_ :* r) new = Field (typeLensSet <$> new) :* r instance {-# OVERLAPPABLE #-} FieldLabel w sch rest g t => FieldLabel w sch (f ': rest) g t where fieldLensGet p (_ :* r) = fieldLensGet p r fieldLensSet p (x :* r) new = x :* fieldLensSet p r new class TypeLabel w (sch :: Schema Symbol Symbol) (t :: FieldType Symbol) (r :: Type) | w sch t -> r where typeLensGet :: FieldValue w sch t -> r typeLensSet :: r -> FieldValue w sch t instance TypeLabel w sch ('TPrimitive t) t where typeLensGet (FPrimitive x) = x typeLensSet = FPrimitive instance (r ~ (sch :/: t)) => TypeLabel w sch ('TSchematic t) (Term w sch r) where typeLensGet (FSchematic x) = x typeLensSet = FSchematic instance (TypeLabel w sch o r', r ~ Maybe r') => TypeLabel w sch ('TOption o) r where typeLensGet (FOption x) = typeLensGet <$> x typeLensSet new = FOption (typeLensSet <$> new) instance (TypeLabel w sch o r', r ~ [r']) => TypeLabel w sch ('TList o) r where typeLensGet (FList x) = typeLensGet <$> x typeLensSet new = FList (typeLensSet <$> new) instance ( TypeLabel w sch k k', TypeLabel w sch v v' , r ~ Map k' v', Ord k', Ord (FieldValue w sch k) ) => TypeLabel w sch ('TMap k v) r where typeLensGet (FMap x) = mapKeys typeLensGet (typeLensGet <$> x) typeLensSet new = FMap (mapKeys typeLensSet (typeLensSet <$> new)) instance (r ~ NS (FieldValue w sch) choices) => TypeLabel w sch ('TUnion choices) r where typeLensGet (FUnion x) = x typeLensSet = FUnion enum :: forall (choiceName :: Symbol) choices w sch name. EnumLabel choices choiceName => Term w sch ('DEnum name choices) enum = TEnum $ enumPrismBuild (Proxy @choiceName) instance (EnumLabel choices choiceName, r ~ ()) => LabelOptic choiceName A_Prism (Term w sch ('DEnum name choices)) (Term w sch ('DEnum name choices)) r r where labelOptic = prism' (\_ -> TEnum $ enumPrismBuild (Proxy @choiceName)) (\(TEnum r) -> enumPrismMatch (Proxy @choiceName) r) class EnumLabel (choices :: [ChoiceDef Symbol]) (choiceName :: Symbol) where enumPrismBuild :: Proxy choiceName -> NS Proxy choices enumPrismMatch :: Proxy choiceName -> NS Proxy choices -> Maybe () instance TypeError ('Text "cannot find choice " ':<>: 'ShowType c) => EnumLabel '[] c where enumPrismBuild = error "this should never be run" enumPrismMatch = error "this should never be run" instance {-# OVERLAPS #-} EnumLabel ('ChoiceDef c ': rest) c where enumPrismBuild _ = Z Proxy enumPrismMatch _ (Z _) = Just () enumPrismMatch _ _ = Nothing instance {-# OVERLAPPABLE #-} EnumLabel rest c => EnumLabel (d ': rest) c where enumPrismBuild p = S (enumPrismBuild p) enumPrismMatch _ (Z _) = Nothing enumPrismMatch p (S x) = enumPrismMatch p x _U0 :: forall w (sch :: Schema') x xs r. TypeLabel w sch x r => Prism' (NS (FieldValue w sch) (x ': xs)) r _U0 = prism' (Z . typeLensSet) (\case (Z x) -> Just $ typeLensGet x (S _) -> Nothing) _Next :: forall w (sch :: Schema') x xs. Prism' (NS (FieldValue w sch) (x ': xs)) (NS (FieldValue w sch) xs) _Next = prism' S (\case (Z _) -> Nothing (S x) -> Just x) _U1 :: forall w (sch :: Schema') a b xs r. TypeLabel w sch b r => Prism' (NS (FieldValue w sch) (a ': b ': xs)) r _U1 = _Next % _U0 _U2 :: forall w (sch :: Schema') a b c xs r. TypeLabel w sch c r => Prism' (NS (FieldValue w sch) (a ': b ': c ': xs)) r _U2 = _Next % _U1 _U3 :: forall w (sch :: Schema') a b c d xs r. TypeLabel w sch d r => Prism' (NS (FieldValue w sch) (a ': b ': c ': d ': xs)) r _U3 = _Next % _U2