strongweak 0.3.2 → 0.4.0
raw patch · 6 files changed
+117/−91 lines, 6 files
Files
- CHANGELOG.md +6/−0
- src/Strongweak.hs +11/−7
- src/Strongweak/Strengthen.hs +30/−27
- src/Strongweak/Strengthen/Unsafe.hs +32/−26
- src/Strongweak/Weaken.hs +36/−29
- strongweak.cabal +2/−2
CHANGELOG.md view
@@ -1,3 +1,9 @@+## 0.4.0 (2023-02-22)+ * redesign some instances to avoid the decomposer style+ * alter `Identity`, `Const` instances+ * remove `Maybe` instance+ * expand sized vector instance+ ## 0.3.2 (2022-11-28) * support GHC 9.4
src/Strongweak.hs view
@@ -13,15 +13,19 @@ {- $strongweak-instance-design -We identify two distinct types of instances for strongweak classes:+strongweak is largely a programmer convenience library. There is a lot of room+to write instances which may seem useful on first glance, but are inconsistent+with the overall design. Here is some relevant guidance. - * /invariant handler:/ removes or adds an invariant- * /decomposer:/ transforms through some structural type+ * Weak types should have _simpler invariants to manage_ than strong ones.+ * In general, weak types should be easier to use than strong ones.+ * Most (all?) instances should handle (relax or assert) a single invariant.+ * Most instances should not have a recursive context. -In order to provide good behaviour and composability, we don't mix both in a-single instance. The decomposers are really just convenience to ease instance-derivation. In general, decomposers will have a recursive context, and invariant-handlers won't.+Some types may not have any invariants which may be usefully relaxed e.g.+@'Either' a b@. For these, you may write a recursive instance that+weakens/strengthens "through" the type e.g. @('Weak' a, 'Weak' b) => Weak+('Either' a b)@). Don't combine the two instance types. An example is @'Data.List.NonEmpty.NonEmpty' a@. We could weaken this to @[a]@, but also to @['Weak' a]@. However, the latter would mean decomposing and
src/Strongweak/Strengthen.hs view
@@ -31,8 +31,8 @@ import Data.Word import Data.Int import Refined ( Refined, refine, Predicate )-import Data.Vector.Sized qualified as Vector-import Data.Vector.Sized ( Vector )+import Data.Vector.Generic.Sized qualified as VGS -- Shazbot!+import Data.Vector.Generic qualified as VG import Data.Foldable qualified as Foldable import Control.Applicative ( liftA2 ) import Data.Functor.Identity@@ -122,27 +122,43 @@ strengthenFailBase w msg = Failure (e :| []) where e = StrengthenFailBase (show $ typeRep @w) (show $ typeRep @s) (show w) msg --- | Obtain a non-empty list by asserting non-emptiness of a plain list.+-- | Assert a predicate to refine a type.+instance (Predicate (p :: k) a, Typeable k, Typeable a, Show a) => Strengthen (Refined p a) where+ strengthen a =+ case refine a of+ Left err -> strengthenFailBase a (show err)+ Right ra -> Success ra++-- | Strengthen a plain list into a non-empty list by asserting non-emptiness. instance (Typeable a, Show a) => Strengthen (NonEmpty a) where strengthen a = case NonEmpty.nonEmpty a of Just a' -> Success a' Nothing -> strengthenFailBase a "empty list" --- | Obtain a sized vector by asserting the size of a plain list.-instance (KnownNat n, Typeable a, Show a) => Strengthen (Vector n a) where+-- | Strengthen a plain list into a sized vector by asserting length.+instance (VG.Vector v a, KnownNat n, Typeable v, Typeable a, Show a)+ => Strengthen (VGS.Vector v n a) where strengthen w =- case Vector.fromList w of+ case VGS.fromList w of Nothing -> strengthenFailBase w "TODO bad size vector" Just s -> Success s --- | Obtain a refined type by applying its associated refinement.-instance (Predicate (p :: k) a, Typeable k, Typeable a, Show a) => Strengthen (Refined p a) where- strengthen a =- case refine a of- Left err -> strengthenFailBase a (show err)- Right ra -> Success ra+-- | Add wrapper.+instance Strengthen (Identity a) where+ strengthen = pure <$> Identity +-- | Add wrapper.+instance Strengthen (Const a b) where+ strengthen = pure <$> Const++{- TODO controversial. seems logical, but also kinda annoying.+instance (Show a, Typeable a) => Strengthen (Maybe a) where+ strengthen = \case [a] -> pure $ Just a+ [] -> pure Nothing+ x -> strengthenFailBase x "list wasn't [a] or []"+-}+ -- Strengthen 'Natural's into Haskell's bounded unsigned numeric types. instance Strengthen Word8 where strengthen = strengthenBounded instance Strengthen Word16 where strengthen = strengthenBounded@@ -173,24 +189,11 @@ instance Strengthen a => Strengthen [a] where strengthen = traverse strengthen --- | Decomposer.+-- | Decomposer. Strengthen both elements of a tuple. instance (Strengthen a, Strengthen b) => Strengthen (a, b) where strengthen (a, b) = liftA2 (,) (strengthen a) (strengthen b) --- | Decomposer.-instance Strengthen a => Strengthen (Maybe a) where- strengthen = \case Just a -> Just <$> strengthen a- Nothing -> pure Nothing---- | Decomposer.+-- | Decomposer. Strengthen either side of an 'Either'. instance (Strengthen a, Strengthen b) => Strengthen (Either a b) where strengthen = \case Left a -> Left <$> strengthen a Right b -> Right <$> strengthen b---- | Decomposer.-instance Strengthen a => Strengthen (Identity a) where- strengthen = fmap Identity . strengthen . runIdentity---- | Decomposer.-instance Strengthen a => Strengthen (Const a b) where- strengthen = fmap Const . strengthen . getConst
src/Strongweak/Strengthen/Unsafe.hs view
@@ -5,9 +5,9 @@ import Data.Int import Refined ( Refined ) import Refined.Unsafe ( reallyUnsafeRefine )-import Data.Vector.Sized ( Vector )+import Data.Vector.Generic.Sized qualified as VGS -- Shazbot!+import Data.Vector.Generic qualified as VG import Data.Vector.Generic.Sized.Internal qualified-import Data.Vector qualified import Data.Functor.Identity import Data.Functor.Const import Data.List.NonEmpty qualified as NonEmpty@@ -37,18 +37,35 @@ -- | Unsafely transform a weak value to its associated strong one. unsafeStrengthen :: Weak a -> a --- | Unsafely assume a list is non-empty.+-- | Add a refinement to a type without checking the associated predicate.+instance UnsafeStrengthen (Refined p a) where+ unsafeStrengthen = reallyUnsafeRefine++-- | Assume a plain list is non-empty. instance UnsafeStrengthen (NonEmpty a) where unsafeStrengthen = NonEmpty.fromList --- | Unsafely assume the size of a plain list.-instance UnsafeStrengthen (Vector n a) where- unsafeStrengthen = Data.Vector.Generic.Sized.Internal.Vector . Data.Vector.fromList+-- | Assume the size of a plain list.+instance VG.Vector v a => UnsafeStrengthen (VGS.Vector v n a) where+ unsafeStrengthen =+ Data.Vector.Generic.Sized.Internal.Vector . VG.fromList --- | Wrap a value to a refined one without checking the predicate.-instance UnsafeStrengthen (Refined p a) where- unsafeStrengthen = reallyUnsafeRefine+-- | Add wrapper.+instance UnsafeStrengthen (Identity a) where+ unsafeStrengthen = Identity +-- | Add wrapper.+instance UnsafeStrengthen (Const a b) where+ unsafeStrengthen = Const++{- TODO controversial. seems logical, but also kinda annoying.+-- | Unsafely grab either 0 or 1 elements from a list.+instance UnsafeStrengthen (Maybe a) where+ unsafeStrengthen = \case [a] -> Just a+ [] -> Nothing+ _ -> error "your list wasn't [] or [a]"+-}+ -- Coerce 'Natural's into Haskell's bounded unsigned numeric types. Poorly-sized -- values will safely overflow according to the type's behaviour. instance UnsafeStrengthen Word8 where unsafeStrengthen = fromIntegral@@ -69,24 +86,13 @@ instance UnsafeStrengthen a => UnsafeStrengthen [a] where unsafeStrengthen = map unsafeStrengthen --- | Decomposer.-instance (UnsafeStrengthen a, UnsafeStrengthen b) => UnsafeStrengthen (a, b) where+-- | Decomposer. Unsafely strengthen both elements of a tuple.+instance (UnsafeStrengthen a, UnsafeStrengthen b)+ => UnsafeStrengthen (a, b) where unsafeStrengthen (a, b) = (unsafeStrengthen a, unsafeStrengthen b) --- | Decomposer.-instance UnsafeStrengthen a => UnsafeStrengthen (Maybe a) where- unsafeStrengthen = \case Just a -> Just $ unsafeStrengthen a- Nothing -> Nothing---- | Decomposer.-instance (UnsafeStrengthen a, UnsafeStrengthen b) => UnsafeStrengthen (Either a b) where+-- | Decomposer. Unsafely strengthen either side of an 'Either'.+instance (UnsafeStrengthen a, UnsafeStrengthen b)+ => UnsafeStrengthen (Either a b) where unsafeStrengthen = \case Left a -> Left $ unsafeStrengthen a Right b -> Right $ unsafeStrengthen b---- | Decomposer.-instance UnsafeStrengthen a => UnsafeStrengthen (Identity a) where- unsafeStrengthen = Identity . unsafeStrengthen . runIdentity---- | Decomposer.-instance UnsafeStrengthen a => UnsafeStrengthen (Const a b) where- unsafeStrengthen = Const . unsafeStrengthen . getConst
src/Strongweak/Weaken.hs view
@@ -13,8 +13,8 @@ import Numeric.Natural ( Natural ) import Data.Word import Data.Int-import Data.Vector.Sized qualified as Vector-import Data.Vector.Sized ( Vector )+import Data.Vector.Generic.Sized qualified as VGS -- Shazbot!+import Data.Vector.Generic qualified as VG import Data.Kind ( Type ) import Data.Functor.Identity import Data.Functor.Const@@ -26,9 +26,14 @@ A given strong type @a@ has exactly one associated weak type @'Weak' a@. Multiple strong types may weaken to the same weak type. -Law: @a === b -> 'weaken' a === 'weaken' b@+The following laws must hold: -Instances should /either/ handle an invariant, or decompose. See "Strongweak"+ * @a == b |= 'weaken' a == 'weaken' b@+ * round-trip: @'strengthen' ('weaken' a) == 'pure' a@++Most instances should strip an invariant, and not have a recursive context. Some+types don't have an invariant+/either/ handle an invariant, or decompose. See "Strongweak" for a discussion on this design. -} class Weaken a where@@ -64,21 +69,39 @@ SW 'Strong a = a SW 'Weak a = Weak a +-- | Strip refined type refinement.+instance Weaken (Refined p a) where+ type Weak (Refined p a) = a+ weaken = unrefine+ -- | Weaken non-empty lists into plain lists. instance Weaken (NonEmpty a) where type Weak (NonEmpty a) = [a] weaken = NonEmpty.toList -- | Weaken sized vectors into plain lists.-instance Weaken (Vector n a) where- type Weak (Vector n a) = [a]- weaken = Vector.toList+instance VG.Vector v a => Weaken (VGS.Vector v n a) where+ type Weak (VGS.Vector v n a) = [a]+ weaken = VGS.toList --- | Strip the refinement from refined types.-instance Weaken (Refined p a) where- type Weak (Refined p a) = a- weaken = unrefine+-- | Strip wrapper.+instance Weaken (Identity a) where+ type Weak (Identity a) = a+ weaken = runIdentity +-- | Strip wrapper.+instance Weaken (Const a b) where+ type Weak (Const a b) = a+ weaken = getConst++{- TODO controversial. seems logical, but also kinda annoying.+-- | Weaken 'Maybe' (0 or 1) into '[]' (0 to n).+instance Weaken (Maybe a) where+ type Weak (Maybe a) = [a]+ weaken = \case Just a -> [a]+ Nothing -> []+-}+ -- Weaken the bounded Haskell numeric types using 'fromIntegral'. instance Weaken Word8 where type Weak Word8 = Natural@@ -112,29 +135,13 @@ type Weak [a] = [Weak a] weaken = map weaken --- | Decomposer.+-- | Decomposer. Weaken both elements of a tuple. instance (Weaken a, Weaken b) => Weaken (a, b) where type Weak (a, b) = (Weak a, Weak b) weaken (a, b) = (weaken a, weaken b) --- | Decomposer.-instance Weaken a => Weaken (Maybe a) where- type Weak (Maybe a) = Maybe (Weak a)- weaken = \case Just a -> Just $ weaken a- Nothing -> Nothing---- | Decomposer.+-- | Decomposer. Weaken either side of an 'Either'. instance (Weaken a, Weaken b) => Weaken (Either a b) where type Weak (Either a b) = Either (Weak a) (Weak b) weaken = \case Left a -> Left $ weaken a Right b -> Right $ weaken b---- | Decomposer.-instance Weaken a => Weaken (Identity a) where- type Weak (Identity a) = Identity (Weak a)- weaken = Identity . weaken . runIdentity---- | Decomposer.-instance Weaken a => Weaken (Const a b) where- type Weak (Const a b) = Const (Weak a) b- weaken = Const . weaken . getConst
strongweak.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.0.+-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack name: strongweak-version: 0.3.2+version: 0.4.0 synopsis: Convert between strong and weak representations of types description: Please see README.md. category: Data