From c1c69ef3321c6c69e00fd3454896a04bb73973f9 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:02:00 +0000 Subject: [PATCH 01/10] Move sections around --- src/lib/Witch.hs | 82 ++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index 390409a..9cd38b0 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -5,7 +5,47 @@ -- -- >>> import Witch module Witch - ( -- * Motivation + ( -- * Type classes + -- ** Cast + Witch.Cast.Cast(cast) + , Witch.Utility.from + , Witch.Utility.into + + -- ** TryCast + , Witch.TryCast.TryCast(tryCast) + , Witch.Utility.tryFrom + , Witch.Utility.tryInto + , Witch.TryCastException.TryCastException(..) + + -- * Utilities + , Witch.Utility.as + , Witch.Utility.over + , Witch.Utility.via + , Witch.Utility.maybeTryCast + , Witch.Utility.eitherTryCast + , Witch.Utility.tryVia + + -- ** Unsafe + , Witch.Utility.unsafeCast + , Witch.Utility.unsafeFrom + , Witch.Utility.unsafeInto + + -- ** Template Haskell + -- | This library uses /typed/ Template Haskell, which may be a little + -- different than what you're used to. Normally Template Haskell uses the + -- @$(...)@ syntax for splicing in things to run at compile time. The typed + -- variant uses the @$$(...)@ syntax for splices, doubling up on the dollar + -- signs. Other than that, using typed Template Haskell should be pretty + -- much the same as using regular Template Haskell. + , Witch.Lift.liftedCast + , Witch.Lift.liftedFrom + , Witch.Lift.liftedInto + + -- * Data types + -- ** Casting + , Witch.Casting.Casting(Casting) + + -- * Motivation -- | Haskell provides many ways to convert between common types, and core -- libraries add even more. It can be challenging to know which function to -- use when converting from some source type @a@ to some target type @b@. It @@ -123,46 +163,6 @@ module Witch -- -- >>> from @Int8 1 :: Int16 -- 1 - - -- * Type classes - -- ** Cast - Witch.Cast.Cast(cast) - , Witch.Utility.from - , Witch.Utility.into - - -- ** TryCast - , Witch.TryCast.TryCast(tryCast) - , Witch.Utility.tryFrom - , Witch.Utility.tryInto - , Witch.TryCastException.TryCastException(..) - - -- * Utilities - , Witch.Utility.as - , Witch.Utility.over - , Witch.Utility.via - , Witch.Utility.maybeTryCast - , Witch.Utility.eitherTryCast - , Witch.Utility.tryVia - - -- ** Unsafe - , Witch.Utility.unsafeCast - , Witch.Utility.unsafeFrom - , Witch.Utility.unsafeInto - - -- ** Template Haskell - -- | This library uses /typed/ Template Haskell, which may be a little - -- different than what you're used to. Normally Template Haskell uses the - -- @$(...)@ syntax for splicing in things to run at compile time. The typed - -- variant uses the @$$(...)@ syntax for splices, doubling up on the dollar - -- signs. Other than that, using typed Template Haskell should be pretty - -- much the same as using regular Template Haskell. - , Witch.Lift.liftedCast - , Witch.Lift.liftedFrom - , Witch.Lift.liftedInto - - -- * Data types - -- ** Casting - , Witch.Casting.Casting(Casting) ) where import qualified Witch.Cast From 6f1c2e671a3c69c90d44ba379734ce200959588b Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:04:27 +0000 Subject: [PATCH 02/10] Add documentation to unsafe section --- src/lib/Witch.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index 9cd38b0..c0dc6c5 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -26,6 +26,13 @@ module Witch , Witch.Utility.tryVia -- ** Unsafe + -- | These functions should only be used in two circumstances: When you know + -- a conversion is safe even though you can't prove it to the compiler, and + -- when you're alright with your program crashing if the conversion fails. + -- In all other cases you should prefer the normal conversion functions like + -- 'Witch.Cast.cast'. And if you're converting a literal value, consider + -- using the Template Haskell conversion functions like + -- 'Witch.Lift.liftedCast'. , Witch.Utility.unsafeCast , Witch.Utility.unsafeFrom , Witch.Utility.unsafeInto From 263c86d4e88535fbabc0aa6d0c9d9f0bce7bba7e Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:05:01 +0000 Subject: [PATCH 03/10] Move `TryCastException` to data type section --- src/lib/Witch.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index c0dc6c5..14752cf 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -6,6 +6,7 @@ -- >>> import Witch module Witch ( -- * Type classes + -- ** Cast Witch.Cast.Cast(cast) , Witch.Utility.from @@ -15,7 +16,6 @@ module Witch , Witch.TryCast.TryCast(tryCast) , Witch.Utility.tryFrom , Witch.Utility.tryInto - , Witch.TryCastException.TryCastException(..) -- * Utilities , Witch.Utility.as @@ -49,6 +49,8 @@ module Witch , Witch.Lift.liftedInto -- * Data types + , Witch.TryCastException.TryCastException(..) + -- ** Casting , Witch.Casting.Casting(Casting) From 34a4636c0f439bb85694211f4c8739846933ae66 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:05:34 +0000 Subject: [PATCH 04/10] Put `tryVia` next to `via` --- src/lib/Witch.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index 14752cf..eeea239 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -21,9 +21,9 @@ module Witch , Witch.Utility.as , Witch.Utility.over , Witch.Utility.via + , Witch.Utility.tryVia , Witch.Utility.maybeTryCast , Witch.Utility.eitherTryCast - , Witch.Utility.tryVia -- ** Unsafe -- | These functions should only be used in two circumstances: When you know From ba95e3ceb2a2f5a166a3aed64f3b916599088df7 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:08:48 +0000 Subject: [PATCH 05/10] Add common usage notes --- src/lib/Witch.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index eeea239..d5234ef 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -4,6 +4,10 @@ -- unqualified, so getting started is as easy as: -- -- >>> import Witch +-- +-- In typical usage, you will most likely use 'Witch.Utility.into' for +-- 'Witch.Cast.Cast' instances and 'With.Utility.tryInto' for +-- 'Witch.TryCast.TryCast' instances. module Witch ( -- * Type classes From a154c392cdccfe52f829ceec3d83260b289ab934 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:09:44 +0000 Subject: [PATCH 06/10] Add note about `maybeTryCast` and `eitherTryCast` --- src/lib/Witch/TryCast.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/Witch/TryCast.hs b/src/lib/Witch/TryCast.hs index 5d05080..f6abc44 100644 --- a/src/lib/Witch/TryCast.hs +++ b/src/lib/Witch/TryCast.hs @@ -15,4 +15,7 @@ class TryCast source target where -- | This method implements the conversion of a value between types. At call -- sites you will usually want to use @tryFrom@ or @tryInto@ instead of this -- method. + -- + -- Consider using @maybeTryCast@ or @eitherTryCast@ to implement this + -- method. tryCast :: source -> Either (TryCastException.TryCastException source target) target From 2c5c9d69d6951544e7e1437e1187baba18a70522 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:10:31 +0000 Subject: [PATCH 07/10] Re-order the definition of `tryVia` --- src/lib/Witch/Utility.hs | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/lib/Witch/Utility.hs b/src/lib/Witch/Utility.hs index 7bad03f..db9481a 100644 --- a/src/lib/Witch/Utility.hs +++ b/src/lib/Witch/Utility.hs @@ -125,6 +125,31 @@ tryInto -> Either (TryCastException.TryCastException source target) target tryInto = TryCast.tryCast +-- | This is similar to 'via' except that it works with 'TryCast.TryCast' +-- instances instead. This function is especially convenient because juggling +-- the types in the 'TryCastException.TryCastException' can be tedious. +-- +-- > -- Avoid this: +-- > fmap (tryFrom @u) . tryInto @u +-- > +-- > -- Prefer this: +-- > tryVia @u +tryVia + :: forall u source target through + . ( Identity.Identity u ~ through + , TryCast.TryCast source through + , TryCast.TryCast through target + ) + => source + -> Either (TryCastException.TryCastException source target) target +tryVia s = case TryCast.tryCast s of + Left (TryCastException.TryCastException _ e) -> + Left $ TryCastException.TryCastException s e + Right u -> case TryCast.tryCast (u :: through) of + Left (TryCastException.TryCastException _ e) -> + Left $ TryCastException.TryCastException s e + Right t -> Right t + -- | This function can be used to implement 'TryCast.tryCast' with a function -- that returns 'Maybe'. For example: -- @@ -163,31 +188,6 @@ eitherTryCast f s = case f s of Left . TryCastException.TryCastException s . Just $ Exception.toException e Right t -> Right t --- | This is similar to 'via' except that it works with 'TryCast.TryCast' --- instances instead. This function is especially convenient because juggling --- the types in the 'TryCastException.TryCastException' can be tedious. --- --- > -- Avoid this: --- > fmap (tryFrom @u) . tryInto @u --- > --- > -- Prefer this: --- > tryVia @u -tryVia - :: forall u source target through - . ( Identity.Identity u ~ through - , TryCast.TryCast source through - , TryCast.TryCast through target - ) - => source - -> Either (TryCastException.TryCastException source target) target -tryVia s = case TryCast.tryCast s of - Left (TryCastException.TryCastException _ e) -> - Left $ TryCastException.TryCastException s e - Right u -> case TryCast.tryCast (u :: through) of - Left (TryCastException.TryCastException _ e) -> - Left $ TryCastException.TryCastException s e - Right t -> Right t - -- | This function is like 'TryCast.tryCast' except that it will throw an -- impure exception if the conversion fails. -- From a700237426af8ed91823ea7efca7b058122b6e6a Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:11:56 +0000 Subject: [PATCH 08/10] Improve `tryVia` example --- src/lib/Witch/Utility.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/Witch/Utility.hs b/src/lib/Witch/Utility.hs index db9481a..e65c4ce 100644 --- a/src/lib/Witch/Utility.hs +++ b/src/lib/Witch/Utility.hs @@ -130,7 +130,11 @@ tryInto = TryCast.tryCast -- the types in the 'TryCastException.TryCastException' can be tedious. -- -- > -- Avoid this: --- > fmap (tryFrom @u) . tryInto @u +-- > case tryInto @u x of +-- > Left _ -> Left ... +-- > Right y -> case tryFrom @u y of +-- > Left _ -> Left ... +-- > Right z -> ... -- > -- > -- Prefer this: -- > tryVia @u From 19cd609002a1a57d34511eb1a1c053d9a785a8b3 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:13:36 +0000 Subject: [PATCH 09/10] Add new top-level "Notes" section --- src/lib/Witch.hs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index d5234ef..8e41056 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -58,7 +58,9 @@ module Witch -- ** Casting , Witch.Casting.Casting(Casting) - -- * Motivation + -- * Notes + + -- ** Motivation -- | Haskell provides many ways to convert between common types, and core -- libraries add even more. It can be challenging to know which function to -- use when converting from some source type @a@ to some target type @b@. It @@ -72,7 +74,7 @@ module Witch -- by the [@From@](https://doc.rust-lang.org/std/convert/trait.From.html) -- trait in Rust. - -- * Alternatives + -- ** Alternatives -- | Many Haskell libraries already provide similar functionality. How is -- this library different? -- @@ -120,7 +122,7 @@ module Witch -- if the conversion is possible, is it safe? For example converting a -- negative 'Int' into a 'Word' will overflow, which may be surprising. - -- * Instances + -- ** Instances -- | When should you add a 'Witch.Cast.Cast' (or 'Witch.TryCast.TryCast') -- instance for some pair of types? This is a surprisingly tricky question -- to answer precisely. Instances are driven more by guidelines than rules. @@ -149,7 +151,7 @@ module Witch -- possible to convert from @s@ to @t@ but there are a lot of caveats, you -- probably should not write any instances at all. - -- * Type applications + -- ** Type applications -- | This library is designed to be used with the [@TypeApplications@](https://downloads.haskell.org/~ghc/9.0.1/docs/html/users_guide/exts/type_applications.html) -- language extension. Although it is not required for basic functionality, -- it is strongly encouraged. You can use 'Witch.Cast.cast', @@ -157,7 +159,7 @@ module Witch -- 'Witch.Lift.liftedCast' without type applications. Everything else -- requires a type application. - -- * Ambiguous types + -- ** Ambiguous types -- | You may see @Identity@ show up in some type signatures. Anywhere you see -- @Identity a@, you can mentally replace it with @a@. It is a type family -- used to trick GHC into requiring type applications for certain functions. From ce20a679cea8956704feaeefbc0630577c3e6c10 Mon Sep 17 00:00:00 2001 From: Taylor Fausak Date: Sat, 1 May 2021 19:26:15 +0000 Subject: [PATCH 10/10] Document exceptions to rules --- src/lib/Witch.hs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/Witch.hs b/src/lib/Witch.hs index 8e41056..528cff2 100644 --- a/src/lib/Witch.hs +++ b/src/lib/Witch.hs @@ -137,16 +137,28 @@ module Witch -- - Conversions should be lossless. If you have @Cast a b@ then no two @a@ -- values should be converted to the same @b@ value. -- + -- - Some conversions necessarily lose information, like converting from a + -- list into a set. + -- -- - If you have both @Cast a b@ and @Cast b a@, then -- @cast \@b \@a . cast \@a \@b@ should be the same as 'id'. In other -- words, @a@ and @b@ are isomorphic. -- + -- - This often true, but not always. For example, converting a list into + -- a set will remove duplicates. And then converting back into a list + -- will put the elements in ascending order. + -- -- - If you have both @Cast a b@ and @Cast b c@, then you could also have -- @Cast a c@ and it should be the same as @cast \@b \@c . cast \@a \@b@. -- In other words, @Cast@ is transitive. -- - -- In general if @s@ is a @t@, then you should add a 'Witch.Cast.Cast' - -- instance for it. But if @s@ merely can be a @t@, then you could add a + -- - This is not always true. For example an @Int8@ may be represented as + -- a number in JSON, whereas an @Int64@ might be represented as a + -- string. That means @into \@JSON (into \@Int64 int8)@ would not be the + -- same as @into \@JSON int8@. + -- + -- In general if @s@ /is/ a @t@, then you should add a 'Witch.Cast.Cast' + -- instance for it. But if @s@ merely /can be/ a @t@, then you could add a -- 'Witch.TryCast.TryCast' instance for it. And if it is technically -- possible to convert from @s@ to @t@ but there are a lot of caveats, you -- probably should not write any instances at all.