packages feed

large-anon-0.1.0.0: test/Test/Infra/DynRecord/Simple.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving  #-}

-- | 'DynRecord' interop with the simple record API.
--
-- Intended for qualified import.
--
-- > import qualified Test.Infra.DynRecord.Simple as Dyn
module Test.Infra.DynRecord.Simple (
    -- * Type inference
    ValidField(..)
  , SomeRecord(..)
  , inferType
    -- * Lens
  , toLens
  , toRecord
  ) where

import Data.Bifunctor
import Data.Kind
import Data.Typeable

import Data.Record.Anon
import Data.Record.Anon.Simple (Record)

import qualified Data.Record.Anon.Advanced as A
import qualified Data.Record.Anon.Simple   as S

import Test.Infra.Discovery
import Test.Infra.DynRecord

{-------------------------------------------------------------------------------
  Type inference

  NOTE: This canont be defined in terms of A.Dyn.inferType, because if we did,
  we would get in scope @AllFields r (Compose Show I)@, from which we cannot
  deduce @AllFields r Show@: @Show (I x)@ does not imply @Show x@. We could
  potentially solve this using a Beam-style HKD definition, but it's not that
  relevant for this test case.
-------------------------------------------------------------------------------}

data ValidField x where
  ValidField ::
       ( Typeable  x
       , Show      x
       , Eq        x
       , ToValue I x
       )
    => x -> ValidField x

data SomeRecord where
  SomeRecord :: forall (r :: Row Type).
       ( KnownFields r
       , SubRow r r
       , AllFields r Typeable
       , AllFields r Show
       , AllFields r Eq
       , AllFields r (ToValue I)
       )
    => Record r -> SomeRecord

inferType :: DynRecord -> SomeRecord
inferType (DynRecord r) =
    case A.someRecord $ map (second mkField) r of
      A.SomeRecord record ->
        case A.reflectSubRow (A.map pairFst record) of
          Reflected -> withSomeRecord (A.map pairSnd record)
  where
    withSomeRecord ::
         ( KnownFields r
         , SubRow r r
         )
      => A.Record ValidField r -> SomeRecord
    withSomeRecord record =
        case ( A.reflectAllFields (A.map dictTypeable  record)
             , A.reflectAllFields (A.map dictShow      record)
             , A.reflectAllFields (A.map dictEq        record)
             , A.reflectAllFields (A.map dictToValue   record)
             ) of
          (Reflected, Reflected, Reflected, Reflected) ->
            SomeRecord (S.fromAdvanced $ A.map fieldValue record)

    fieldValue :: ValidField x -> I x
    fieldValue (ValidField value) = I value

    dictTypeable  :: ValidField x -> Dict Typeable    x
    dictShow      :: ValidField x -> Dict Show        x
    dictEq        :: ValidField x -> Dict Eq          x
    dictToValue   :: ValidField x -> Dict (ToValue I) x

    dictTypeable (ValidField _) = Dict
    dictShow     (ValidField _) = Dict
    dictEq       (ValidField _) = Dict
    dictToValue  (ValidField _) = Dict

    mkField :: Value -> Some ValidField
    mkField (VI x) = Some $ ValidField x
    mkField (VB x) = Some $ ValidField x
    mkField (VC x) = Some $ ValidField x

{-------------------------------------------------------------------------------
  Projection to known row
-------------------------------------------------------------------------------}

-- | Lens to record over some known row @r@
toLens :: forall (r :: Row Type) proxy.
     ( KnownFields r
     , SubRow r r
     , AllFields r Typeable
     )
  => proxy r
  -> DynRecord
  -> Either NotSubRow (Record r, Record r -> DynRecord)
toLens p = \r ->
    -- In order to be able to check if we can project to the known row @r@,
    -- we must first to type inference on the @DynRecord@. /If/ this succeeds,
    -- we know the types line up, and there can be no further type errors
    -- (there is no need for a separate parsing step).
    case inferType r of
      SomeRecord r' ->
        fmap (withSomeRecord r') $ checkIsSubRow r' p
  where
    -- @r'@ is the row inferred for the 'DynRecord'
    withSomeRecord :: forall (r' :: Row Type).
         ( KnownFields r'
         , AllFields r' (ToValue I)
         )
      => Record r'
      -> Reflected (SubRow r' r)
      -> (Record r, Record r -> DynRecord)
    withSomeRecord r Reflected = (
          getter
        , DynRecord . A.toList . toValues . S.toAdvanced . setter
        )
      where
        getter :: Record r
        setter :: Record r -> Record r'
        (getter, setter) = S.lens r

toRecord :: forall (r :: Row Type) proxy.
     ( KnownFields r
     , SubRow r r
     , AllFields r Typeable
     )
  => proxy r
  -> DynRecord
  -> Either NotSubRow (Record r)
toRecord p = fmap fst . toLens p