packages feed

large-anon-0.1.1: test/Test/Infra/DynRecord/Advanced.hs

{-# LANGUAGE ConstraintKinds     #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE PolyKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving  #-}
{-# LANGUAGE TypeApplications    #-}

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

import Data.Bifunctor
import Data.Kind
import Data.Record.Generic
import Data.SOP.Constraint
import Data.Typeable

import Data.Record.Anon
import Data.Record.Anon.Advanced (Record)
import qualified Data.Record.Anon.Advanced as Anon

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

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

data ValidField (f :: k -> Type) (x :: k) where
  ValidField ::
       ( Typeable    x
       , Show     (f x)
       , Eq       (f x)
       , ToValue   f x
       )
    => f x -> ValidField f x

class IsValue f where
  isValue :: Value -> Some (ValidField f)

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

inferType :: forall k (f :: k -> Type). IsValue f => DynRecord -> SomeRecord f
inferType (DynRecord r) =
     case Anon.someRecord (map (second isValue) r) of
       Anon.SomeRecord record ->
         case Anon.reflectSubRow (Anon.map pairFst record) of
           Reflected -> withSomeRecord (Anon.map pairSnd record)
  where
    withSomeRecord ::
         ( KnownFields r
         , SubRow r r
         )
      => Record (ValidField f) r -> SomeRecord f
    withSomeRecord record =
        case ( Anon.reflectAllFields (Anon.map dictTypeable  record)
             , Anon.reflectAllFields (Anon.map dictShow      record)
             , Anon.reflectAllFields (Anon.map dictEq        record)
             , Anon.reflectAllFields (Anon.map dictToValue   record)
             ) of
          (Reflected, Reflected, Reflected, Reflected) ->
            SomeRecord (Anon.map fieldValue record)

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

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

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

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

-- | Lens to record over some known row @r@
toLens :: forall k (f :: k -> Type) (r :: Row k) proxy.
     ( IsValue f
     , KnownFields r
     , SubRow r r
     , AllFields r Typeable
     )
  => proxy r
  -> DynRecord
  -> Either NotSubRow (Record f r, Record f 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 k).
         ( KnownFields r'
         , AllFields r' (ToValue f)
         )
      => Record f r'
      -> Reflected (SubRow r' r)
      -> (Record f r, Record f r -> DynRecord)
    withSomeRecord r Reflected = (
          getter
        , DynRecord . Anon.toList . toValues . setter
        )
      where
        getter :: Record f r
        setter :: Record f r -> Record f r'
        (getter, setter) = Anon.lens r

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