packages feed

witch (empty) → 0.0.0.0

raw patch · 5 files changed

+526/−0 lines, 5 filesdep +QuickCheckdep +basedep +bytestring

Dependencies added: QuickCheck, base, bytestring, containers, hspec, text, witch

Files

+ LICENSE.txt view
@@ -0,0 +1,13 @@+Copyright 2020 Taylor Fausak++Permission to use, copy, modify, and/or distribute this software for any+purpose with or without fee is hereby granted, provided that the above+copyright notice and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR+PERFORMANCE OF THIS SOFTWARE.
+ README.markdown view
@@ -0,0 +1,3 @@+# Witch++:mage_woman: Convert values from one type into another.
+ src/lib/Witch.hs view
@@ -0,0 +1,298 @@+{-# language AllowAmbiguousTypes #-}+{-# language DefaultSignatures #-}+{-# language FlexibleInstances #-}+{-# language MultiParamTypeClasses #-}+{-# language ScopedTypeVariables #-}++-- | This module provides the 'From' type class for converting values between+-- various types. This aims to be a common interface for the various @xToY@ or+-- @yFromX@ functions you might write instead. It is inspired by the+-- @std::convert::From@ trait that the Rust programming language provides.+--+-- Many Haskell libraries already provide similar functionality. Here's how+-- this module compares to them:+--+-- - <https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Coerce.html>:+--   This type class is convenient because it's automatically inferred by the+--   compiler, but it only works for types that have the same runtime+--   representation.+--+-- - <https://hackage.haskell.org/package/convertible-1.1.1.0/docs/Data-Convertible-Base.html>:+--   This type class allows for conversions to fail.+--+-- - <https://hackage.haskell.org/package/basement-0.0.11/docs/Basement-From.html>:+--   This type class is essentially the same, but the @basement@ package is an+--   alternative standard library that some people may not want to depend on.+--+-- - <https://hackage.haskell.org/package/inj-base-0.2.0.0/docs/Inj-Base.html>:+--   This type class requires conversions to be injective, as opposed to merely+--   suggesting it. Also some conversions fail at runtime.+--+-- - <https://github.com/mbj/conversions/blob/6ac6c52/src/Data/Conversions/FromType.hs>:+--   This type class comes with many convenient helper functions, but some of+--   the provided instances fail at runtime.+--+-- - <https://github.com/kframework/kore/blob/626f230/kore/src/From.hs>:+--   This package is not available on Hackage, but otherwise is very similar to+--   this one.+module Witch (From(from), into, via) where++import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Coerce as Coerce+import qualified Data.Foldable as Foldable+import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as Map+import qualified Data.Sequence as Seq+import qualified Data.Set as Set+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LazyText+import qualified Data.Tuple as Tuple+import qualified Data.Void as Void+import qualified Data.Word as Word+import qualified Numeric.Natural as Natural++-- | This type class represents a way to convert values from some type into+-- another type. The constraint @From a b@ means that you can convert from a+-- value of type @a@ into a value of type @b@.+--+-- This is primarily intended for "zero cost" conversions like @newtype@s. For+-- example if you wanted to have a type to represent someone's name, you could+-- say:+--+-- > newtype Name = Name String+-- > instance From String Name+-- > instance From Name String+--+-- And then you could convert back and forth between @Name@s and @String@s:+--+-- > let someString = "Taylor"+-- > let someName = Name someString+-- > into @Name someString -- convert from string to name+-- > into @String someName -- convert from name to string+--+-- This type class does not have any laws, but it does have some expectations:+--+-- - Conversions should be total. A conversion should never fail or crash.+--   Avoid writing instances like @From Int (Maybe Char)@. (It might be+--   worthwhile to have a separate @TryFrom@ type class for this.)+--+-- - Conversions should be unambiguous. For example there are many ways to+--   decode a @ByteString@ into @Text@, so you shouldn't provide an instance+--   for that.+--+-- - Conversions should be cheap, ideally free. For example converting from+--   @String@ to @Text@ is probably fine, but converting from a UTF-8 encoded+--   @ByteString@ to @Text@ is problematic.+--+-- - Conversions should be lossless. In other words if you have @From a b@ then+--   no two @a@ values should be converted to the same @b@ value. For example+--   @From Int Integer@ is fine because every @Int@ can be mapped to a+--   corresponding @Integer@, but @From Integer Int@ is not good because some+--   @Integer@s are out of bounds and would need to be clamped.+--+-- - If you have both @From a b@ and @From b a@, then @from . from@ should be+--   the same as 'id'. In other words @a@ and @b@ are isomorphic.+--+-- - If you have both @From a b@ and @From b c@, then it's up to you if you+--   want to provide @From a c@. Sometimes using 'via' is ergonomic enough,+--   other times you want the extra instance. (It would be nice if we could+--   provide @instance (From a b, From b c) => From a c where from = via \@b@.)+class From a b where+  -- | This method converts a value from one type into another. This is+  -- intended to be used with the @TypeApplications@ language extension. For+  -- example, here are a few ways to convert from an 'Int' into an 'Integer':+  --+  -- > from @Int @Integer 123+  -- > from @_ @Integer (123 :: Int)+  -- > from @Int @_ 123 :: Integer+  -- > from @Int 123 :: Integer+  -- > from (123 :: Int) :: Integer+  --+  -- Often the context around an expression will make the explicit type+  -- signatures unnecessary. If you find yourself using a partial type+  -- signature, consider using 'into' instead. For example:+  --+  -- > let someInt = 123 :: Int+  -- > from @_ @Integer someInt -- avoid this+  -- > into @Integer someInt -- prefer this+  --+  -- The default implementation of 'from' simply calls 'Coerce.coerce', which+  -- works for types that have the same runtime representation.+  from :: a -> b+  default from :: Coerce.Coercible a b => a -> b+  from = Coerce.coerce++-- | This function converts a value from one type into another. This is the+-- same as 'from' except that the type variables are in the opposite order.+into :: forall b a . From a b => a -> b+into = from++-- | This function converts a value from one type into another by going through+-- some third type. This is the same as calling 'from' (or 'into') twice, but+-- can sometimes be more convenient.+--+-- Note that the type in the middle of the conversion is the first type+-- variable of this function. In other words, @via \@b \@a \@c@ first converts+-- from @a@ to @b@, and then from @b@ to @c@. Often both @a@ and @c@ will be+-- inferred from context, which means you can just write @via \@b@.+via :: forall b a c . (From a b, From b c) => a -> c+via = from . (\ x -> x :: b) . from++-- | 'id'+instance From a a where+  from = id++-- | 'const'+instance From a (x -> a) where+  from = const++-- | 'pure'+instance From a [a] where+  from = pure++-- | 'Just'+instance From a (Maybe a) where+  from = Just++-- | 'Left'+instance From a (Either a x) where+  from = Left++-- | 'Right'+instance From a (Either x a) where+  from = Right++-- | 'Void.absurd'+instance From Void.Void x where+  from = Void.absurd++-- | 'fst'+instance From (a, x) a where+  from = fst++-- | 'snd'+instance From (x, a) a where+  from = snd++-- | 'Tuple.swap'+instance From (a, b) (b, a) where+  from = Tuple.swap++-- | 'NonEmpty.toList'+instance From (NonEmpty.NonEmpty a) [a] where+  from = NonEmpty.toList++-- | 'fromIntegral'+instance From Word.Word8 Word.Word16 where+  from = fromIntegral++-- | 'fromIntegral'+instance From Word.Word16 Word.Word32 where+  from = fromIntegral++-- | 'fromIntegral'+instance From Word.Word32 Word.Word64 where+  from = fromIntegral++-- | 'fromIntegral'+instance From Word Natural.Natural where+  from = fromIntegral++-- | 'fromIntegral'+instance From Natural.Natural Integer where+  from = fromIntegral++-- | 'fromIntegral'+instance From Int.Int8 Int.Int16 where+  from = fromIntegral++-- | 'fromIntegral'+instance From Int.Int16 Int.Int32 where+  from = fromIntegral++-- | 'fromIntegral'+instance From Int.Int32 Int.Int64 where+  from = fromIntegral++-- | 'fromIntegral'+instance From Int Integer where+  from = fromIntegral++-- | 'fromIntegral'+instance From Integer Rational where+  from = fromIntegral++-- | 'realToFrac'+instance From Float Double where+  from = realToFrac++-- | 'fromEnum'+instance From Bool Int where+  from = fromEnum++-- | 'fromEnum'+instance From Char Int where+  from = fromEnum++-- | 'ByteString.pack'+instance From [Word.Word8] ByteString.ByteString where+  from = ByteString.pack++-- | 'ByteString.unpack'+instance From ByteString.ByteString [Word.Word8] where+  from = ByteString.unpack++-- | 'LazyByteString.fromStrict'+instance From ByteString.ByteString LazyByteString.ByteString where+  from = LazyByteString.fromStrict++-- | 'LazyByteString.toStrict'+instance From LazyByteString.ByteString ByteString.ByteString where+  from = LazyByteString.toStrict++-- | 'Text.pack'+instance From String Text.Text where+  from = Text.pack++-- | 'Text.unpack'+instance From Text.Text String where+  from = Text.unpack++-- | 'LazyText.fromStrict'+instance From Text.Text LazyText.Text where+  from = LazyText.fromStrict++-- | 'LazyText.toStrict'+instance From LazyText.Text Text.Text where+  from = LazyText.toStrict++-- | 'Seq.fromList'+instance From [a] (Seq.Seq a) where+  from = Seq.fromList++-- | 'Foldable.toList'+instance From (Seq.Seq a) [a] where+  from = Foldable.toList++-- | 'Set.fromList'+--+-- Note that this will remove duplicate elements from the list.+instance Ord a => From [a] (Set.Set a) where+  from = Set.fromList++-- | 'Set.toAscList'+instance From (Set.Set a) [a] where+  from = Set.toAscList++-- | 'Map.fromList'+--+-- Note that if there are duplicate keys in the list, the one closest to the+-- end will win.+instance Ord k => From [(k, v)] (Map.Map k v) where+  from = Map.fromList++-- | 'Map.toAscList'+instance From (Map.Map k v) [(k, v)] where+  from = Map.toAscList
+ src/test/Main.hs view
@@ -0,0 +1,161 @@+{-# language TypeApplications #-}++import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import qualified Data.Foldable as Foldable+import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as Map+import qualified Data.Sequence as Seq+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified Data.Text.Lazy as LT+import qualified Data.Tuple as Tuple+import qualified Data.Word as Word+import qualified Numeric.Natural as Natural+import qualified Test.Hspec as Hspec+import qualified Test.Hspec.QuickCheck as Hspec+import qualified Test.QuickCheck as QuickCheck+import qualified Witch++main :: IO ()+main = Hspec.hspec . Hspec.parallel $ do++  Hspec.describe "From a a" $ do+    Hspec.prop "from" $ \ x -> Witch.from @Int @Int x == x+    Hspec.prop "into" $ \ x -> Witch.into @Int @Int x == x+    Hspec.prop "via" $ \ x -> Witch.via @Int @Int @Int x == x++  Hspec.prop "From a (x -> a)" $ \ x ->+    Witch.from @Int @(() -> Int) x () == x++  Hspec.prop "From a [a]" $ \ x ->+    Witch.from @Int @[Int] x == [x]++  Hspec.prop "From a (Maybe a)" $ \ x ->+    Witch.from @Int @(Maybe Int) x == Just x++  Hspec.prop "From a (Either a x)" $ \ x ->+    Witch.from @Int @(Either Int ()) x == Left x++  Hspec.prop "From a (Either x a)" $ \ x ->+    Witch.from @Int @(Either () Int) x == Right x++  Hspec.it "From Void x" $ Hspec.pending++  Hspec.prop "From (a, x) a" $ \ x ->+    Witch.from @(Int, ()) @Int (x, ()) == x++  Hspec.prop "From (x, a) a" $ \ x ->+    Witch.from @((), Int) @Int ((), x) == x++  Hspec.prop "From (a, b) (b, a)" $ \ x ->+    Witch.from @(Char, Int) @(Int, Char) x == Tuple.swap x++  Hspec.prop "From (NonEmpty a) [a]" $ \ x ->+    Witch.from @(NonEmpty.NonEmpty Int) @[Int] x == NonEmpty.toList x++  Hspec.prop "From Word8 Word16" $ \ x ->+    Witch.from @Word.Word8 @Word.Word16 x == fromIntegral x++  Hspec.prop "From Word16 Word32" $ \ x ->+    Witch.from @Word.Word16 @Word.Word32 x == fromIntegral x++  Hspec.prop "From Word32 Word64" $ \ x ->+    Witch.from @Word.Word32 @Word.Word64 x == fromIntegral x++  Hspec.prop "From Word Natural" $ \ x ->+    Witch.from @Word @Natural.Natural x == fromIntegral x++  Hspec.prop "From Natural Integer" $ \ x ->+    Witch.from @Natural.Natural @Integer x == fromIntegral x++  Hspec.prop "From Int8 Int16" $ \ x ->+    Witch.from @Int.Int8 @Int.Int16 x == fromIntegral x++  Hspec.prop "From Int16 Int32" $ \ x ->+    Witch.from @Int.Int16 @Int.Int32 x == fromIntegral x++  Hspec.prop "From Int32 Int64" $ \ x ->+    Witch.from @Int.Int32 @Int.Int64 x == fromIntegral x++  Hspec.prop "From Int Integer" $ \ x ->+    Witch.from @Int @Integer x == fromIntegral x++  Hspec.prop "From Integer Rational" $ \ x ->+    Witch.from @Integer @Rational x == fromIntegral x++  Hspec.prop "From Float Double" $ \ x ->+    Witch.from @Float @Double x == realToFrac x++  Hspec.prop "From Bool Int" $ \ x ->+    Witch.from @Bool @Int x == fromEnum x++  Hspec.prop "From Char Int" $ \ x ->+    Witch.from @Char @Int x == fromEnum x++  Hspec.prop "From [Word8] ByteString" $ \ x ->+    Witch.from @[Word.Word8] @B.ByteString x == B.pack x++  Hspec.prop "From ByteString [Word8]" $ \ x ->+    Witch.from @B.ByteString @[Word.Word8] x == B.unpack x++  Hspec.prop "From ByteString LazyByteString" $ \ x ->+    Witch.from @B.ByteString @LB.ByteString x == LB.fromStrict x++  Hspec.prop "From LazyByteString ByteString" $ \ x ->+    Witch.from @LB.ByteString @B.ByteString x == LB.toStrict x++  Hspec.prop "From String Text" $ \ x ->+    Witch.from @String @T.Text x == T.pack x++  Hspec.prop "From Text String" $ \ x ->+    Witch.from @T.Text @String x == T.unpack x++  Hspec.prop "From Text LazyText" $ \ x ->+    Witch.from @T.Text @LT.Text x == LT.fromStrict x++  Hspec.prop "From LazyText Text" $ \ x ->+    Witch.from @LT.Text @T.Text x == LT.toStrict x++  Hspec.prop "From [a] (Seq a)" $ \ x ->+    Witch.from @[Int] @(Seq.Seq Int) x == Seq.fromList x++  Hspec.prop "From (Seq a) [a]" $ \ x ->+    Witch.from @(Seq.Seq Int) @[Int] x == Foldable.toList x++  Hspec.prop "From [a] (Set a)" $ \ x ->+    Witch.from @[Int] @(Set.Set Int) x == Set.fromList x++  Hspec.prop "From (Set a) [a]" $ \ x ->+    Witch.from @(Set.Set Int) @[Int] x == Set.toAscList x++  Hspec.prop "From [(k, v)] (Map k v)" $ \ x ->+    Witch.from @[(Char, Int)] @(Map.Map Char Int) x == Map.fromList x++  Hspec.prop "From (Map k v) [(k, v)]" $ \ x ->+    Witch.from @(Map.Map Char Int) @[(Char, Int)] x == Map.toAscList x++instance QuickCheck.Arbitrary Natural.Natural where+  arbitrary = QuickCheck.arbitrarySizedNatural+  shrink = QuickCheck.shrinkIntegral++instance QuickCheck.Arbitrary a => QuickCheck.Arbitrary (NonEmpty.NonEmpty a) where+  arbitrary = QuickCheck.applyArbitrary2 (NonEmpty.:|)+  shrink = QuickCheck.genericShrink++instance QuickCheck.Arbitrary LB.ByteString where+  arbitrary = fmap LB.pack QuickCheck.arbitrary+  shrink = QuickCheck.shrinkMap LB.pack LB.unpack++instance QuickCheck.Arbitrary B.ByteString where+  arbitrary = fmap B.pack QuickCheck.arbitrary+  shrink = QuickCheck.shrinkMap B.pack B.unpack++instance QuickCheck.Arbitrary T.Text where+  arbitrary = fmap T.pack QuickCheck.arbitrary+  shrink = QuickCheck.shrinkMap T.pack T.unpack++instance QuickCheck.Arbitrary LT.Text where+  arbitrary = fmap LT.pack QuickCheck.arbitrary+  shrink = QuickCheck.shrinkMap LT.pack LT.unpack
+ witch.cabal view
@@ -0,0 +1,51 @@+cabal-version: >= 1.10++build-type: Simple+category: Data+description: Witch converts values from one type into another.+extra-source-files: README.markdown+license-file: LICENSE.txt+license: ISC+maintainer: Taylor Fausak+name: witch+synopsis: Convert values from one type into another.+version: 0.0.0.0++source-repository head+  location: https://github.com/tfausak/witch+  type: git++library+  build-depends:+    base >= 4.12.0 && < 4.15+    , bytestring >= 0.10.8 && < 0.11+    , containers >= 0.6.0 && < 0.7+    , text >= 1.2.3 && < 1.3+  default-language: Haskell2010+  exposed-modules: Witch+  ghc-options:+    -Weverything+    -Wno-implicit-prelude+    -Wno-safe+    -Wno-unsafe+  hs-source-dirs: src/lib++  if impl(ghc >= 8.10)+    ghc-options:+      -Wno-missing-safe-haskell-mode+      -Wno-prepositive-qualified-module++test-suite test+  build-depends:+    base+    , bytestring+    , containers+    , hspec >= 2.7.1 && < 2.8+    , QuickCheck >= 2.13.2 && < 2.14+    , text+    , witch+  default-language: Haskell2010+  ghc-options: -rtsopts -threaded+  hs-source-dirs: src/test+  main-is: Main.hs+  type: exitcode-stdio-1.0