{-# language AllowAmbiguousTypes #-} {-# language DefaultSignatures #-} {-# language FlexibleInstances #-} {-# language MultiParamTypeClasses #-} {-# language ScopedTypeVariables #-} {-# language TypeFamilies #-} -- | This module provides the 'Cast' 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: -- -- - : -- 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. -- -- - : -- This type class allows for conversions to fail. -- -- - : -- 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. -- -- - : -- This type class requires conversions to be injective, as opposed to merely -- suggesting it. Also some conversions fail at runtime. -- -- - : -- This type class comes with many convenient helper functions, but some of -- the provided instances fail at runtime. -- -- - : -- This package is not available on Hackage, but otherwise is very similar to -- this one. module Witch (Cast(cast), 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 @Cast 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 Cast String Name -- > instance Cast 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 @Cast Int (Maybe Char)@. (It might be -- worthwhile to have a separate @TryCast@ 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 @Cast a b@ then -- no two @a@ values should be converted to the same @b@ value. For example -- @Cast Int Integer@ is fine because every @Int@ can be mapped to a -- corresponding @Integer@, but @Cast Integer Int@ is not good because some -- @Integer@s are out of bounds and would need to be clamped. -- -- - If you have both @Cast a b@ and @Cast b a@, then @cast . cast@ should be -- the same as 'id'. In other words @a@ and @b@ are isomorphic. -- -- - If you have both @Cast a b@ and @Cast b c@, then it's up to you if you -- want to provide @Cast a c@. Sometimes using 'via' is ergonomic enough, -- other times you want the extra instance. (It would be nice if we could -- provide @instance (Cast a b, Cast b c) => Cast a c where cast = via \@b@.) class Cast source target where -- | This method implements the conversion of a value between types. In -- practice most instances don't need an explicit implementation. At call -- sites you'll usually want to use 'from' or 'into' instead of 'cast'. -- -- The default implementation of 'cast' simply calls 'Coerce.coerce', which -- works for types that have the same runtime representation. cast :: source -> target default cast :: Coerce.Coercible source target => source -> target cast = Coerce.coerce -- This ugly hack is used to require type applications when calling 'from' and -- 'into'. See . data Never type family Ambiguous a where Ambiguous Never = () Ambiguous a = a -- | This function converts a value from one type into another. This is -- intended to be used with the @TypeApplications@ language extension. The -- @Ambiguous@ type in the signature makes a type application required. If -- you'd prefer not to provide a type application, use 'cast' instead. -- -- As an 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 -- -- 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 -- from :: forall s target source . (Ambiguous s ~ source, Cast source target) => source -> target from = cast -- | 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 t source target . (Ambiguous t ~ target, Cast source target) => source -> target into = cast -- | This function converts a value from one type into another by going through -- some third type. This is the same as calling 'cast' (or '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 through source target . (Cast source through, Cast through target) => source -> target via = cast . (\ x -> x :: through) . cast -- | 'id' instance Cast a a where cast = id -- | 'const' instance Cast a (x -> a) where cast = const -- | 'pure' instance Cast a [a] where cast = pure -- | 'Just' instance Cast a (Maybe a) where cast = Just -- | 'Left' instance Cast a (Either a x) where cast = Left -- | 'Right' instance Cast a (Either x a) where cast = Right -- | 'Void.absurd' instance Cast Void.Void x where cast = Void.absurd -- | 'fst' instance Cast (a, x) a where cast = fst -- | 'snd' instance Cast (x, a) a where cast = snd -- | 'Tuple.swap' instance Cast (a, b) (b, a) where cast = Tuple.swap -- | 'NonEmpty.toList' instance Cast (NonEmpty.NonEmpty a) [a] where cast = NonEmpty.toList -- | 'fromIntegral' instance Cast Word.Word8 Word.Word16 where cast = fromIntegral -- | 'fromIntegral' instance Cast Word.Word16 Word.Word32 where cast = fromIntegral -- | 'fromIntegral' instance Cast Word.Word32 Word.Word64 where cast = fromIntegral -- | 'fromIntegral' instance Cast Word Natural.Natural where cast = fromIntegral -- | 'fromIntegral' instance Cast Natural.Natural Integer where cast = fromIntegral -- | 'fromIntegral' instance Cast Int.Int8 Int.Int16 where cast = fromIntegral -- | 'fromIntegral' instance Cast Int.Int16 Int.Int32 where cast = fromIntegral -- | 'fromIntegral' instance Cast Int.Int32 Int.Int64 where cast = fromIntegral -- | 'fromIntegral' instance Cast Int Integer where cast = fromIntegral -- | 'fromIntegral' instance Cast Integer Rational where cast = fromIntegral -- | 'realToFrac' instance Cast Float Double where cast = realToFrac -- | 'fromEnum' instance Cast Bool Int where cast = fromEnum -- | 'fromEnum' instance Cast Char Int where cast = fromEnum -- | 'ByteString.pack' instance Cast [Word.Word8] ByteString.ByteString where cast = ByteString.pack -- | 'ByteString.unpack' instance Cast ByteString.ByteString [Word.Word8] where cast = ByteString.unpack -- | 'LazyByteString.fromStrict' instance Cast ByteString.ByteString LazyByteString.ByteString where cast = LazyByteString.fromStrict -- | 'LazyByteString.toStrict' instance Cast LazyByteString.ByteString ByteString.ByteString where cast = LazyByteString.toStrict -- | 'Text.pack' -- -- Note that some 'Char' values cannot be represented in 'Text' and will be -- replaced by U+FFFD. instance Cast String Text.Text where cast = Text.pack -- | 'Text.unpack' instance Cast Text.Text String where cast = Text.unpack -- | 'LazyText.fromStrict' instance Cast Text.Text LazyText.Text where cast = LazyText.fromStrict -- | 'LazyText.toStrict' instance Cast LazyText.Text Text.Text where cast = LazyText.toStrict -- | 'Seq.fromList' instance Cast [a] (Seq.Seq a) where cast = Seq.fromList -- | 'Foldable.toList' instance Cast (Seq.Seq a) [a] where cast = Foldable.toList -- | 'Set.fromList' -- -- Note that this will remove duplicate elements from the list. instance Ord a => Cast [a] (Set.Set a) where cast = Set.fromList -- | 'Set.toAscList' instance Cast (Set.Set a) [a] where cast = 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 => Cast [(k, v)] (Map.Map k v) where cast = Map.fromList -- | 'Map.toAscList' instance Cast (Map.Map k v) [(k, v)] where cast = Map.toAscList