witch 1.2.2.0 → 1.2.3.0
raw patch · 4 files changed
+171/−3 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Witch.Generic: class GFrom s t
+ Witch.Generic: gFrom :: GFrom s t => s x -> t x
+ Witch.Generic: instance (GHC.Generics.Generic s, GHC.Generics.Generic t, Witch.Generic.GFrom (GHC.Generics.Rep s) (GHC.Generics.Rep t)) => Witch.From.From s (GHC.Generics.Generically t)
+ Witch.Generic: instance (Witch.Generic.GFrom s1 t1, Witch.Generic.GFrom s2 t2) => Witch.Generic.GFrom (s1 GHC.Generics.:*: s2) (t1 GHC.Generics.:*: t2)
+ Witch.Generic: instance (Witch.Generic.GFrom s1 t1, Witch.Generic.GFrom s2 t2) => Witch.Generic.GFrom (s1 GHC.Generics.:+: s2) (t1 GHC.Generics.:+: t2)
+ Witch.Generic: instance Witch.From.From s t => Witch.Generic.GFrom (GHC.Generics.K1 a s) (GHC.Generics.K1 b t)
+ Witch.Generic: instance Witch.Generic.GFrom GHC.Generics.U1 GHC.Generics.U1
+ Witch.Generic: instance Witch.Generic.GFrom GHC.Generics.V1 GHC.Generics.V1
+ Witch.Generic: instance Witch.Generic.GFrom s t => Witch.Generic.GFrom (GHC.Generics.M1 a b s) (GHC.Generics.M1 c d t)
Files
- source/library/Witch.hs +1/−0
- source/library/Witch/Generic.hs +82/−0
- source/test-suite/Main.hs +86/−2
- witch.cabal +2/−1
source/library/Witch.hs view
@@ -302,6 +302,7 @@ import qualified Witch.Encoding import qualified Witch.From+import Witch.Generic () import Witch.Instances () import qualified Witch.Lift import qualified Witch.TryFrom
+ source/library/Witch/Generic.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Witch.Generic where++import qualified GHC.Generics as Generics+import qualified Witch.From as From++-- | This type class is used to implement generic conversions using the 'Generics.Generically' helper.+-- This is an advanced use case.+-- Most users will not need to know about this type class.+-- And even for those that want to derive 'Generics.Generically', this type class should be an implementation detail.+--+-- This type class can convert between any two types as long as they have 'Generics.Generic' instances and they are structurally similar.+-- For example, if you define your own empty type you could convert it to the typical 'Data.Void.Void' type:+--+-- > data Empty deriving Generic+-- > deriving via Generically Void instance From Empty Void+--+-- Or your own unit type:+--+-- > data Unit = MkUnit deriving Generic+-- > deriving via Generically () instance From Unit ()+--+-- Note that this looks superficially similar to @newtype Unit = MkUnit ()@ together with @instance From Unit ()@, but that goes through 'Data.Coerce.Coercible' and requires the types to be representationally equal.+-- This approach (with 'Generics.Generically') only requires the types to be /structurally/ equal.+-- In this case, @Unit@ is structurally equal to @()@ since they both have a single constructor with no arguments.+--+-- This also works with arbitrary sum types, like a custom pair type:+--+-- > data Pair a b = MkPair a b deriving Generic+-- > deriving via Generically (Pair c d)+-- > instance (From a c, From b d) => From (a, b) (Pair c d)+--+-- Note that this can also convert the type variables as long as they have 'From.From' instances as well.+-- This allows converting from @(Int, Int)@ to @Pair Integer Integer@ in one step, for example.+--+-- And this works with arbitrary product types as well:+--+-- > data Result a b = Failure a | Success b deriving Generic+-- > deriving via Generically (Result c d)+-- > instance (From a c, From b d) => From (Either a b) (Result c d)+--+-- Note that these conversions are all /structural/ not semantic.+-- That means if you had defined @Result@ as @Success b | Failure a@, then converting from 'Either' would be "wrong".+-- 'Left' would convert into @Success@ and 'Right' would convert into @Failure@.+class GFrom s t where+ gFrom :: s x -> t x++instance GFrom Generics.V1 Generics.V1 where+ gFrom = id++instance GFrom Generics.U1 Generics.U1 where+ gFrom = id++instance (From.From s t) => GFrom (Generics.K1 a s) (Generics.K1 b t) where+ gFrom = Generics.K1 . From.from . Generics.unK1++instance (GFrom s t) => GFrom (Generics.M1 a b s) (Generics.M1 c d t) where+ gFrom = Generics.M1 . gFrom . Generics.unM1++instance (GFrom s1 t1, GFrom s2 t2) => GFrom (s1 Generics.:+: s2) (t1 Generics.:+: t2) where+ gFrom x = case x of+ Generics.L1 l -> Generics.L1 $ gFrom l+ Generics.R1 r -> Generics.R1 $ gFrom r++instance (GFrom s1 t1, GFrom s2 t2) => GFrom (s1 Generics.:*: s2) (t1 Generics.:*: t2) where+ gFrom (l Generics.:*: r) = gFrom l Generics.:*: gFrom r++-- | See the 'GFrom' type class for an explanation of this instance.+instance+ ( Generics.Generic s,+ Generics.Generic t,+ GFrom (Generics.Rep s) (Generics.Rep t)+ ) =>+ From.From s (Generics.Generically t)+ where+ from = Generics.Generically . Generics.to . gFrom . Generics.from
source/test-suite/Main.hs view
@@ -1,6 +1,9 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NegativeLiterals #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -Wno-error=overflowed-literals #-}@@ -28,7 +31,10 @@ import qualified Data.Time.Clock.POSIX as Time import qualified Data.Time.Clock.System as Time import qualified Data.Time.Clock.TAI as Time+import qualified Data.Tuple as Tuple+import qualified Data.Void as Void import qualified Data.Word as Word+import qualified GHC.Generics as Generics import qualified GHC.Stack as Stack import qualified Numeric.Natural as Natural import qualified Test.HUnit as HUnit@@ -64,7 +70,7 @@ describe "over" $ do it "works" $ do- Utility.over @Int.Int8 (+ 1) (Age 1) `shouldBe` Age 2+ Utility.over @Int.Int8 (+ 1) (MkAge 1) `shouldBe` MkAge 2 describe "via" $ do it "works" $ do@@ -2439,8 +2445,86 @@ it "works" $ do f "a" `shouldBe` Tagged.Tagged (LazyByteString.pack [0x00, 0x00, 0x00, 0x61]) + describe "Generically" $ do+ it "converts into empty" $ do+ -- This only needs to type check.+ let _ = Witch.from @Void.Void @Empty+ pure ()++ it "converts into unit" $ do+ let f = Witch.from @() @Unit+ f () `shouldBe` MkUnit++ it "converts into only" $ do+ let f = Witch.from @(Tuple.Solo Int) @(Only Integer)+ f (Tuple.MkSolo 1) `shouldBe` MkOnly 1++ it "converts into pair" $ do+ let f = Witch.from @(Int, Int.Int8) @(Pair Integer Age)+ f (1, 2) `shouldBe` MkPair 1 (MkAge 2)++ it "converts into result" $ do+ let f = Witch.from @(Either Int Int.Int8) @(Result Integer Age)+ f (Left 1) `shouldBe` Failure 1+ f (Right 2) `shouldBe` Success (MkAge 2)++ it "converts into list" $ do+ let f = Witch.from @[Int] @(List Integer)+ f [] `shouldBe` Nil+ f [1] `shouldBe` Cons 1 Nil+ f [1, 2] `shouldBe` Cons 1 (Cons 2 Nil)++data Empty+ deriving (Generics.Generic)++deriving via Generics.Generically Empty instance Witch.From Void.Void Empty++data Unit+ = MkUnit+ deriving (Eq, Generics.Generic, Show)++deriving via Generics.Generically Unit instance Witch.From () Unit++newtype Only a+ = MkOnly a+ deriving (Eq, Generics.Generic, Show)++deriving via+ Generics.Generically (Only b)+ instance+ (Witch.From a b) => Witch.From (Tuple.Solo a) (Only b)++data Pair a b+ = MkPair a b+ deriving (Eq, Generics.Generic, Show)++deriving via+ Generics.Generically (Pair c d)+ instance+ (Witch.From a c, Witch.From b d) => Witch.From (a, b) (Pair c d)++data Result a b+ = Failure a+ | Success b+ deriving (Eq, Generics.Generic, Show)++deriving via+ Generics.Generically (Result c d)+ instance+ (Witch.From a c, Witch.From b d) => Witch.From (Either a b) (Result c d)++data List a+ = Nil+ | Cons a (List a)+ deriving (Eq, Generics.Generic, Show)++deriving via+ Generics.Generically (List b)+ instance+ (Witch.From a b) => Witch.From [a] (List b)+ newtype Age- = Age Int.Int8+ = MkAge Int.Int8 deriving (Eq, Show) instance Witch.From Age Int.Int8
witch.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: witch-version: 1.2.2.0+version: 1.2.3.0 synopsis: Convert values from one type into another. description: Witch converts values from one type into another. build-type: Simple@@ -70,6 +70,7 @@ Witch Witch.Encoding Witch.From+ Witch.Generic Witch.Instances Witch.Lift Witch.TryFrom