{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} module SizedGrid.Coord.Class where import SizedGrid.Ordinal import Control.Lens import Data.Constraint import Data.Maybe (fromJust) import Data.Proxy import GHC.TypeLits import Unsafe.Coerce (unsafeCoerce) -- | Proof an idiom about how `CoordFromNat` works. This relies on 'CoordFromNat a (CoordSized a ~ a' coordFromNatCollapse :: forall a x y. Dict (CoordFromNat (CoordFromNat a x) y ~ CoordFromNat a y) coordFromNatCollapse = unsafeCoerce (Dict :: Dict (z ~ z)) coordFromNatSame :: (CoordFromNat a ~ CoordFromNat b) :- (a ~ CoordFromNat b (CoordSized a)) coordFromNatSame = Sub (unsafeCoerce (Dict :: Dict (a ~ a))) coordSizedCollapse :: forall c n . Dict (CoordSized (CoordFromNat c n) ~ n) coordSizedCollapse = unsafeCoerce (Dict :: Dict (a ~ a)) -- | Everything that can be uses as a Coordinate. The only required function is `asOrdinal` and the type instance of `CoordSized`: the rest can be derived automatically. -- -- This is kind * -> Constraint for ease of use later. There is some argument that it should be of kind (Nat -> *) -> Constraint and we could remove `CoordSized`, but that has other complications class (1 <= CoordSized c, KnownNat (CoordSized c)) => IsCoord c where -- | The maximum number of values that a Coord can take type CoordSized c :: Nat type CoordFromNat c :: (Nat -> *) -- | As each coord represents a finite number of states, it must be isomorphic to an Ordinal asOrdinal :: Iso' c (Ordinal (CoordSized c)) -- | The origin. If c is an instance of `Monoid`, this should be mempty zeroPosition :: c default zeroPosition :: Monoid c => c zeroPosition = mempty -- | Retrive a `Proxy` of the size sCoordSized :: proxy c -> Proxy (CoordSized c) sCoordSized _ = Proxy -- | The largest possible number expressable maxCoordSize :: proxy c -> Integer maxCoordSize p = natVal (sCoordSized p) - 1 maxCoord :: c maxCoord = view (re asOrdinal) maxCoord asSizeProxy :: c -> (forall n. (KnownNat n, n + 1 <= (CoordSized c)) => Proxy n -> x) -> x asSizeProxy c = asSizeProxy (view asOrdinal c) weakenIsCoord :: IsCoord (CoordFromNat c n) => c -> Maybe (CoordFromNat c n) weakenIsCoord = fmap (review asOrdinal) . weakenOrdinal . view asOrdinal strengthenIsCoord :: (IsCoord (CoordFromNat c n), CoordSized c <= CoordSized (CoordFromNat c n)) => c -> CoordFromNat c n strengthenIsCoord = review asOrdinal . strengthenOrdinal . view asOrdinal instance (1 <= n, KnownNat n) => IsCoord (Ordinal n) where type CoordSized (Ordinal n) = n type CoordFromNat (Ordinal n) = Ordinal asOrdinal = id zeroPosition = Ordinal (Proxy @0) asSizeProxy (Ordinal p) func = func p maxCoord = fromJust $ numToOrdinal (maxCoordSize (Proxy :: Proxy (Ordinal n))) -- | Enumerate all possible values of a coord, in order allCoordLike :: IsCoord c => [c] allCoordLike = toListOf (traverse . re asOrdinal) [minBound .. maxBound]