strongweak 0.7.1 → 0.8.0
raw patch · 9 files changed
+92/−104 lines, 9 filesdep −eitherdep ~text
Dependencies removed: either
Dependency ranges changed: text
Files
- CHANGELOG.md +6/−0
- LICENSE +1/−1
- src/Strongweak/Strengthen.hs +50/−37
- src/Strongweak/Strengthen/Generic.hs +25/−35
- src/Strongweak/Util/Text.hs +0/−7
- src/Strongweak/Util/Typeable.hs +0/−8
- strongweak.cabal +5/−9
- test/Common.hs +3/−4
- test/Strongweak/LawsSpec.hs +2/−3
CHANGELOG.md view
@@ -1,3 +1,9 @@+## 0.8.0 (2024-06-11)+* no longer use `Validation`: failures now must wrap explicitly instead of being+ implicitly collated+* fix some bounds (`text` lower bound was too low)+* various tweaks+ ## 0.7.1 (2024-05-27) * bump meta/dependencies
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2022 Ben Orchard (@raehik) <thefirstmuffinman@gmail.com>+Copyright (c) 2022-2024 Ben Orchard (@raehik) <thefirstmuffinman@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
src/Strongweak/Strengthen.hs view
@@ -6,13 +6,13 @@ -- * 'Strengthen' class Strengthen(..) , restrengthen- , Result -- ** Helpers , strengthenBounded -- * Strengthen failures , StrengthenFailure(..)+ , StrengthenFailure' , failStrengthen1 , failStrengthen @@ -20,11 +20,8 @@ , Strongweak.Weaken.Weak ) where -import Strongweak.Util.Typeable ( typeRep' ) import Strongweak.Util.TypeNats ( natVal'' ) import Strongweak.Weaken ( Weaken(..) )-import Data.Either.Validation-import Data.Typeable ( Typeable ) import GHC.TypeNats ( KnownNat ) import Data.Word@@ -42,7 +39,7 @@ import Data.Bits ( FiniteBits ) -type Result = Validation StrengthenFailure+import Data.Typeable ( Typeable, TypeRep, typeRep, Proxy(Proxy) ) {- | Attempt to strengthen some @'Weak' a@, asserting certain invariants. @@ -57,7 +54,7 @@ class Weaken a => Strengthen a where -- | Attempt to strengthen some @'Weak' a@ to its associated strong type -- @a@.- strengthen :: Weak a -> Result a+ strengthen :: Weak a -> Either StrengthenFailure' a -- | Weaken a strong value, then strengthen it again. --@@ -66,35 +63,48 @@ -- invariants. For example: -- -- >>> restrengthen $ unsafeStrengthen @(Vector 2 Natural) [0]--- Failure ...-restrengthen- :: (Strengthen a, Weaken a)- => a -> Result a+-- Left ...+restrengthen :: (Strengthen a, Weaken a) => a -> Either StrengthenFailure' a restrengthen = strengthen . weaken -- | A failure encountered during strengthening.-data StrengthenFailure = StrengthenFailure- { strengthenFailDetail :: [TBL.Builder]+--+-- Strengthening can involve multiple distinct checks. In such cases, you may+-- record multiple failures in a single 'StrengthenFailure' by placing them in+-- the inner failure list and noting their meaning in the detail field.+data StrengthenFailure text = StrengthenFailure+ { strengthenFailDetail :: [text] -- ^ Detail on strengthen failure. -- -- We use a list here for the cases where you want multiple lines of detail. -- Separating with a newline would make prettifying later harder, so we delay.+ --+ -- Note that this should probably never be empty. TODO consider @NonEmpty@,+ -- but fairly unimportant. - , strengthenFailInner :: [(TBL.Builder, StrengthenFailure)]- -- ^ Indexed.+ , strengthenFailInner :: [(text, StrengthenFailure text)]+ -- ^ Optional wrapped failures.+ --+ -- The @text@ type acts as an index. Its meaning depends on the failure+ -- in question, and should be explained in 'strengthenFailDetail'. } deriving stock Show +type StrengthenFailure' = StrengthenFailure TBL.Builder++-- | Shorthand for failing a strengthen. failStrengthen- :: [TBL.Builder] -> [(TBL.Builder, StrengthenFailure)] -> Result a-failStrengthen t fs = Failure $ StrengthenFailure t fs+ :: [text] -> [(text, StrengthenFailure text)]+ -> Either (StrengthenFailure text) a+failStrengthen t fs = Left $ StrengthenFailure t fs -failStrengthen1 :: [TBL.Builder] -> Result a+-- | Shorthand for failing a strengthen with no inner failures.+failStrengthen1 :: [text] -> Either (StrengthenFailure text) a failStrengthen1 t = failStrengthen t [] -- | Strengthen a type by refining it with a predicate. instance Refine p a => Strengthen (Refined p a) where strengthen = refine .> \case- Right ra -> Success ra+ Right ra -> Right ra Left rf -> failStrengthen1 [ "refinement failure:" , TBL.fromText (prettyRefineFailure rf) ]@@ -103,7 +113,7 @@ -- | Strengthen a type by refining it with a functor predicate. instance Refine1 p f => Strengthen (Refined1 p f a) where strengthen = refine1 .> \case- Right ra -> Success ra+ Right ra -> Right ra Left rf -> failStrengthen1 [ "refinement failure:" , TBL.fromText (prettyRefineFailure rf) ]@@ -111,7 +121,7 @@ -- | Strengthen a plain list into a non-empty list by asserting non-emptiness. instance Strengthen (NonEmpty a) where strengthen = NonEmpty.nonEmpty .> \case- Just neas -> Success neas+ Just neas -> Right neas Nothing -> failStrengthen1 $ [ "type: [a] -> NonEmpty a" , "fail: empty list" ]@@ -121,7 +131,7 @@ -- TODO another case of TBL not supporting unbounded integrals strengthen as = case VGS.fromList as of- Just va -> Success va+ Just va -> Right va Nothing -> failStrengthen1 $ [ "type: [a] -> Vector v "<>fromString (show n)<>" a" , "fail: wrong length (got "<>TBL.fromDec (length as)<>")" ]@@ -129,11 +139,11 @@ -- | Add wrapper. instance Strengthen (Identity a) where- strengthen = Success . Identity+ strengthen = Right . Identity -- | Add wrapper. instance Strengthen (Const a b) where- strengthen = Success . Const+ strengthen = Right . Const {- TODO controversial. seems logical, but also kinda annoying. instance (Show a, Typeable a) => Strengthen (Maybe a) where@@ -165,9 +175,9 @@ :: forall m n . ( Typeable n, Integral n, Show n , Typeable m, Integral m, Bounded m, FiniteBits m- ) => n -> Result m+ ) => n -> Either StrengthenFailure' m strengthenBounded n- | n <= maxBn && n >= minBn = Success (fromIntegral n)+ | n <= maxBn && n >= minBn = Right (fromIntegral n) | otherwise = failStrengthen1 [ "numeric strengthen: "<>fromString (show (typeRep' @n)) <>" -> "<>fromString (show (typeRep' @m))@@ -187,36 +197,36 @@ instance Strengthen a => Strengthen [a] where strengthen = strengthenList -strengthenList :: Strengthen a => [Weak a] -> Result [a]+strengthenList :: Strengthen a => [Weak a] -> Either StrengthenFailure' [a] strengthenList = goR (0 :: Int) [] . map strengthen where goR i as = \case r:rs -> case r of- Success a -> goR (i+1) (a:as) rs- Failure e -> goL (i+1) [(TBL.fromDec i, e)] rs- [] -> Success as+ Right a -> goR (i+1) (a:as) rs+ Left e -> goL (i+1) [(TBL.fromDec i, e)] rs+ [] -> Right as goL i es = \case r:rs -> case r of- Success _ -> goL (i+1) es rs- Failure e -> goL (i+1) ((TBL.fromDec i, e):es) rs+ Right _ -> goL (i+1) es rs+ Left e -> goL (i+1) ((TBL.fromDec i, e):es) rs [] -> failStrengthen ["list had failures"] es -- | Decomposer. Strengthen both elements of a tuple. instance (Strengthen l, Strengthen r) => Strengthen (l, r) where strengthen (l, r) = case strengthen l of- Success sl ->+ Right sl -> case strengthen r of- Success sr -> Success (sl, sr)- Failure er -> failStrengthen ["2-tuple: right failed"]+ Right sr -> Right (sl, sr)+ Left er -> failStrengthen ["2-tuple: right failed"] [("R", er)]- Failure el ->+ Left el -> case strengthen @r r of- Success _ -> failStrengthen ["2-tuple: left failed"]+ Right _ -> failStrengthen ["2-tuple: left failed"] [("L", el)]- Failure er -> failStrengthen ["2-tuple: l&r failed"]+ Left er -> failStrengthen ["2-tuple: l&r failed"] [("R", er), ("L", el)] -- | Decomposer. Strengthen either side of an 'Either'.@@ -229,3 +239,6 @@ -- from flow (.>) :: (a -> b) -> (b -> c) -> a -> c f .> g = g . f++typeRep' :: forall a. Typeable a => TypeRep+typeRep' = typeRep (Proxy @a)
src/Strongweak/Strengthen/Generic.hs view
@@ -17,46 +17,28 @@ module Strongweak.Strengthen.Generic where +import Data.Kind ( type Constraint ) import Strongweak.Strengthen import GHC.Generics-import Data.Either.Validation import Data.Kind ( Type ) import GHC.TypeNats ( Natural, type (+), KnownNat )-import Control.Applicative qualified as A -- liftA2 export workaround import Strongweak.Util.TypeNats ( natVal'' ) import Data.Text.Builder.Linear qualified as TBL import GHC.Exts ( Symbol, fromString, proxy# ) import GHC.TypeLits ( KnownSymbol, symbolVal' ) -{- TODO-So, now that we're improving the error story, we can do so here as well.--At product level in these generics, we know that neither data type names or-constructor names (weak or strong) will change. So individual fields can simply-annotate themselves with the weak & strong field identifiers. Then those can get-wrapped into a nice clean error higher up, that says "this constructor had the-following errors".--It's gonna look like the tuple and list 'Strengthen' instances but worse. Lots-of fiddly stuff.--Also, we can do the data type equality check I noted earlier. If weak & strong-data type names/constructor names match, we're probably doing @SW@ tricks, and-could probably shorten the error a bit.--}- -- | Strengthen a value generically. -- -- The weak and strong types must be /compatible/. See 'Strongweak.Generic' for -- the definition of compatibility in this context. strengthenGeneric :: (Generic w, Generic s, GStrengthenD (Rep w) (Rep s))- => w -> Result s+ => w -> Either StrengthenFailure' s strengthenGeneric = fmap to . gstrengthenD . from -- | Generic strengthening at the datatype level. class GStrengthenD w s where- gstrengthenD :: w p -> Result (s p)+ gstrengthenD :: w p -> Either StrengthenFailure' (s p) -- | Strengthen a generic data type, replacing its metadata wrapping. instance GStrengthenC wdn sdn w s@@ -67,10 +49,10 @@ -- | Generic strengthening at the constructor sum level. class GStrengthenC (wdn :: Symbol) (sdn :: Symbol) w s where- gstrengthenC :: w p -> Result (s p)+ gstrengthenC :: w p -> Either StrengthenFailure' (s p) -- | Nothing to do for empty datatypes.-instance GStrengthenC wdn sdn V1 V1 where gstrengthenC = Success+instance GStrengthenC wdn sdn V1 V1 where gstrengthenC = Right -- | Strengthen sum types by casing and strengthening left or right. instance@@ -87,11 +69,11 @@ (C1 (MetaCons scn _smc2 _smc3) s) where gstrengthenC (M1 w) = case gstrengthenS @0 w of- Success s -> Success (M1 s)- Failure es -> failStrengthen [reifyCstrs @wcd @wcn @scd @scn] es+ Right s -> Right (M1 s)+ Left e -> failStrengthen [reifyCstrs @wcd @wcn @scd @scn] e -class ReifyCstrs (ld :: Symbol) (lc :: Symbol) (rd :: Symbol) (rc :: Symbol) where- reifyCstrs :: TBL.Builder+type ReifyCstrs :: Symbol -> Symbol -> Symbol -> Symbol -> Constraint+class ReifyCstrs ld lc rd rc where reifyCstrs :: TBL.Builder -- | Special case: data type and constructor names are equivalent: simplify instance {-# OVERLAPPING #-} (KnownSymbol d, KnownSymbol c)@@ -114,10 +96,10 @@ -- | Generic strengthening at the constructor level. class GStrengthenS (i :: Natural) w s where- gstrengthenS :: w p -> Validation [(TBL.Builder, StrengthenFailure)] (s p)+ gstrengthenS :: w p -> Either [(TBL.Builder, StrengthenFailure')] (s p) -- | Nothing to do for empty constructors.-instance GStrengthenS i U1 U1 where gstrengthenS = Success+instance GStrengthenS i U1 U1 where gstrengthenS = Right -- | Strengthen product types by strengthening left and right. instance@@ -125,9 +107,17 @@ , GStrengthenS (i + ProdArity wl) wr sr ) => GStrengthenS i (wl :*: wr) (sl :*: sr) where gstrengthenS (l :*: r) =- A.liftA2 (:*:)- (gstrengthenS @i l)- (gstrengthenS @(i + ProdArity wl) r)+ -- With @Validation@, @A.liftA2 (:*:)@. But I don't have a use for it+ -- elsewhere, so let's just be explicit.+ case gstrengthenS @i l of+ Right la ->+ case gstrengthenS @(i + ProdArity wl) r of+ Right ra -> Right (la :*: ra)+ Left re -> Left re+ Left le ->+ case gstrengthenS @(i + ProdArity wl) @_ @sr r of+ Right _ra -> Left le+ Left re -> Left (le <> re) -- | Special case: if source and target types are equivalent, just replace meta. --@@ -137,7 +127,7 @@ instance {-# OVERLAPPING #-} GStrengthenS i (S1 (MetaSel _wms1 _wms2 _wms3 _wms4) (Rec0 a)) (S1 (MetaSel _sms1 _sms2 _sms3 _sms4) (Rec0 a)) where- gstrengthenS = Success . M1 . unM1+ gstrengthenS = Right . M1 . unM1 -- | Strengthen a field using the existing 'Strengthen' instance. instance@@ -148,8 +138,8 @@ (S1 (MetaSel wmr _wms2 _wms3 _wms4) (Rec0 w)) (S1 (MetaSel smr _sms2 _sms3 _sms4) (Rec0 s)) where gstrengthenS = unM1 .> unK1 .> strengthen .> \case- Success s -> Success $ M1 $ K1 s- Failure e -> Failure [(reifySelector @i @wmr @smr, e)]+ Right s -> Right $ M1 $ K1 s+ Left e -> Left [(reifySelector @i @wmr @smr, e)] {- TODO * how to separate index and record name? @.@ is good and bad, uses same syntax
− src/Strongweak/Util/Text.hs
@@ -1,7 +0,0 @@-module Strongweak.Util.Text where--import Data.Text qualified as Text-import Data.Text ( Text )--tshow :: Show a => a -> Text-tshow = Text.pack . show
− src/Strongweak/Util/Typeable.hs
@@ -1,8 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}--module Strongweak.Util.Typeable where--import Data.Typeable--typeRep' :: forall a. Typeable a => TypeRep-typeRep' = typeRep (Proxy @a)
strongweak.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: strongweak-version: 0.7.1+version: 0.8.0 synopsis: Convert between strong and weak representations of types description: Please see README.md. category: Data@@ -34,8 +34,6 @@ Strongweak.Strengthen Strongweak.Strengthen.Generic Strongweak.Strengthen.Unsafe- Strongweak.Util.Text- Strongweak.Util.Typeable Strongweak.Util.TypeNats Strongweak.Weaken Strongweak.Weaken.Generic@@ -57,12 +55,11 @@ ghc-options: -Wall -Wno-unticked-promoted-constructors build-depends: base >=4.18 && <5- , either >=5.0.1.1 && <5.1 , rerefined >=0.5.0 && <0.6- , text >=1.2.5.0 && <2.2+ , text >=2.0 && <2.2 , text-builder-linear >=0.1.2 && <0.2 , vector >=0.12.3.1 && <0.14- , vector-sized >=1.5.0 && <1.6+ , vector-sized >=1.5.0 && <1.7 default-language: GHC2021 test-suite spec@@ -91,14 +88,13 @@ build-depends: QuickCheck >=2.14.2 && <2.16 , base >=4.18 && <5- , either >=5.0.1.1 && <5.1 , generic-random >=1.5.0.1 && <1.6 , hspec >=2.7 && <2.12 , quickcheck-instances >=0.3.26 && <0.4 , rerefined >=0.5.0 && <0.6 , strongweak- , text >=1.2.5.0 && <2.2+ , text >=2.0 && <2.2 , text-builder-linear >=0.1.2 && <0.2 , vector >=0.12.3.1 && <0.14- , vector-sized >=1.5.0 && <1.6+ , vector-sized >=1.5.0 && <1.7 default-language: GHC2021
test/Common.hs view
@@ -1,7 +1,7 @@ module Common where import Strongweak-import Strongweak.Strengthen qualified as Strengthen+import Strongweak.Strengthen import Strongweak.Generic import Rerefined import Rerefined.Predicates@@ -11,7 +11,6 @@ import Numeric.Natural ( Natural ) import Data.Word import Test.QuickCheck.Instances.Natural()-import Data.Either.Validation data DS (s :: Strength) = DS0 (SW s Word8) (SW s Word8) Word8 (SW s Word8) (SW s Word8)@@ -52,5 +51,5 @@ weaken = weakenGeneric instance Strengthen (DP 'Strong) where strengthen = strengthenGeneric -tryStrengthenSuccessEq :: Eq a => a -> Strengthen.Result a -> Bool-tryStrengthenSuccessEq a = \case Success a' -> a == a'; Failure{} -> False+tryStrengthenSuccessEq :: Eq a => a -> Either StrengthenFailure' a -> Bool+tryStrengthenSuccessEq a = \case Right a' -> a == a'; Left{} -> False
test/Strongweak/LawsSpec.hs view
@@ -2,7 +2,6 @@ import Strongweak import Common-import Data.Either.Validation import Test.Hspec import Test.Hspec.QuickCheck @@ -14,6 +13,6 @@ prop "strengthen-weaken-strengthen roundtrip partial isomorphism (generic)" $ do \(dw :: DS 'Weak) -> case strengthen @(DS 'Strong) dw of- Failure{} -> pure ()- Success ds ->+ Right ds -> strengthen (weaken ds) `shouldSatisfy` tryStrengthenSuccessEq ds+ Left{} -> pure ()