relude-1.2.2.2: src/Relude/String/Conversion.hs
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 902
{-# OPTIONS_GHC -Wno-operator-whitespace #-}
#endif
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Safe #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{- |
Module : Relude.String.Conversion
Copyright : (c) 2016 Stephen Diehl
(c) 2016-2018 Serokell
(c) 2018-2023 Kowainik
SPDX-License-Identifier : MIT
Maintainer : Kowainik <xrom.xkov@gmail.com>
Stability : Stable
Portability : Portable
This module implements type class which allow to have conversion to and from
'Relude.String.Reexport.Text', 'String' and 'ByteString' types
(including both strict and lazy versions). Usually you need to export
'Relude.String.Reexport.Text' modules qualified and use 'T.pack' \/ 'T.unpack'
functions to convert to\/from 'Relude.String.Reexport.Text'. Now you can just
use 'toText' \/ 'toString' functions.
-}
module Relude.String.Conversion
( -- * Convenient type aliases
LText
, LByteString
-- * Conversion type classes
, ConvertUtf8 (..)
, ToText (..)
, ToLText (..)
, ToString (..)
, LazyStrict (..)
, fromLazy
, fromStrict
-- * Show and read functions
, readEither
, show
) where
import GHC.TypeLits (ErrorMessage (..), Symbol, TypeError)
import Prelude (error)
import Relude.Base (Constraint, Type)
import Relude.Function (id, (.))
import Relude.Functor (first, (<$>))
import Relude.Monad.Reexport (Either)
import Relude.String.Reexport (ByteString, IsString, Read, ShortByteString, String, Text, fromShort,
fromString, toShort)
import qualified Data.ByteString.Lazy as LB
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Encoding.Error as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT
import qualified GHC.Show as Show (Show (show))
import qualified Text.Read (readEither)
-- $setup
-- >>> import Relude
-- | Type synonym for 'Data.Text.Lazy.Text'.
type LText = LT.Text
-- | Type synonym for 'Data.ByteString.Lazy.ByteString'.
type LByteString = LB.ByteString
-- | Type class for conversion to utf8 representation of text.
class ConvertUtf8 a b where
-- | Encode as utf8 string (usually 'B.ByteString').
--
-- >>> encodeUtf8 @Text @ByteString "патак"
-- "\208\191\208\176\209\130\208\176\208\186"
encodeUtf8 :: a -> b
-- | Decode from utf8 string.
--
-- >>> decodeUtf8 @Text @ByteString "\208\191\208\176\209\130\208\176\208\186"
-- "\1087\1072\1090\1072\1082"
-- >>> putTextLn $ decodeUtf8 @Text @ByteString "\208\191\208\176\209\130\208\176\208\186"
-- патак
decodeUtf8 :: b -> a
{- | Decode as utf8 string but returning execption if byte sequence is malformed.
#if MIN_VERSION_text(1,2,3)
>>> decodeUtf8 @Text @ByteString "\208\208\176\209\130\208\176\208\186"
"\65533\1072\1090\1072\1082"
#else
>>> decodeUtf8 @Text @ByteString "\208\208\176\209\130\208\176\208\186"
"\65533\65533\1090\1072\1082"
#endif
#if MIN_VERSION_text(2,0,2)
>>> decodeUtf8Strict @Text @ByteString "\208\208\176\209\130\208\176\208\186"
Left Cannot decode byte '\xd0': ...: Invalid UTF-8 stream
#elif MIN_VERSION_text(2,0,0)
>>> decodeUtf8Strict @Text @ByteString "\208\208\176\209\130\208\176\208\186"
Left Cannot decode byte '\xd0': ...: Invalid UTF-8 stream
#else
>>> decodeUtf8Strict @Text @ByteString "\208\208\176\209\130\208\176\208\186"
Left Cannot decode byte '\xd0': ...: Invalid UTF-8 stream
#endif
-}
decodeUtf8Strict :: b -> Either T.UnicodeException a
instance ConvertUtf8 String ByteString where
encodeUtf8 :: String -> ByteString
encodeUtf8 = T.encodeUtf8 . T.pack
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: ByteString -> String
decodeUtf8 = T.unpack . T.decodeUtf8
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: ByteString -> Either T.UnicodeException String
decodeUtf8Strict = (T.unpack <$>) . decodeUtf8Strict
{-# INLINE decodeUtf8Strict #-}
instance ConvertUtf8 Text ByteString where
encodeUtf8 :: Text -> ByteString
encodeUtf8 = T.encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: ByteString -> Text
decodeUtf8 = T.decodeUtf8With T.lenientDecode
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: ByteString -> Either T.UnicodeException Text
decodeUtf8Strict = T.decodeUtf8'
{-# INLINE decodeUtf8Strict #-}
instance ConvertUtf8 LText ByteString where
encodeUtf8 :: LText -> ByteString
encodeUtf8 = LB.toStrict . encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: ByteString -> LText
decodeUtf8 = LT.decodeUtf8With T.lenientDecode . LB.fromStrict
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: ByteString -> Either T.UnicodeException LText
decodeUtf8Strict = decodeUtf8Strict . LB.fromStrict
{-# INLINE decodeUtf8Strict #-}
{- | Converting 'String' to 'LB.ByteString' might be a slow operation.
Consider using lazy bytestring at first place.
-}
instance ConvertUtf8 String LByteString where
encodeUtf8 :: String -> LByteString
encodeUtf8 = LT.encodeUtf8 . LT.pack
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: LByteString -> String
decodeUtf8 = LT.unpack . LT.decodeUtf8
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: LByteString -> Either T.UnicodeException String
decodeUtf8Strict = (T.unpack <$>) . decodeUtf8Strict
{-# INLINE decodeUtf8Strict #-}
instance ConvertUtf8 Text LByteString where
encodeUtf8 :: Text -> LByteString
encodeUtf8 = LB.fromStrict . T.encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: LByteString -> Text
decodeUtf8 = T.decodeUtf8With T.lenientDecode . LB.toStrict
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: LByteString -> Either T.UnicodeException Text
decodeUtf8Strict = T.decodeUtf8' . LB.toStrict
{-# INLINE decodeUtf8Strict #-}
instance ConvertUtf8 LText LByteString where
encodeUtf8 :: LText -> LByteString
encodeUtf8 = LT.encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: LByteString -> LText
decodeUtf8 = LT.decodeUtf8With T.lenientDecode
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: LByteString -> Either T.UnicodeException LText
decodeUtf8Strict = LT.decodeUtf8'
{-# INLINE decodeUtf8Strict #-}
-- | @since 0.6.0.0
instance ConvertUtf8 String ShortByteString where
encodeUtf8 :: String -> ShortByteString
encodeUtf8 = toShort . encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: ShortByteString -> String
decodeUtf8 = decodeUtf8 . fromShort
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: ShortByteString -> Either T.UnicodeException String
decodeUtf8Strict = decodeUtf8Strict . fromShort
{-# INLINE decodeUtf8Strict #-}
-- | @since 0.6.0.0
instance ConvertUtf8 Text ShortByteString where
encodeUtf8 :: Text -> ShortByteString
encodeUtf8 = toShort . encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: ShortByteString -> Text
decodeUtf8 = decodeUtf8 . fromShort
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: ShortByteString -> Either T.UnicodeException Text
decodeUtf8Strict = decodeUtf8Strict . fromShort
{-# INLINE decodeUtf8Strict #-}
-- | @since 0.6.0.0
instance ConvertUtf8 LText ShortByteString where
encodeUtf8 :: LText -> ShortByteString
encodeUtf8 = toShort . encodeUtf8
{-# INLINE encodeUtf8 #-}
decodeUtf8 :: ShortByteString -> LText
decodeUtf8 = decodeUtf8 . fromShort
{-# INLINE decodeUtf8 #-}
decodeUtf8Strict :: ShortByteString -> Either T.UnicodeException LText
decodeUtf8Strict = decodeUtf8Strict . fromShort
{-# INLINE decodeUtf8Strict #-}
-- | Type class for converting other strings to 'T.Text'.
class ToText a where
toText :: a -> Text
instance ToText String where
toText :: String -> Text
toText = T.pack
{-# INLINE toText #-}
instance ToText Text where
toText :: Text -> Text
toText = id
{-# INLINE toText #-}
instance ToText LText where
toText :: LText -> Text
toText = LT.toStrict
{-# INLINE toText #-}
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toText ("some string" :: ByteString)
...
... Type 'ByteString' doesn't have instance of 'ToText'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: ByteString -> Text
decodeUtf8Strict :: ByteString -> Either UnicodeException Text
...
@since 0.6.0.0
-}
instance EncodingError ToText "ByteString" "Text" => ToText ByteString where
toText :: ByteString -> Text
toText = error "Unreachable ByteString instance of ToText"
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toText ("some string" :: LByteString)
...
... Type 'LByteString' doesn't have instance of 'ToText'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: LByteString -> Text
decodeUtf8Strict :: LByteString -> Either UnicodeException Text
...
@since 0.6.0.0
-}
instance EncodingError ToText "LByteString" "Text" => ToText LByteString where
toText :: LByteString -> Text
toText = error "Unreachable LByteString instance of ToText"
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toText ("some string" :: ShortByteString)
...
... Type 'ShortByteString' doesn't have instance of 'ToText'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: ShortByteString -> Text
decodeUtf8Strict :: ShortByteString -> Either UnicodeException Text
...
@since 0.6.0.0
-}
instance EncodingError ToText "ShortByteString" "Text" => ToText ShortByteString where
toText :: ShortByteString -> Text
toText = error "Unreachable ShortByteString instance of ToText"
-- | Type class for converting other strings to 'LT.Text'.
class ToLText a where
toLText :: a -> LText
instance ToLText String where
toLText :: String -> LText
toLText = LT.pack
{-# INLINE toLText #-}
instance ToLText Text where
toLText :: Text -> LText
toLText = LT.fromStrict
{-# INLINE toLText #-}
instance ToLText LT.Text where
toLText :: LText -> LText
toLText = id
{-# INLINE toLText #-}
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toLText ("some string" :: ByteString)
...
... Type 'ByteString' doesn't have instance of 'ToLText'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: ByteString -> LText
decodeUtf8Strict :: ByteString -> Either UnicodeException LText
...
@since 0.6.0.0
-}
instance EncodingError ToLText "ByteString" "LText" => ToLText ByteString where
toLText :: ByteString -> LText
toLText = error "Unreachable ByteString instance of ToLText"
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toLText ("some string" :: LByteString)
...
... Type 'LByteString' doesn't have instance of 'ToLText'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: LByteString -> LText
decodeUtf8Strict :: LByteString -> Either UnicodeException LText
...
@since 0.6.0.0
-}
instance EncodingError ToLText "LByteString" "LText" => ToLText LByteString where
toLText :: LByteString -> LText
toLText = error "Unreachable LByteString instance of ToLText"
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toLText ("some string" :: ShortByteString)
...
... Type 'ShortByteString' doesn't have instance of 'ToLText'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: ShortByteString -> LText
decodeUtf8Strict :: ShortByteString -> Either UnicodeException LText
...
@since 0.6.0.0
-}
instance EncodingError ToLText "ShortByteString" "LText" => ToLText ShortByteString where
toLText :: ShortByteString -> LText
toLText = error "Unreachable ShortByteString instance of ToLText"
-- | Type class for converting other strings to 'String'.
class ToString a where
toString :: a -> String
instance ToString String where
toString :: String -> String
toString = id
{-# INLINE toString #-}
instance ToString Text where
toString :: Text -> String
toString = T.unpack
{-# INLINE toString #-}
instance ToString LText where
toString :: LText -> String
toString = LT.unpack
{-# INLINE toString #-}
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toString ("some string" :: ByteString)
...
... Type 'ByteString' doesn't have instance of 'ToString'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: ByteString -> String
decodeUtf8Strict :: ByteString -> Either UnicodeException String
...
@since 0.6.0.0
-}
instance EncodingError ToString "ByteString" "String" => ToString ByteString where
toString :: ByteString -> String
toString = error "Unreachable ByteString instance of ToString"
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toString ("some string" :: LByteString)
...
... Type 'LByteString' doesn't have instance of 'ToString'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: LByteString -> String
decodeUtf8Strict :: LByteString -> Either UnicodeException String
...
@since 0.6.0.0
-}
instance EncodingError ToString "LByteString" "String" => ToString LByteString where
toString :: LByteString -> String
toString = error "Unreachable LByteString instance of ToString"
{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.
You should always specify encoding of bytes explicitly.
In case it is used by mistake, the user will see the following:
>>> toString ("some string" :: ShortByteString)
...
... Type 'ShortByteString' doesn't have instance of 'ToString'.
Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:
decodeUtf8 :: ShortByteString -> String
decodeUtf8Strict :: ShortByteString -> Either UnicodeException String
...
@since 0.6.0.0
-}
instance EncodingError ToString "ShortByteString" "String" => ToString ShortByteString where
toString :: ShortByteString -> String
toString = error "Unreachable ShortByteString instance of ToString"
-- | Helper type family to produce error messages
type family EncodingError
(c :: Type -> Constraint)
(from :: Symbol)
(to :: Symbol)
:: Constraint
where
EncodingError c from to = TypeError
( 'Text "Type '" ':<>: 'Text from ':<>: 'Text "' doesn't have instance of '"
':<>: 'ShowType c ':<>: 'Text "'."
':$$: 'Text "Use 'decodeUtf8' or 'decodeUtf8Strict' to convert from UTF-8:"
':$$: 'Text " decodeUtf8 :: " ':<>: 'Text from
':<>: 'Text " -> " ':<>: 'Text to
':$$: 'Text " decodeUtf8Strict :: " ':<>: 'Text from
':<>: 'Text " -> Either UnicodeException " ':<>: 'Text to
)
{- | Version of 'Text.Read.readEither' that returns 'Text' in case of the parse
error.
>>> readEither @Int "123"
Right 123
>>> readEither @Int "aa"
Left "Prelude.read: no parse"
-}
readEither :: (Read a) => String -> Either Text a
readEither = first toText . Text.Read.readEither
{-# INLINEABLE readEither #-}
{- | Generalized version of 'Prelude.show'. Unlike 'Prelude.show' this function
is polymorphic in its result type. This makes it more convenient to work with
data types like 'Relude.String.Reexport.Text' or 'ByteString'. However, if you
pass the result of 'show' to a function that expects polymorphic argument, this
can break type inference, so use @-XTypeApplications@ to specify the textual
type explicitly.
>>> show (42 :: Int)
"42"
>>> show (42 :: Double)
"42.0"
>>> print (show @Text True)
"True"
-}
show :: forall b a . (Show.Show a, IsString b) => a -> b
show x = fromString (Show.show x)
{-# INLINE show #-}
{-# SPECIALIZE show :: Show.Show a => a -> Text #-}
{-# SPECIALIZE show :: Show.Show a => a -> LText #-}
{-# SPECIALIZE show :: Show.Show a => a -> ByteString #-}
{-# SPECIALIZE show :: Show.Show a => a -> LByteString #-}
{-# SPECIALIZE show :: Show.Show a => a -> String #-}
{- | Type class for lazy-strict conversions.
@since 0.1.0
-}
class LazyStrict l s | l -> s, s -> l where
toLazy :: s -> l
toStrict :: l -> s
-- | Alias for 'toStrict' function.
fromLazy :: LazyStrict l s => l -> s
fromLazy = toStrict
{-# INLINE fromLazy #-}
{-# SPECIALIZE fromLazy :: LByteString -> ByteString #-}
{-# SPECIALIZE fromLazy :: LText -> Text #-}
-- | Alias for 'toLazy' function.
fromStrict :: LazyStrict l s => s -> l
fromStrict = toLazy
{-# INLINE fromStrict #-}
{-# SPECIALIZE fromStrict :: ByteString -> LByteString #-}
{-# SPECIALIZE fromStrict :: Text -> LText #-}
instance LazyStrict LByteString ByteString where
toLazy :: ByteString -> LByteString
toLazy = LB.fromStrict
{-# INLINE toLazy #-}
toStrict :: LByteString -> ByteString
toStrict = LB.toStrict
{-# INLINE toStrict #-}
instance LazyStrict LText Text where
toLazy :: Text -> LText
toLazy = LT.fromStrict
{-# INLINE toLazy #-}
toStrict :: LText -> Text
toStrict = LT.toStrict
{-# INLINE toStrict #-}