packages feed

strongweak 0.3.2 → 0.4.0

raw patch · 6 files changed

+117/−91 lines, 6 files

Files

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