dimensions-1.0.1.1: src/Numeric/Dimensions/Dims.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# LANGUAGE ViewPatterns #-}
-----------------------------------------------------------------------------
-- |
-- Module : Numeric.Dimensions.Dims
-- Copyright : (c) Artem Chirkin
-- License : BSD3
--
-- Maintainer : chirkin@arch.ethz.ch
--
-- Provides a data type `Dims ds` to keep dimension sizes
-- for multiple-dimensional data.
-- Lower indices go first, i.e. assumed enumeration
-- is i = i1 + i2*n1 + i3*n1*n2 + ... + ik*n1*n2*...*n(k-1).
--
-----------------------------------------------------------------------------
module Numeric.Dimensions.Dims
( Dims, SomeDims (..), Dimensions (..), XDimensions (..)
, TypedList ( Dims, XDims, AsXDims, KnownDims
, U, (:*), Empty, TypeList, Cons, Snoc, Reverse)
, listDims, someDimsVal, totalDim, totalDim'
, sameDims, sameDims'
, compareDims, compareDims'
, inSpaceOf, asSpaceOf
, xDims, xDims'
-- * Type-level programming
-- Provide type families to work with lists of dimensions (`[Nat]` or `[XNat]`)
, AsXDims, AsDims, FixedDims, KnownXNatTypes, type (:<), type (>:)
-- * Re-export type list
, RepresentableList (..), TypeList, types
, order, order'
-- * Re-export single dimension type and functions
, module Numeric.Dim
) where
import GHC.Exts (unsafeCoerce#, Constraint)
import qualified Text.Read as Read
import Numeric.Dim
import Numeric.Type.Evidence
import Numeric.Type.List
import Numeric.TypedList (RepresentableList (..), TypeList,
TypedList (..), order, order', types)
-- | Type-level dimensionality O(1).
type Dims (xs :: [k]) = TypedList Dim xs
-- Starting from GHC 8.2, compiler supports specifying lists of complete
-- pattern synonyms.
#if __GLASGOW_HASKELL__ >= 802
{-# COMPLETE Dims #-}
{-# COMPLETE XDims #-}
{-# COMPLETE AsXDims #-}
{-# COMPLETE KnownDims #-}
#endif
-- | @O(1)@ Pattern-matching against this constructor brings a `Dimensions`
-- instance into the scope.
-- Thus, you can do arbitrary operations on your dims and use this pattern
-- at any time to reconstruct the class instance at runtime.
pattern Dims :: forall ds . () => Dimensions ds => Dims ds
pattern Dims <- (dimsEv -> E)
where
Dims = dims @_ @ds
-- | @O(Length ds)@ `Dimensions` and `KnownDim` for each individual dimension.
pattern KnownDims :: forall ds . ()
=> (All KnownDim ds, Dimensions ds) => Dims ds
pattern KnownDims <- (patKDims -> PatKDims)
where
KnownDims = dims @_ @ds
-- | Pattern-matching against this constructor reveals Nat-kinded list of dims,
-- pretending the dimensionality is known at compile time within the scope
-- of the pattern match.
-- This is the main recommended way to get `Dims` at runtime;
-- for example, reading a list of dimensions from a file.
--
-- In order to use this pattern, one must know @XNat@ type constructors in
-- each dimension at compile time.
pattern XDims :: forall (xns :: [XNat]) . KnownXNatTypes xns
=> forall (ns :: [Nat]) . (FixedDims xns ns, Dimensions ns)
=> Dims ns -> Dims xns
pattern XDims ns <- (patXDims -> PatXDims ns)
where
XDims ns = unsafeCoerce# ns
-- | An easy way to convert Nat-indexed dims into XNat-indexed dims.
pattern AsXDims :: forall (ns :: [Nat]) . ()
=> (KnownXNatTypes (AsXDims ns), RepresentableList (AsXDims ns))
=> Dims (AsXDims ns) -> Dims ns
pattern AsXDims xns <- (patAsXDims -> PatAsXDims xns)
where
AsXDims xns = unsafeCoerce# xns
-- | Same as SomeNat, but for Dimensions:
-- Hide all information about Dimensions inside
data SomeDims = forall (ns :: [Nat]) . SomeDims (Dims ns)
-- | Put runtime evidence of `Dims` value inside function constraints.
-- Similar to `KnownDim` or `KnownNat`, but for lists of numbers.
-- Normally, the kind paramater is `Nat` (known dimenionality)
-- or `XNat` (either known or constrained dimensionality).
class Dimensions (ds :: [k]) where
-- | Get dimensionality of a space at runtime,
-- represented as a list of `Dim`.
--
-- Note, this function is supposed to be used with @TypeApplications@,
-- and the @Dimensions@ class has varying kind of the parameter;
-- thus, the function has two type paremeters (kind and type of @ds@).
-- For example, you can type:
--
-- >>>:set -XTypeApplications
-- >>>:set -XDataKinds
-- >>>:t dims @_ @'[N 17, N 12]
-- dims @_ @'[N 17, N 12] :: Dims '[N 17, N 12]
--
-- >>>:t dims @XNat @'[]
-- dims @XNat @'[] :: Dims '[]
--
--
-- >>>:t dims @_ @(Tail '[3,2,5,7])
-- dims @_ @(Tail '[3,2,5,7]) :: Dims '[2, 5, 7]
--
dims :: Dims ds
instance Dimensions ('[] :: [k]) where
dims = U
{-# INLINE dims #-}
instance (KnownDim d, Dimensions ds) => Dimensions (d ': ds :: [k]) where
dims = dim :* dims
{-# INLINE dims #-}
-- | Analogous to `Dimensions`, but weaker and more specific (list of `XNat`).
-- This class is used to check if an existing fixed `Dims` satisfy
-- constraints imposed by some interface (e.g. all dimensions are greater than 2).
-- It is weaker than `Dimensions` in that it only requires knowledge of constraints
-- rather than exact dimension values.
class KnownXNatTypes xds => XDimensions (xds :: [XNat]) where
-- | Given a `Dims`, test if its runtime value satisfies constraints imposed by
-- @XDimensions@, and returns it back being `XNat`-indexed on success.
--
-- This function allows to guess safely individual dimension values,
-- as well as the length of the dimension list.
constrainDims :: Dims (ds :: [k]) -> Maybe (Dims xds)
instance XDimensions '[] where
constrainDims U = Just U
constrainDims _ = Nothing
{-# INLINE constrainDims #-}
instance (XDimensions xs, KnownDim m) => XDimensions (XN m ': xs) where
constrainDims (d :* ds) = case constrain d of
Nothing -> Nothing
Just xd -> (xd :*) <$> constrainDims ds
constrainDims Empty = Nothing
instance (XDimensions xs, KnownDim n) => XDimensions (N n ': xs) where
constrainDims (d :* ds)
| unsafeCoerce# d == dimVal' @n = (Dn D :*) <$> constrainDims ds
| otherwise = Nothing
constrainDims Empty = Nothing
-- | Convert `Dims xs` to a plain haskell list of dimension sizes @O(1)@.
listDims :: Dims xs -> [Word]
listDims = unsafeCoerce#
{-# INLINE listDims #-}
-- | Convert a plain haskell list of dimension sizes into an unknown
-- type-level dimensionality @O(1)@.
someDimsVal :: [Word] -> SomeDims
someDimsVal = SomeDims . unsafeCoerce#
{-# INLINE someDimsVal #-}
-- | Product of all dimension sizes @O(Length xs)@.
totalDim :: Dims xs -> Word
totalDim = product . listDims
{-# INLINE totalDim #-}
-- | Product of all dimension sizes @O(Length xs)@.
totalDim' :: forall xs . Dimensions xs => Word
totalDim' = totalDim (dims @_ @xs)
{-# INLINE totalDim' #-}
-- | Get XNat-indexed dims given their fixed counterpart.
xDims :: FixedDims xns ns => Dims ns -> Dims xns
xDims = unsafeCoerce#
{-# INLINE xDims #-}
-- | Get XNat-indexed dims given their fixed counterpart.
xDims' :: forall xns ns . (FixedDims xns ns, Dimensions ns) => Dims xns
xDims' = xDims @xns (dims @Nat @ns)
{-# INLINE xDims' #-}
-- | We either get evidence that this function was instantiated with the
-- same type-level Dimensions, or 'Nothing' @O(Length xs)@.
--
-- Note, this function works on @Nat@-indexed dimensions only,
-- because @Dims '[XN x]@ does not have runtime evidence to infer @x@
-- and `KnownDim x` does not imply `KnownDim (XN x)`.
sameDims :: Dims (as :: [Nat]) -> Dims (bs :: [Nat]) -> Maybe (Evidence (as ~ bs))
sameDims as bs
| listDims as == listDims bs
= Just (unsafeCoerce# (E @('[] ~ '[])))
| otherwise = Nothing
{-# INLINE sameDims #-}
-- | We either get evidence that this function was instantiated with the
-- same type-level Dimensions, or 'Nothing' @O(Length xs)@.
sameDims' :: forall (as :: [Nat]) (bs :: [Nat]) p q
. (Dimensions as, Dimensions bs)
=> p as -> q bs -> Maybe (Evidence (as ~ bs))
sameDims' _ _ = sameDims (dims @Nat @as) (dims @Nat @bs)
{-# INLINE sameDims' #-}
-- | Compare dimensions by their size in lexicorgaphic order
-- from the last dimension to the first dimension
-- (the last dimension is the most significant one).
--
-- Literally,
--
-- > compareDims a b = compare (reverse $ listDims a) (reverse $ listDims b)
compareDims :: Dims as -> Dims bs -> Ordering
compareDims a b = compare (reverse $ listDims a) (reverse $ listDims b)
{-# INLINE compareDims #-}
-- | Compare dimensions by their size in lexicorgaphic order
-- from the last dimension to the first dimension
-- (the last dimension is the most significant one) @O(Length xs)@.
--
-- Literally,
--
-- > compareDims a b = compare (reverse $ listDims a) (reverse $ listDims b)
--
-- This is the same @compare@ rule, as for `Idxs`.
compareDims' :: forall as bs p q
. (Dimensions as, Dimensions bs)
=> p as -> q bs -> Ordering
compareDims' _ _ = compareDims (dims @_ @as) (dims @_ @bs)
{-# INLINE compareDims' #-}
-- | Similar to `const` or `asProxyTypeOf`;
-- to be used on such implicit functions as `dim`, `dimMax`, etc.
inSpaceOf :: a ds -> b ds -> a ds
inSpaceOf x _ = x
{-# INLINE inSpaceOf #-}
-- | Similar to `asProxyTypeOf`,
-- Give a hint to type checker to fix the type of a function argument.
asSpaceOf :: a ds -> (b ds -> c) -> (b ds -> c)
asSpaceOf _ = id
{-# INLINE asSpaceOf #-}
instance Eq (Dims (ds :: [Nat])) where
(==) _ _ = True
instance Eq (Dims (ds :: [XNat])) where
(==) = unsafeCoerce# ((==) :: [Word] -> [Word] -> Bool)
instance Eq SomeDims where
SomeDims as == SomeDims bs = listDims as == listDims bs
instance Ord (Dims (ds :: [Nat])) where
compare _ _ = EQ
instance Ord (Dims (ds :: [XNat])) where
compare = compareDims
instance Ord SomeDims where
compare (SomeDims as) (SomeDims bs) = compareDims as bs
instance Show (Dims xs) where
show ds = "Dims " ++ show (listDims ds)
showsPrec p ds
= showParen (p >= 10)
$ showString "Dims " . showsPrec p (listDims ds)
instance Show SomeDims where
show (SomeDims ds) = "SomeDims " ++ show (listDims ds)
showsPrec p (SomeDims ds)
= showParen (p >= 10)
$ showString "SomeDims " . showsPrec p (listDims ds)
instance Read SomeDims where
readPrec = Read.parens $ Read.prec 10 $ do
s <- Read.lexP
if s == Read.Ident "SomeDims"
then someDimsVal <$> Read.readPrec
else Read.pfail
instance Dimensions ds => Bounded (Dims ds) where
maxBound = dims
{-# INLINE maxBound #-}
minBound = dims
{-# INLINE minBound #-}
-- | Map Dims onto XDims (injective)
type family AsXDims (ns :: [Nat]) = (xns :: [XNat]) | xns -> ns where
AsXDims '[] = '[]
AsXDims (n ': ns) = N n ': AsXDims ns
-- | Map XDims onto Dims (injective)
type family AsDims (xns::[XNat]) = (ns :: [Nat]) | ns -> xns where
AsDims '[] = '[]
AsDims (N x ': xs) = x ': AsDims xs
-- | Constrain @Nat@ dimensions hidden behind @XNat@s.
type family FixedDims (xns::[XNat]) (ns :: [Nat]) :: Constraint where
FixedDims '[] ns = (ns ~ '[])
FixedDims (xn ': xns) ns
= ( ns ~ (Head ns ': Tail ns)
, FixedDim xn (Head ns)
, FixedDims xns (Tail ns))
-- | Know the structure of each dimension
type KnownXNatTypes xns = All KnownXNatType xns
-- | Synonym for (:+) that treats Nat values 0 and 1 in a special way:
-- it preserves the property that all dimensions are greater than 1.
type family (n :: Nat) :< (ns :: [Nat]) :: [Nat] where
0 :< _ = '[]
1 :< ns = ns
n :< ns = n :+ ns
infixr 6 :<
-- | Synonym for (+:) that treats Nat values 0 and 1 in a special way:
-- it preserves the property that all dimensions are greater than 1.
type family (ns :: [Nat]) >: (n :: Nat) :: [Nat] where
_ >: 0 = '[]
ns >: 1 = ns
ns >: n = ns +: n
infixl 6 >:
--------------------------------------------------------------------------------
-- | This function does GHC's magic to convert user-supplied `dims` function
-- to create an instance of `Dimensions` typeclass at runtime.
-- The trick is taken from Edward Kmett's reflection library explained
-- in https://www.schoolofhaskell.com/user/thoughtpolice/using-reflection
reifyDims :: forall r ds . Dims ds -> ( Dimensions ds => r) -> r
reifyDims ds k = unsafeCoerce# (MagicDims k :: MagicDims ds r) ds
{-# INLINE reifyDims #-}
newtype MagicDims ds r = MagicDims (Dimensions ds => r)
dimsEv :: Dims ds -> Evidence (Dimensions ds)
dimsEv ds = reifyDims ds E
{-# INLINE dimsEv #-}
data PatXDims (xns :: [XNat])
= forall (ns :: [Nat])
. (FixedDims xns ns, Dimensions ns) => PatXDims (Dims ns)
patXDims :: All KnownXNatType xns => Dims xns -> PatXDims xns
patXDims U = PatXDims U
patXDims (Dn n :* xns) = case patXDims xns of
PatXDims ns -> PatXDims (n :* ns)
patXDims (Dx n :* xns) = case patXDims xns of
PatXDims ns -> PatXDims (n :* ns)
#if __GLASGOW_HASKELL__ >= 802
#else
patXDims _ = error "XDims/patXDims: impossible argument"
#endif
{-# INLINE patXDims #-}
data PatAsXDims (ns :: [Nat])
= (KnownXNatTypes (AsXDims ns), RepresentableList (AsXDims ns))
=> PatAsXDims (Dims (AsXDims ns))
patAsXDims :: Dims ns -> PatAsXDims ns
patAsXDims U = PatAsXDims U
patAsXDims (n@D :* ns) = case patAsXDims ns of
PatAsXDims xns -> PatAsXDims (Dn n :* xns)
#if __GLASGOW_HASKELL__ >= 802
#else
patAsXDims _ = error "AsXDims/patAsXDims: impossible argument"
#endif
{-# INLINE patAsXDims #-}
data PatKDims (ns :: [k])
= (All KnownDim ns, Dimensions ns) => PatKDims
patKDims :: Dims ns -> PatKDims ns
patKDims U = PatKDims
patKDims (Dim :* ns) = case patKDims ns of
PatKDims -> PatKDims
#if __GLASGOW_HASKELL__ >= 802
#else
patKDims _ = error "Dims/patKDims: impossible argument"
#endif
{-# INLINE patKDims #-}