by-other-names-1.2.3.0: lib/ByOtherNamesH.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-missing-methods #-}
-- |
-- This module provides the general mechanism for defining field and branch
-- aliases for algebraic datatypes.
--
-- Aliases can be defined for multiple contexts (json serialization, orms...).
-- Each of those contexts is termed a Rubric, basically a marker datakind used
-- to namespace the aliases.
--
-- This module should only be imported if you want to define your own adapter
-- package for some new 'Rubric'. See "ByOtherNamesH.Aeson" for a concrete
-- example.
--
-- This module provides a more versatile, but also more verbose, version of the
-- functionality provided by "ByOtherNames". If you plan to use both
-- "ByOtherNames" and "ByOtherNamesH", import this module qualified to avoid
-- name collisions:
--
-- > import qualified ByOthernamesH as H
--
module ByOtherNamesH (
-- * Aliases
Aliases,
AliasList,
aliasListBegin,
alias,
aliasListEnd,
SlotList,
singleSlot,
slot,
slotListEnd,
-- * Rubrics
Rubric (..),
Aliased (..),
-- * Generic helpers
GRecord(..),
-- * Re-exports
Symbol,
) where
import Control.Applicative
import Data.Kind
import Data.Proxy
import GHC.Generics
import GHC.TypeLits
import Data.Functor.Identity
-- | This datatype carries the field/branch aliases, along with a value wrapped in @h@
-- for each field in the original datatype.
--
-- It matches the shape of the generic 'Rep'.
type Aliases :: (Type -> Type) -> Type -> (Type -> Type) -> Type
data Aliases rep a (h :: Type -> Type) where
Field ::
KnownSymbol fieldName =>
a ->
h v ->
Aliases (S1 ('MetaSel ('Just fieldName) unpackedness strictness laziness) (Rec0 v)) a h
Branch ::
KnownSymbol branchName =>
a ->
BranchFields v h ->
Aliases (C1 ('MetaCons branchName fixity sels) v) a h
EmptyBranch ::
KnownSymbol branchName =>
a ->
Aliases (C1 ('MetaCons branchName fixity sels) U1) a h
FieldTree ::
Aliases left a h ->
Aliases right a h ->
Aliases (left :*: right) a h
BranchTree ::
Aliases left a h ->
Aliases right a h ->
Aliases (left :+: right) a h
-- | We force the sum to contain at least two branches.
Sum ::
Aliases (left :+: right) a h ->
Aliases (D1 x (left :+: right)) a h
Record ::
Aliases fields a h ->
Aliases (D1 x (C1 y fields)) a h
type BranchFields :: (Type -> Type) -> (Type -> Type) -> Type
data BranchFields rep h where
BranchFieldTree ::
BranchFields left h ->
BranchFields right h ->
BranchFields (left :*: right) h
BranchField ::
h v ->
BranchFields (S1 ('MetaSel 'Nothing unpackedness strictness laziness) (Rec0 v)) h
-- | A list of slots associated an alias. Indexed by the types of each slot
-- and a type constructor that wraps each slot value.
--
-- For records, each field alias will have one and only one slot: the
-- corresponding record field. See 'singleSlot'.
--
-- For sum types, each branch alias might have zero or more slots, depending on
-- the structure of the datatype. See 'slot' and 'slotListEnd'.
data SlotList :: [Type] -> (Type -> Type) -> Type where
EmptyTuple :: SlotList '[] h
ConsTuple :: h x -> SlotList xs h -> SlotList (x ': xs) h
-- | An intermediate helper datatype for specifying the aliases.
--
-- Indexed by a list of names accompanied by field types.
--
-- See 'aliasListBegin', 'alias' and 'aliasListEnd'.
type AliasList :: [(Symbol, [Type])] -> Type -> (Type -> Type) -> Type
data AliasList (names_slots :: [(Symbol, [Type])]) a (h :: Type -> Type) where
EmptyAliasList :: AliasList '[] a h
ConsAliasList ::
Proxy name ->
a ->
SlotList slots h ->
AliasList prev a h -> AliasList ('(name,slots) : prev) a h
type ToAliases :: [(Symbol, [Type])] -> (Type -> Type) -> [(Symbol, [Type])] -> Constraint
-- | The second functional dependency is needed for type inference to work.
class ToAliases before rep after | before rep -> after, after rep -> before where
parseAliasTree :: AliasList before a h -> (Aliases rep a h, AliasList after a h)
type ToBranchFields :: [Type] -> (Type -> Type) -> [Type] -> Constraint
-- | The second functional dependency is needed for type inference to work.
class ToBranchFields before rep after | before rep -> after, after rep -> before where
parseBranchFields :: SlotList before h -> (BranchFields rep h, SlotList after h)
instance (ToBranchFields before left middle,
ToBranchFields middle right end)
=> ToBranchFields before (left :*: right) end where
parseBranchFields t0 = do
let (leftResult, leftLeftover) = parseBranchFields @before t0
(rightResult, rightLeftover) = parseBranchFields @middle leftLeftover
(BranchFieldTree leftResult rightResult, rightLeftover)
instance ToBranchFields (v ': vs) (S1 ('MetaSel 'Nothing unpackedness strictness laziness) (Rec0 v)) vs where
parseBranchFields (ConsTuple hv rest) = (BranchField hv, rest)
instance ToAliases before tree '[] => ToAliases before (D1 x (C1 y tree)) '[] where
parseAliasTree as =
let (aliases', as') = parseAliasTree as
in (Record aliases', as')
instance (ToAliases before left middle, ToAliases middle right end)
=> ToAliases before (left :*: right) end where
parseAliasTree as =
let (left, middle) = parseAliasTree @before as
(right, end) = parseAliasTree @middle middle
in (FieldTree left right, end)
instance KnownSymbol name
=> ToAliases ('(name, '[v]) : rest) (S1 ('MetaSel (Just name) x y z) (Rec0 v)) rest where
parseAliasTree (ConsAliasList _ a (ConsTuple hv EmptyTuple) rest) = (Field a hv, rest)
instance ToAliases before (left :+: right) '[] => ToAliases before (D1 x (left :+: right)) '[] where
parseAliasTree as =
let (aliases', as') = parseAliasTree as
in (Sum aliases', as')
instance (ToAliases before left middle, ToAliases middle right end) => ToAliases before (left :+: right) end where
parseAliasTree as =
let (left, middle) = parseAliasTree @before as
(right, end) = parseAliasTree @middle middle
in (BranchTree left right, end)
instance (KnownSymbol name,
ToBranchFields vs (S1 u v) '[]) =>
ToAliases ('(name, vs) : rest) (C1 ('MetaCons name fixity False) (S1 u v)) rest where
parseAliasTree (ConsAliasList _ a branchFields rest) = do
let (theBranchFields, EmptyTuple) = parseBranchFields @vs branchFields
(Branch a theBranchFields, rest)
instance (KnownSymbol name,
ToBranchFields vs (left :*: right) '[]) =>
ToAliases ('(name, vs) : rest) (C1 ('MetaCons name fixity False) (left :*: right)) rest where
parseAliasTree (ConsAliasList _ a branchFields rest) = do
let (theBranchFields, EmptyTuple) = parseBranchFields @vs branchFields
(Branch a theBranchFields, rest)
instance KnownSymbol name =>
ToAliases ('(name, '[]) : rest) (C1 ('MetaCons name fixity False) U1) rest where
parseAliasTree (ConsAliasList _ a EmptyTuple rest) = do
(EmptyBranch a, rest)
--
--
type Aliased :: k -> Type -> Constraint
class (Rubric k, Generic r) => Aliased k r where
aliases :: Aliases (Rep r) (AliasType k) (WrapperType k)
type Rubric :: k -> Constraint
class Rubric k where
type AliasType k :: Type
type WrapperType k :: Type -> Type
aliasListBegin :: forall names_slots a h rep. (ToAliases names_slots rep '[])
=> AliasList names_slots a h -- ^ indexed by a list of alias names / slots types
-> Aliases rep a h -- ^ indexed by a generic 'Rep'
aliasListBegin names =
let (aliases, EmptyAliasList) = parseAliasTree @names_slots @rep names
in aliases
-- | The empty 'AliasList'.
aliasListEnd :: AliasList '[] a h
aliasListEnd = EmptyAliasList
alias :: forall name slots a h names_slots.
-- | The alias value
a ->
-- | \"wrapped\" values for each slot of the alias
SlotList slots h ->
AliasList names_slots a h ->
AliasList ('(name, slots) : names_slots) a h
alias = ConsAliasList (Proxy @name)
-- | The empty 'SlotList'.
slotListEnd :: SlotList '[] h
slotListEnd = EmptyTuple
singleSlot :: h v -> SlotList '[v] h
singleSlot hv = ConsTuple hv EmptyTuple
slot :: h v -> SlotList rest h -> SlotList (v ': rest) h
slot hv = ConsTuple hv
class GRecord rep where
-- | Builds a parser for the entire generic 'Rep' out of parsers for each field.
gToRecord ::
Applicative g =>
-- | Field aliases.
Aliases rep a h ->
(forall v. a -> h v -> g v) ->
g (rep z)
gFromRecord ::
-- | Record representation.
rep z ->
Aliases rep String Identity
gBiliftA2RecordAliases ::
-- | Combine aliases
(a1 -> a2 -> ar) ->
-- | Combine slots
(forall v. h1 v -> h2 v -> hr v) ->
Aliases rep a1 h1 ->
Aliases rep a2 h2 ->
Aliases rep ar hr
instance GRecord prod => GRecord (D1 x (C1 y prod)) where
gToRecord (Record as) parseField =
M1 . M1 <$> gToRecord as parseField
gFromRecord (M1 (M1 prod)) =
Record (gFromRecord prod)
gBiliftA2RecordAliases f g (Record a1) (Record a2) =
Record (gBiliftA2RecordAliases f g a1 a2)
instance
(GRecord left, GRecord right) =>
GRecord (left :*: right)
where
gToRecord (FieldTree aleft aright) parseField =
(:*:) <$> gToRecord aleft parseField <*> gToRecord aright parseField
gFromRecord (left :*: right) =
FieldTree (gFromRecord left) (gFromRecord right)
gBiliftA2RecordAliases f g (FieldTree left1 right1) (FieldTree left2 right2) =
FieldTree (gBiliftA2RecordAliases f g left1 left2) (gBiliftA2RecordAliases f g right1 right2)
instance KnownSymbol fieldName => GRecord (S1 ('MetaSel ('Just fieldName) unpackedness strictness laziness) (Rec0 v)) where
gToRecord (Field a hv) parseField =
M1 . K1 <$> parseField a hv
gFromRecord (M1 (K1 v)) = Field (symbolVal (Proxy @fieldName)) (Identity v)
gBiliftA2RecordAliases f g (Field a1 h1) (Field a2 h2) =
Field (f a1 a2) (g h1 h2)