ox-arrays-0.2.0.0: src/Data/Array/Nested/Ranked/Shape.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE StrictData #-}
#if MIN_VERSION_GLASGOW_HASKELL(9,8,0,0)
{-# LANGUAGE TypeAbstractions #-}
#endif
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
module Data.Array.Nested.Ranked.Shape where
import Control.DeepSeq (NFData(..))
import Control.Exception (assert)
import Data.Coerce (coerce)
import Data.Foldable qualified as Foldable
import Data.Kind (Type)
import Data.Proxy
import Data.Type.Equality
import GHC.Exts (build)
import GHC.IsList (IsList)
import GHC.IsList qualified as IsList
import GHC.TypeLits
import GHC.TypeNats qualified as TN
import Data.Array.Nested.Lemmas
import Data.Array.Nested.Mixed.ListX
import Data.Array.Nested.Mixed.Shape
import Data.Array.Nested.Permutation
import Data.Array.Nested.Types
-- * Ranked indices
-- | An index into a rank-typed array.
type role IxR nominal representational
type IxR :: Nat -> Type -> Type
newtype IxR n i = IxR (IxX (Replicate n Nothing) i)
deriving (Eq, Ord, NFData, Functor, Foldable)
pattern ZIR :: forall n i. () => n ~ 0 => IxR n i
pattern ZIR <- IxR (matchZIX @n -> Just Refl)
where ZIR = IxR ZIX
matchZIX :: forall n i. IxX (Replicate n Nothing) i -> Maybe (n :~: 0)
matchZIX ZIX | Refl <- lemReplicateEmpty (Proxy @n) Refl = Just Refl
matchZIX _ = Nothing
pattern (:.:)
:: forall {n1} {i}.
forall n. (n + 1 ~ n1)
=> i -> IxR n i -> IxR n1 i
pattern i :.: l <- (ixrUncons -> Just (UnconsIxRRes i l))
where i :.: IxR l | Refl <- lemReplicateSucc2 (Proxy @n1) Refl = IxR (i :.% l)
infixr 3 :.:
data UnconsIxRRes i n1 =
forall n. (n + 1 ~ n1) => UnconsIxRRes i (IxR n i)
ixrUncons :: forall n1 i. IxR n1 i -> Maybe (UnconsIxRRes i n1)
ixrUncons (IxR ((:.%) @n @sh i l))
| Refl <- lemReplicateHead (Proxy @n) (Proxy @sh) (Proxy @Nothing) (Proxy @n1) Refl
, Refl <- lemReplicateCons (Proxy @sh) (Proxy @n1) Refl
, Refl <- lemReplicateCons2 (Proxy @sh) (Proxy @n1) Refl =
Just (UnconsIxRRes i (IxR @(Rank sh) l))
ixrUncons (IxR _) = Nothing
{-# COMPLETE ZIR, (:.:) #-}
-- For convenience, this contains regular 'Int's instead of bounded integers
-- (traditionally called \"@Fin@\").
type IIxR n = IxR n Int
#ifdef OXAR_DEFAULT_SHOW_INSTANCES
deriving instance Show i => Show (IxR n i)
#else
instance Show i => Show (IxR n i) where
showsPrec _ = ixrShow shows
#endif
-- | This checks only whether the ranks are equal, not whether the actual
-- values are.
ixrEqRank :: IxR n i -> IxR n' i -> Maybe (n :~: n')
ixrEqRank ZIR ZIR = Just Refl
ixrEqRank (_ :.: sh) (_ :.: sh')
| Just Refl <- ixrEqRank sh sh'
= Just Refl
ixrEqRank _ _ = Nothing
-- | This compares the lists for value equality.
ixrEqual :: Eq i => IxR n i -> IxR n' i -> Maybe (n :~: n')
ixrEqual ZIR ZIR = Just Refl
ixrEqual (i :.: sh) (j :.: sh')
| Just Refl <- ixrEqual sh sh'
, i == j
= Just Refl
ixrEqual _ _ = Nothing
{-# INLINE ixrShow #-}
ixrShow :: forall n i. (i -> ShowS) -> IxR n i -> ShowS
ixrShow f l = showString "[" . go "" l . showString "]"
where
go :: String -> IxR n' i -> ShowS
go _ ZIR = id
go prefix (x :.: xs) = showString prefix . f x . go "," xs
ixrRank :: IxR n i -> SNat n
ixrRank ZIR = SNat
ixrRank (_ :.: sh) = snatSucc (ixrRank sh)
ixrZero :: SNat n -> IIxR n
ixrZero SZ = ZIR
ixrZero (SS n) = 0 :.: ixrZero n
{-# INLINE ixrFromList #-}
ixrFromList :: SNat n -> [i] -> IxR n i
ixrFromList topsn topl = assert (fromSNat' topsn == length topl)
$ IxR $ IsList.fromList topl
ixrHead :: IxR (n + 1) i -> i
ixrHead (i :.: _) = i
ixrTail :: IxR (n + 1) i -> IxR n i
ixrTail (_ :.: sh) = sh
ixrInit :: IxR (n + 1) i -> IxR n i
ixrInit (n :.: sh@(_ :.: _)) = n :.: ixrInit sh
ixrInit (_ :.: ZIR) = ZIR
ixrLast :: IxR (n + 1) i -> i
ixrLast (_ :.: sh@(_ :.: _)) = ixrLast sh
ixrLast (n :.: ZIR) = n
-- | Performs a runtime check that the lengths are identical.
ixrCast :: SNat n' -> IxR n i -> IxR n' i
ixrCast SZ ZIR = ZIR
ixrCast (SS n) (i :.: l) = i :.: ixrCast n l
ixrCast _ _ = error "ixrCast: ranks don't match"
-- lemReplicatePlusApp requires SNat that would cause overhead (not benchmarked)
ixrAppend :: forall n m i. IxR n i -> IxR m i -> IxR (n + m) i
ixrAppend = gcastWith (unsafeCoerceRefl :: Replicate (n + m) (Nothing @Nat) :~: Replicate n Nothing ++ Replicate m Nothing) $
coerce (ixxAppend @_ @_ @i)
ixrIndex :: forall k n i. (k + 1 <= n) => SNat k -> IxR n i -> i
ixrIndex SZ (x :.: _) = x
ixrIndex (SS i) (_ :.: xs) | Refl <- lemLeqSuccSucc (Proxy @k) (Proxy @n) = ixrIndex i xs
ixrIndex _ ZIR = error "k + 1 <= 0"
ixrZip :: IxR n i -> IxR n j -> IxR n (i, j)
ixrZip ZIR ZIR = ZIR
ixrZip (i :.: irest) (j :.: jrest) = (i, j) :.: ixrZip irest jrest
ixrZip _ _ = error "ixrZip: impossible pattern needlessly required"
{-# INLINE ixrZipWith #-}
ixrZipWith :: (i -> j -> k) -> IxR n i -> IxR n j -> IxR n k
ixrZipWith _ ZIR ZIR = ZIR
ixrZipWith f (i :.: irest) (j :.: jrest) =
f i j :.: ixrZipWith f irest jrest
ixrZipWith _ _ _ =
error "ixrZipWith: impossible pattern needlessly required"
ixrSplitAt :: m <= n' => SNat m -> IxR n' i -> (IxR m i, IxR (n' - m) i)
ixrSplitAt SZ sh = (ZIR, sh)
ixrSplitAt (SS m) (n :.: sh) = (\(pre, post) -> (n :.: pre, post)) (ixrSplitAt m sh)
ixrSplitAt SS{} ZIR = error "m' + 1 <= 0"
ixrPermutePrefix :: forall n i. PermR -> IxR n i -> IxR n i
ixrPermutePrefix = \perm sh ->
TN.withSomeSNat (fromIntegral (length perm)) $ \permlen@SNat ->
case ixrRank sh of { shlen@SNat ->
let sperm = ixrFromList permlen perm in
case cmpNat permlen shlen of
LTI -> let (pre, post) = ixrSplitAt permlen sh in ixrAppend (applyPermRFull permlen sperm pre) post
EQI -> let (pre, post) = ixrSplitAt permlen sh in ixrAppend (applyPermRFull permlen sperm pre) post
GTI -> error $ "Length of permutation (" ++ show (fromSNat' permlen) ++ ")"
++ " > length of shape (" ++ show (fromSNat' shlen) ++ ")"
}
where
applyPermRFull :: SNat m -> IxR k Int -> IxR m i -> IxR k i
applyPermRFull _ ZIR _ = ZIR
applyPermRFull sm@SNat (i :.: perm) l =
TN.withSomeSNat (fromIntegral i) $ \si@(SNat :: SNat idx) ->
case cmpNat (SNat @(idx + 1)) sm of
LTI -> ixrIndex si l :.: applyPermRFull sm perm l
EQI -> ixrIndex si l :.: applyPermRFull sm perm l
GTI -> error "ixrPermutePrefix: Index in permutation out of range"
-- | Given a multidimensional index, get the corresponding linear
-- index into the buffer.
{-# INLINEABLE ixrToLinear #-}
ixrToLinear :: Num i => IShR m -> IxR m i -> i
ixrToLinear (ShR sh) ix = ixxToLinear sh (ixxFromIxR ix)
ixxFromIxR :: IxR n i -> IxX (Replicate n Nothing) i
ixxFromIxR = coerce
{-# INLINEABLE ixrFromLinear #-}
ixrFromLinear :: forall i m. Num i => IShR m -> Int -> IxR m i
ixrFromLinear (ShR sh) i
| Refl <- lemRankReplicate (Proxy @m)
= ixrFromIxX $ ixxFromLinear sh i
ixrFromIxX :: IxX (Replicate n Nothing) i -> IxR n i
ixrFromIxX = coerce
shrEnum :: IShR n -> [IIxR n]
shrEnum = shrEnum'
{-# INLINABLE shrEnum' #-} -- ensure this can be specialised at use site
shrEnum' :: forall i n. Num i => IShR n -> [IxR n i]
shrEnum' (ShR sh)
| Refl <- lemRankReplicate (Proxy @n)
= (coerce :: [IxX (Replicate n Nothing) i] -> [IxR n i]) $ shxEnum' sh
-- * Ranked shapes
type role ShR nominal representational
type ShR :: Nat -> Type -> Type
newtype ShR n i = ShR (ShX (Replicate n Nothing) i)
deriving (Eq, Ord, NFData, Functor)
pattern ZSR :: forall n i. () => n ~ 0 => ShR n i
pattern ZSR <- ShR (matchZSR @n -> Just Refl)
where ZSR = ShR ZSX
matchZSR :: forall n i. ShX (Replicate n Nothing) i -> Maybe (n :~: 0)
matchZSR ZSX | Refl <- lemReplicateEmpty (Proxy @n) Refl = Just Refl
matchZSR _ = Nothing
pattern (:$:)
:: forall {n1} {i}.
forall n. (n + 1 ~ n1)
=> i -> ShR n i -> ShR n1 i
pattern i :$: sh <- (shrUncons -> Just (UnconsShRRes i sh))
where i :$: ShR sh | Refl <- lemReplicateSucc2 (Proxy @n1) Refl = ShR (SUnknown i :$% sh)
infixr 3 :$:
data UnconsShRRes i n1 =
forall n. (n + 1 ~ n1) => UnconsShRRes i (ShR n i)
shrUncons :: forall n1 i. ShR n1 i -> Maybe (UnconsShRRes i n1)
shrUncons (ShR (SUnknown x :$% (sh' :: ShX sh' i)))
| Refl <- lemReplicateCons (Proxy @sh') (Proxy @n1) Refl
, Refl <- lemReplicateCons2 (Proxy @sh') (Proxy @n1) Refl
= Just (UnconsShRRes x (ShR sh'))
shrUncons (ShR _) = Nothing
{-# COMPLETE ZSR, (:$:) #-}
type IShR n = ShR n Int
#ifdef OXAR_DEFAULT_SHOW_INSTANCES
deriving instance Show i => Show (ShR n i)
#else
instance Show i => Show (ShR n i) where
showsPrec d (ShR l) = showsPrec d l
#endif
-- | This checks only whether the ranks are equal, not whether the actual
-- values are.
shrEqRank :: ShR n i -> ShR n' i -> Maybe (n :~: n')
shrEqRank ZSR ZSR = Just Refl
shrEqRank (_ :$: sh) (_ :$: sh')
| Just Refl <- shrEqRank sh sh'
= Just Refl
shrEqRank _ _ = Nothing
-- | This compares the shapes for value equality.
shrEqual :: Eq i => ShR n i -> ShR n' i -> Maybe (n :~: n')
shrEqual ZSR ZSR = Just Refl
shrEqual (i :$: sh) (i' :$: sh')
| Just Refl <- shrEqual sh sh'
, i == i'
= Just Refl
shrEqual _ _ = Nothing
shrLength :: ShR sh i -> Int
shrLength (ShR l) = shxLength l
-- | This function can also be used to conjure up a 'KnownNat' dictionary;
-- pattern matching on the returned 'SNat' with the 'pattern SNat' pattern
-- synonym yields 'KnownNat' evidence.
shrRank :: forall n i. ShR n i -> SNat n
shrRank (ShR sh) | Refl <- lemRankReplicate (Proxy @n) = shxRank sh
-- | The number of elements in an array described by this shape.
shrSize :: IShR n -> Int
shrSize (ShR sh) = shxSize sh
-- This is equivalent to but faster than @coerce (shxFromList (ssxReplicate snat))@.
-- We don't report the size of the list in case of errors in order not to retain the list.
{-# INLINEABLE shrFromList #-}
shrFromList :: SNat n -> [Int] -> IShR n
shrFromList snat topl = ShR $ go snat topl
where
go :: SNat n -> [Int] -> ShX (Replicate n Nothing) Int
go SZ [] = ZSX
go SZ _ = error $ "shrFromList: List too long (type says " ++ show (fromSNat' snat) ++ ")"
go (SS sn :: SNat n1) (i : is) | Refl <- lemReplicateSucc2 (Proxy @n1) Refl = ConsUnknown i (go sn is)
go _ _ = error $ "shrFromList: List too short (type says " ++ show (fromSNat' snat) ++ ")"
-- This is equivalent to but faster than @coerce shxToList@.
{-# INLINEABLE shrToList #-}
shrToList :: IShR n -> [Int]
shrToList (ShR l) = build (\(cons :: i -> is -> is) (nil :: is) ->
let go :: ShX sh Int -> is
go ZSX = nil
go (ConsUnknown i rest) = i `cons` go rest
go ConsKnown{} = error "shrToList: impossible case"
in go l)
shrHead :: forall n i. ShR (n + 1) i -> i
shrHead (ShR sh)
| Refl <- lemReplicateSucc @(Nothing @Nat) (Proxy @n)
= case shxHead @Nothing @(Replicate n Nothing) sh of
SUnknown i -> i
shrTail :: forall n i. ShR (n + 1) i -> ShR n i
shrTail
| Refl <- lemReplicateSucc @(Nothing @Nat) (Proxy @n)
= coerce (shxTail @_ @_ @i)
{-# INLINEABLE shrTakeIx #-}
shrTakeIx :: forall n n' i j. Proxy n' -> IxR n j -> ShR (n + n') i -> ShR n i
shrTakeIx _ ZIR _ = ZSR
shrTakeIx p (_ :.: idx) sh = case sh of n :$: sh' -> n :$: shrTakeIx p idx sh'
{-# INLINEABLE shrDropIx #-}
shrDropIx :: forall n n' i j. IxR n j -> ShR (n + n') i -> ShR n' i
shrDropIx ZIR long = long
shrDropIx (_ :.: short) long = case long of _ :$: long' -> shrDropIx short long'
shrInit :: forall n i. ShR (n + 1) i -> ShR n i
shrInit
| Refl <- lemReplicateSucc @(Nothing @Nat) (Proxy @n)
= -- TODO: change this and all other unsafeCoerceRefl to lemmas:
gcastWith (unsafeCoerceRefl
:: Init (Replicate (n + 1) (Nothing @Nat)) :~: Replicate n Nothing) $
coerce (shxInit @i)
shrLast :: forall n i. ShR (n + 1) i -> i
shrLast (ShR sh)
| Refl <- lemReplicateSucc @(Nothing @Nat) (Proxy @n)
= case shxLast sh of
SUnknown i -> i
SKnown{} -> error "shrLast: impossible SKnown"
-- | Performs a runtime check that the lengths are identical.
shrCast :: SNat n' -> ShR n i -> ShR n' i
shrCast SZ ZSR = ZSR
shrCast (SS n) (i :$: sh) = i :$: shrCast n sh
shrCast _ _ = error "shrCast: ranks don't match"
shrAppend :: forall n m i. ShR n i -> ShR m i -> ShR (n + m) i
shrAppend =
-- lemReplicatePlusApp requires an SNat
gcastWith (unsafeCoerceRefl
:: Replicate n (Nothing @Nat) ++ Replicate m Nothing :~: Replicate (n + m) Nothing) $
coerce (shxAppend @_ @i)
{-# INLINE shrZipWith #-}
shrZipWith :: (i -> j -> k) -> ShR n i -> ShR n j -> ShR n k
shrZipWith _ ZSR ZSR = ZSR
shrZipWith f (i :$: irest) (j :$: jrest) =
f i j :$: shrZipWith f irest jrest
shrZipWith _ _ _ =
error "shrZipWith: impossible pattern needlessly required"
shrSplitAt :: m <= n' => SNat m -> ShR n' i -> (ShR m i, ShR (n' - m) i)
shrSplitAt SZ sh = (ZSR, sh)
shrSplitAt (SS m) (n :$: sh) = (\(pre, post) -> (n :$: pre, post)) (shrSplitAt m sh)
shrSplitAt SS{} ZSR = error "m' + 1 <= 0"
shrIndex :: forall k sh i. SNat k -> ShR sh i -> i
shrIndex k (ShR sh) = case shxIndex @i k sh of
SUnknown i -> i
SKnown{} -> error "shrIndex: impossible SKnown"
-- Copy-pasted from ixrPermutePrefix, probably unavoidably.
shrPermutePrefix :: forall i n. PermR -> ShR n i -> ShR n i
shrPermutePrefix = \perm sh ->
TN.withSomeSNat (fromIntegral (length perm)) $ \permlen@SNat ->
case shrRank sh of { shlen@SNat ->
let sperm = shrFromList permlen perm in
case cmpNat permlen shlen of
LTI -> let (pre, post) = shrSplitAt permlen sh in shrAppend (applyPermRFull permlen sperm pre) post
EQI -> let (pre, post) = shrSplitAt permlen sh in shrAppend (applyPermRFull permlen sperm pre) post
GTI -> error $ "Length of permutation (" ++ show (fromSNat' permlen) ++ ")"
++ " > length of shape (" ++ show (fromSNat' shlen) ++ ")"
}
where
applyPermRFull :: SNat m -> ShR k Int -> ShR m i -> ShR k i
applyPermRFull _ ZSR _ = ZSR
applyPermRFull sm@SNat (i :$: perm) l =
TN.withSomeSNat (fromIntegral i) $ \si@(SNat :: SNat idx) ->
case cmpNat (SNat @(idx + 1)) sm of
LTI -> shrIndex si l :$: applyPermRFull sm perm l
EQI -> shrIndex si l :$: applyPermRFull sm perm l
GTI -> error "shrPermutePrefix: Index in permutation out of range"
-- | Untyped: length is checked at runtime.
instance KnownNat n => IsList (IxR n i) where
type Item (IxR n i) = i
fromList = ixrFromList (SNat @n)
toList = Foldable.toList
-- | Untyped: length is checked at runtime.
instance KnownNat n => IsList (IShR n) where
type Item (IShR n) = Int
fromList = shrFromList (SNat @n)
toList = shrToList