packages feed

rel8-1.0.0.0: src/Rel8/Generic/Table/ADT.hs

{-# language AllowAmbiguousTypes #-}
{-# language DataKinds #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language LambdaCase #-}
{-# language MultiParamTypeClasses #-}
{-# language PolyKinds #-}
{-# language RankNTypes #-}
{-# language ScopedTypeVariables #-}
{-# language StandaloneKindSignatures #-}
{-# language TypeApplications #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language UndecidableInstances #-}

module Rel8.Generic.Table.ADT
  ( GTableADT, GColumnsADT, gfromColumnsADT, gtoColumnsADT, gtableADT
  , GTableADT', GColumnsADT'
  , GToExprsADT, gfromResultADT, gtoResultADT
  )
where

-- base
import Data.Kind ( Constraint, Type )
import Data.Proxy ( Proxy( Proxy ) )
import GHC.Generics
  ( (:+:)( L1, R1 ), M1( M1 ), U1( U1 )
  , C, D
  , Meta( MetaCons )
  )
import GHC.TypeLits ( KnownSymbol, symbolVal )
import Prelude hiding ( null )

-- rel8
import Rel8.FCF ( Eval, Exp )
import Rel8.Generic.Table.Record
  ( GTable, GColumns, gtable
  , GToExprs, gfromResult, gtoResult
  )
import Rel8.Schema.Context.Label ( HLabelable, hlabeler, labeler, unlabeler )
import Rel8.Schema.HTable ( HTable, hmap )
import Rel8.Schema.HTable.Identity ( HIdentity( HType ), HType )
import Rel8.Schema.HTable.Label ( HLabel, hlabel, hunlabel )
import Rel8.Schema.HTable.Nullify ( HNullify, hnulls, hnullify, hunnullify )
import Rel8.Schema.HTable.Product ( HProduct( HProduct ) )
import qualified Rel8.Schema.Kind as K
import Rel8.Schema.Null ( Nullify )
import Rel8.Schema.Spec ( Spec( Spec ), SSpec )
import Rel8.Schema.Result
  ( Col( R ), Result
  , null, nullifier, unnullifier
  )
import Rel8.Type.Tag ( Tag( Tag ) )

-- text
import Data.Text ( pack )


type GColumnsADT
  :: (Type -> Exp K.HTable)
  -> (Type -> Type) -> K.HTable
type family GColumnsADT _Columns rep where
  GColumnsADT _Columns (M1 D _ rep) =
    GColumnsADT' _Columns (HLabel "tag" (HType Tag)) rep


type GColumnsADT'
  :: (Type -> Exp K.HTable)
  -> K.HTable -> (Type -> Type) -> K.HTable
type family GColumnsADT' _Columns htable rep  where
  GColumnsADT' _Columns htable (a :+: b) =
    GColumnsADT' _Columns (GColumnsADT' _Columns htable a) b
  GColumnsADT' _Columns htable (M1 C ('MetaCons _ _ _) U1) = htable
  GColumnsADT' _Columns htable (M1 C ('MetaCons label _ _) rep) =
    HProduct htable (HLabel label (HNullify (GColumns _Columns rep)))


type GTableADT
  :: (Type -> Exp Constraint)
  -> (Type -> Exp K.HTable)
  -> K.HContext -> (Type -> Type) -> Constraint
class GTableADT _Table _Columns context rep where
  gfromColumnsADT :: ()
    => (forall spec. context spec -> Col Result spec)
    -> (forall spec. Col Result spec -> context spec)
    -> (forall a. Eval (_Table a) => Eval (_Columns a) context -> a)
    -> GColumnsADT _Columns rep context
    -> rep x

  gtoColumnsADT :: ()
    => (forall spec. context spec -> Col Result spec)
    -> (forall spec. Col Result spec -> context spec)
    -> (forall a. Eval (_Table a) => a -> Eval (_Columns a) context)
    -> rep x
    -> GColumnsADT _Columns rep context

  gtableADT :: ()
    => (forall a proxy. Eval (_Table a) => proxy a -> Eval (_Columns a) context)
    -> (forall a labels. ()
        => SSpec ('Spec labels a)
        -> context ('Spec labels a)
        -> context ('Spec labels (Nullify a)))
    -> GColumnsADT _Columns rep context


instance
  ( htable ~ HLabel "tag" (HType Tag)
  , HTable (GColumnsADT _Columns (M1 D meta rep))
  , Eval (_Table (htable context))
  , Eval (_Columns (htable context)) ~ htable
  , GTableADT' _Table _Columns htable context rep
  , GToExprsADT' (Const _Table) _Columns htable rep rep
  )
  => GTableADT _Table _Columns context (M1 D meta rep)
 where
  gfromColumnsADT fromContext toContext fromColumns =
    gfromResultADT @(Const _Table) @_Columns @(M1 D meta rep) @(M1 D meta rep)
      (const (fromColumns . hmap toContext)) .
    hmap fromContext

  gtoColumnsADT fromContext toContext toColumns =
    hmap toContext .
    gtoResultADT @(Const _Table) @_Columns @(M1 D meta rep) @(M1 D meta rep)
      (const (hmap fromContext . toColumns))

  gtableADT table hnullifier =
    gtableADT' @_Table @_Columns @htable @context @rep table hnullifier htable
    where
      htable = table (Proxy @(htable context))


type GTableADT'
  :: (Type -> Exp Constraint)
  -> (Type -> Exp K.HTable)
  -> K.HTable -> K.HContext -> (Type -> Type) -> Constraint
class GTableADT' _Table _Columns htable context rep where
  gtableADT' :: ()
    => (forall a proxy. Eval (_Table a) => proxy a -> Eval (_Columns a) context)
    -> (forall a labels. ()
        => SSpec ('Spec labels a)
        -> context ('Spec labels a)
        -> context ('Spec labels (Nullify a)))
    -> htable context
    -> GColumnsADT' _Columns htable rep context


instance
  ( htable' ~ GColumnsADT' _Columns htable a
  , GTableADT' _Table _Columns htable context a
  , GTableADT' _Table _Columns htable' context b
  )
  => GTableADT' _Table _Columns htable context (a :+: b)
 where
  gtableADT' table hnullifier =
    gtableADT' @_Table @_Columns @_ @_ @b table hnullifier .
    gtableADT' @_Table @_Columns @_ @_ @a table hnullifier


instance meta ~ 'MetaCons label _fixity _isRecord =>
  GTableADT' _Table _Columns htable context (M1 C meta U1)
 where
  gtableADT' _ _ = id


instance {-# OVERLAPPABLE #-}
  ( HTable (GColumns _Columns rep)
  , GTable _Table _Columns context rep
  , HLabelable context
  , meta ~ 'MetaCons label _fixity _isRecord
  , KnownSymbol label
  , GColumnsADT' _Columns htable (M1 C ('MetaCons label _fixity _isRecord) rep) ~
      HProduct htable (HLabel label (HNullify (GColumns _Columns rep)))
  )
  => GTableADT' _Table _Columns htable context (M1 C meta rep)
 where
  gtableADT' table hnullifier htable =
    HProduct htable $
      hlabel hlabeler $
      hnullify hnullifier $
      gtable @_Table @_Columns @_ @rep table


type GToExprsADT
  :: (Type -> Type -> Exp Constraint)
  -> (Type -> Exp K.HTable)
  -> (Type -> Type) -> (Type -> Type) -> Constraint
class GToExprsADT _ToExprs _Columns exprs rep where
  gfromResultADT :: ()
    => (forall expr a proxy.
        ( Eval (_ToExprs expr a)
        , HTable (Eval (_Columns expr))
        )
        => proxy expr
        -> Eval (_Columns expr) (Col Result)
        -> a)
    -> GColumnsADT _Columns exprs (Col Result)
    -> rep x

  gtoResultADT :: ()
    => (forall expr a proxy.
        ( Eval (_ToExprs expr a)
        , HTable (Eval (_Columns expr))
        )
        => proxy expr
        -> a
        -> Eval (_Columns expr) (Col Result))
    -> rep x
    -> GColumnsADT _Columns exprs (Col Result)


instance
  ( htable ~ HLabel "tag" (HType Tag)
  , GToExprsADT' _ToExprs _Columns htable exprs rep
  )
  => GToExprsADT _ToExprs _Columns (M1 D meta exprs) (M1 D meta rep)
 where
  gfromResultADT fromResult columns =
    case gfromResultADT' @_ToExprs @_Columns @_ @exprs fromResult tag columns of
      Just rep -> M1 rep
      _ -> error "ADT.fromColumns: mismatch between tag and data"
    where
      tag = (\(HType (R a)) -> a) . hunlabel @_ @"tag" unlabeler

  gtoResultADT toResult (M1 rep) =
    gtoResultADT' @_ToExprs @_Columns @_ @exprs toResult tag (Just rep)
    where
      tag = hlabel @_ @"tag" labeler . HType . R


type GToExprsADT'
  :: (Type -> Type -> Exp Constraint)
  -> (Type -> Exp K.HTable)
  -> K.HTable -> (Type -> Type) -> (Type -> Type) -> Constraint
class GToExprsADT' _ToExprs _Columns htable exprs rep where
  gfromResultADT' :: ()
    => (forall expr a proxy.
        ( Eval (_ToExprs expr a)
        , HTable (Eval (_Columns expr))
        )
        => proxy expr
        -> Eval (_Columns expr) (Col Result)
        -> a)
    -> (htable (Col Result) -> Tag)
    -> GColumnsADT' _Columns htable exprs (Col Result)
    -> Maybe (rep x)

  gtoResultADT' :: ()
    => (forall expr a proxy.
        ( Eval (_ToExprs expr a)
        , HTable (Eval (_Columns expr))
        )
        => proxy expr
        -> a
        -> Eval (_Columns expr) (Col Result))
    -> (Tag -> htable (Col Result))
    -> Maybe (rep x)
    -> GColumnsADT' _Columns htable exprs (Col Result)

  extract :: GColumnsADT' _Columns htable exprs context -> htable context


instance
  ( htable' ~ GColumnsADT' _Columns htable a
  , GToExprsADT' _ToExprs _Columns htable a rep1
  , GToExprsADT' _ToExprs _Columns htable' b rep2
  )
  => GToExprsADT' _ToExprs _Columns htable (a :+: b) (rep1 :+: rep2)
 where
  gfromResultADT' fromResult f columns =
    case ma of
      Just a -> Just (L1 a)
      Nothing -> R1 <$>
        gfromResultADT' @_ToExprs @_Columns @_ @b @rep2
          fromResult
          (f . extract @_ToExprs @_Columns @_ @a @rep1)
          columns
    where
      ma =
        gfromResultADT' @_ToExprs @_Columns @_ @a @rep1
          fromResult
          f
          (extract @_ToExprs @_Columns @_ @b @rep2 columns)

  gtoResultADT' toResult tag = \case
    Just (L1 a) ->
      gtoResultADT' @_ToExprs @_Columns @_ @b @rep2
        toResult
        (\_ -> gtoResultADT' @_ToExprs @_Columns @_ @a @rep1
          toResult
          tag
          (Just a))
        Nothing
    Just (R1 b) ->
      gtoResultADT' @_ToExprs @_Columns @_ @b @rep2
        toResult
        (\tag' ->
          gtoResultADT' @_ToExprs @_Columns @_ @a @rep1
            toResult
            (\_ -> tag tag')
            Nothing)
        (Just b)
    Nothing ->
      gtoResultADT' @_ToExprs @_Columns @_ @b @rep2
        toResult
        (\_ -> gtoResultADT' @_ToExprs @_Columns @_ @a @rep1 toResult tag Nothing)
        Nothing

  extract =
    extract @_ToExprs @_Columns @_ @a @rep1 .
    extract @_ToExprs @_Columns @_ @b @rep2


instance
  ( meta ~ 'MetaCons label _fixity _isRecord
  , KnownSymbol label
  )
  => GToExprsADT' _ToExprs _Columns htable (M1 C meta U1) (M1 C meta U1)
 where
  gfromResultADT' _ tag columns
    | tag columns == tag' = Just (M1 U1)
    | otherwise = Nothing
    where
      tag' = Tag $ pack $ symbolVal (Proxy @label)

  gtoResultADT' _ tag _ = tag tag'
    where
      tag' = Tag $ pack $ symbolVal (Proxy @label)

  extract = id


instance {-# OVERLAPPABLE #-}
  ( HTable (GColumns _Columns exprs)
  , GToExprs _ToExprs _Columns exprs rep
  , meta ~ 'MetaCons label _fixity _isRecord
  , KnownSymbol label
  , GColumnsADT' _Columns htable (M1 C meta exprs) ~
      HProduct htable (HLabel label (HNullify (GColumns _Columns exprs)))
  )
  => GToExprsADT' _ToExprs _Columns htable (M1 C meta exprs) (M1 C meta rep)
 where
  gfromResultADT' fromResult tag (HProduct a b)
    | tag a == tag' =
        M1 . gfromResult @_ToExprs @_Columns @exprs fromResult <$>
          hunnullify unnullifier (hunlabel unlabeler b)
    | otherwise = Nothing
    where
      tag' = Tag $ pack $ symbolVal (Proxy @label)

  gtoResultADT' toResult tag = \case
    Nothing -> HProduct (tag tag') (hlabel labeler (hnulls (const null)))
    Just (M1 rep) -> HProduct (tag tag') $
      hlabel labeler $
      hnullify nullifier $
      gtoResult @_ToExprs @_Columns @exprs toResult rep
    where
      tag' = Tag $ pack $ symbolVal (Proxy @label)

  extract (HProduct a _) = a


data Const :: (a -> Exp Constraint) -> a -> a -> Exp Constraint
type instance Eval (Const f x a) = (Eval (f a), x ~ a)