easytensor-2.0.0.0: src/Numeric/DataFrame/Internal/Backend.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
#if defined(__HADDOCK__) || defined(__HADDOCK_VERSION__)
{-# OPTIONS_GHC -fno-warn-simplifiable-class-constraints #-}
#else
{-# OPTIONS_GHC -fplugin Data.Constraint.Deriving #-}
#endif
module Numeric.DataFrame.Internal.Backend
( DFBackend, Backend (..), BackendFamily, KnownBackend ()
, inferKnownBackend, inferPrimElem
-- * Auto-deriving instances
, inferEq, inferOrd
, inferProductOrder, inferPONonTransitive, inferPOPartial
, inferBounded, inferNum
, inferFractional, inferFloating
, inferPrimBytes, inferPrimArray
) where
import Data.Constraint
import Data.Constraint.Deriving
import Data.Constraint.Unsafe
import Data.Kind (Type)
import Unsafe.Coerce (unsafeCoerce)
import Numeric.DataFrame.Internal.PrimArray
import Numeric.Dimensions
import Numeric.PrimBytes
import Numeric.ProductOrd
import qualified Numeric.ProductOrd.NonTransitive as NonTransitive
import qualified Numeric.ProductOrd.Partial as Partial
import Numeric.DataFrame.Internal.Backend.Family (BackendFamily)
import qualified Numeric.DataFrame.Internal.Backend.Family as Impl (KnownBackend,
inferBackendInstance,
inferKnownBackend,
inferPrimArray,
inferPrimElem)
import Numeric.DataFrame.Internal.BackendI (I)
-- | Implementation behind the DataFrame
type DFBackend (t :: Type) (ds :: [Nat]) = Backend I t ds (BackendFamily t ds)
-- | Backend resolver:
-- Use this constraint to find any class instances defined for all DataFrame implementations,
-- e.g. @Num@, @PrimBytes@, etc.
type KnownBackend (t :: Type) (ds :: [Nat]) = Impl.KnownBackend t ds (BackendFamily t ds)
-- | A newtype wrapper for all DataFrame implementations.
-- I need two layers of wrappers to provide default overlappable instances to
-- all type classes using KnownBackend mechanics.
-- Type arguments are redundant here;
-- nevertheless, they improve readability of error messages.
newtype Backend (i :: Type) (t :: Type) (ds :: [Nat]) (backend :: Type)
= Backend { _getBackend :: backend }
type role Backend phantom phantom phantom representational
type instance DeriveContext (Backend I t ds b) = b ~ BackendFamily t ds
-- When typechecker knows what the third parameter @b@ is, no instances overlap,
-- because @b@ is always always a concrete type.
-- The "dynamic instances" derived via ToInstance have to be incoherent,
-- because @BackendFamily t ds@ as a type family (before resolving it)
-- is no more conrete than just @b@.
{-# ANN type Backend (DeriveAll' NoOverlap ["KnownBackend"]) #-}
inferKnownBackend
:: forall (t :: Type) (ds :: [Nat])
. (PrimBytes t, Dimensions ds)
=> Dict (KnownBackend t ds)
inferKnownBackend
= Impl.inferKnownBackend @t @ds @(BackendFamily t ds)
inferPrimElem
:: forall (t :: Type) (d :: Nat) (ds :: [Nat])
. KnownBackend t (d ': ds)
=> DFBackend t (d ': ds) -> Dict (PrimBytes t)
inferPrimElem = Impl.inferPrimElem @t @d @ds . _getBackend
inferEq
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Eq t, Impl.KnownBackend t ds b)
=> Dict (Eq (Backend I t ds b))
inferEq
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferOrd
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Ord t, Impl.KnownBackend t ds b)
=> Dict (Ord (Backend I t ds b))
inferOrd
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferProductOrder
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Ord t, Impl.KnownBackend t ds b)
=> Dict (ProductOrder (Backend I t ds b))
inferProductOrder
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferPONonTransitive
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Ord t, Impl.KnownBackend t ds b)
=> Dict (Ord (NonTransitive.ProductOrd (Backend I t ds b)))
inferPONonTransitive
= mapDict (unsafeDerive NonTransitive.ProductOrd)
. mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferPOPartial
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Ord t, Impl.KnownBackend t ds b)
=> Dict (Ord (Partial.ProductOrd (Backend I t ds b)))
inferPOPartial
= mapDict (unsafeDerive Partial.ProductOrd)
. mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferBounded
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Bounded t, Impl.KnownBackend t ds b)
=> Dict (Bounded (Backend I t ds b))
inferBounded
-- Here is an ugly trick:
-- We don't have instance of @Bounded Float@ and @Bounded Double@.
-- However, we want to have an instance @Bounded t => Bounded (Backend t ds b)@.
-- Some specialized implementations (e.g. FloatX4) require explicit
-- constraints @Bounded Float@ or @Bounded Double@.
-- To satisfy these, I pull @Bounded t@ convincing the compiler that @t ~ Float@.
-- This should work fine even if a user implements instances of Bounded for
-- the floating types, because we know that @t ~ Float@ or @t ~ Double@
-- automatically whenever this instance is called for these types.
| Dict <- (case (unsafeCoerce (Dict @(t ~ t)) :: Dict (t ~ Float)) of
Dict -> Dict) :: Dict (Bounded Float)
, Dict <- (case (unsafeCoerce (Dict @(t ~ t)) :: Dict (t ~ Double)) of
Dict -> Dict) :: Dict (Bounded Double)
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferNum
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Num t, Impl.KnownBackend t ds b)
=> Dict (Num (Backend I t ds b))
inferNum
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferFractional
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Fractional t, Impl.KnownBackend t ds b)
=> Dict (Fractional (Backend I t ds b))
inferFractional
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferFloating
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (Floating t, Impl.KnownBackend t ds b)
=> Dict (Floating (Backend I t ds b))
inferFloating
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferPrimBytes
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (PrimBytes t, Dimensions ds, Impl.KnownBackend t ds b)
=> Dict (PrimBytes (Backend I t ds b))
inferPrimBytes
= mapDict toBackend
. mapDict (Sub (Impl.inferBackendInstance @t @ds))
$ inferDeriveContext @t @ds @b undefined
inferPrimArray
:: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. (PrimBytes t, Impl.KnownBackend t ds b)
=> Dict (PrimArray t (Backend I t ds b))
inferPrimArray
= mapDict toBackend
. mapDict (Sub (Impl.inferPrimArray @t @ds))
$ inferDeriveContext @t @ds @b undefined
-- This is the rule that cannot be encoded in the type system, but enforced
-- as an invariant: Backend t ds b implies DeriveContext t ds b
inferDeriveContext :: forall (t :: Type) (ds :: [Nat]) (b :: Type)
. Backend I t ds b
-> Dict (DeriveContext (Backend I t ds b))
inferDeriveContext _ = unsafeCoerce (Dict :: Dict (b ~ b))
{-# INLINE inferDeriveContext #-}
-- Backend is the newtype wrapper over b.
-- It has the same represenation and I expect it to have the same instance behavior.
toBackend :: forall c t ds b . c b :- c (Backend I t ds b)
toBackend = unsafeDerive Backend
{-# INLINE toBackend #-}