ttc
Textual Type Classes
https://github.com/ExtremaIS/ttc-haskell#readme
LTS Haskell 22.42: | 1.4.0.0@rev:1 |
Stackage Nightly 2024-11-24: | 1.4.0.0@rev:1 |
Latest on Hackage: | 1.4.0.0@rev:1 |
ttc-1.4.0.0@sha256:a0ce1326934127cfd9184016cd0eaee715ca4c47c74faa126a0da47cb00b173c,1852
Module documentation for 1.4.0.0
- Data
TTC: Textual Type Classes
Overview
TTC, an initialism of Textual Type Classes, is a library that provides
Render
and Parse
type classes for conversion between data types and
textual data types (strings). Use the Show
and Read
type classes for
debugging/development, and use the Render
and Parse
type classes for your
own purposes. The library also provides a Textual
type class for conversion
between textual data types as well as functions for validating constants at
compile-time.
Since a type may have at most one instance of a given type class, special care must be taken when defining type class instances in a shared library. In particular, orphan instances should generally not be used in shared libraries since they prevent users of the libraries from writing their own instances.
Render
and Parse
are best used with types that have canonical textual
representations, such as textual identifiers. When there is more than one way
to create a textual representation, such as configurable formatting, using a
normal function is probably more appropriate. Such a function can make use of
the Textual
type class to support multiple textual data types.
This overview includes a brief introduction of the library. The following resources are also available:
- API documentation is viewable on Hackage.
- A series of articles gives a guided tour of the library.
- The examples directory in the GitHub repository contains usage examples.
Textual
The Textual
type class is used to convert between the following textual data
types:
String
- Strict
Text
- Lazy
Text
Text
Builder
ShortText
- Strict
ByteString
- Lazy
ByteString
ByteString
Builder
(andData.Binary.Builder
)ShortByteString
This type class has two key features:
- Type conversion is not done through a fixed type (such as
String
orText
). - It has a single type variable, making it easy to write functions that accept arguments and/or return values that may be any of the supported textual data types.
For more details, see the Textual Type Class article.
Render
The Render
type class renders a data type as a Textual
data type:
class Render a where
render :: Textual t => a -> t
It is analogous to the Show
type class, which can be reserved for
debugging/development.
The render
function returns any of the supported textual data types. Use
the textual data type that is most natural in the implementation of render
instances, and return values are converted to other textual data types when
necessary. The Show
and IsString
type classes are not used, so use of the
String
type is not required.
As a simple example, consider a Username
type that is implemented as a
newtype
over Text
:
module Username (Username) where
import Control.Monad (unless, when)
import Data.Char (isAsciiLower)
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.TTC as TTC
newtype Username = Username Text
deriving (Eq, Ord, Show)
instance TTC.Render Username where
render (Username t) = TTC.convert t
If a username needs to be included in a String
error message, conversion is
automatic:
putStrLn $ "user not found: " ++ TTC.render uname
For more details, see the Render and Parse article.
Parse
The Parse
type class parses a data type from a Textual
data type:
class Parse a where
parse :: (Textual t, Textual e) => t -> Either e a
It is analogous to the Read
type class, which can be reserved for
debugging/development.
The parse
function takes any of the supported textual data types as an
argument. Use the textual data type that is most natural in the
implementation of parse
instances, and arguments are converted from other
textual data types when necessary. The IsString
type class is not used, so
use of the String
type is not required.
Here is an example instance for Username
, implementing some restrictions:
instance TTC.Parse Username where
parse = TTC.asT $ \t -> TTC.prefixErrorS "invalid username: " $ do
unless (T.all isAsciiLower t) $ Left "not only lowercase ASCII letters"
let len = T.length t
when (len < 3) $ Left "fewer than 3 characters"
when (len > 12) $ Left "more than 12 characters"
pure $ Username t
If a username needs to be parsed from a String
, conversion is automatic:
case TTC.parse s :: Either String Username of
Right uname -> "valid username: " ++ TTC.render uname
Left err -> err
For more details, see the Render and Parse article.
Constant Validation
TTC provides functions to validate constants at compile-time, using Template
Haskell. For example, a Username
constant can be defined as follows:
user :: Username
user = $$(TTC.valid "tcard")
For more details, see the Validated Constants article.
Related Work
Rendering and Parsing
The relude library has polymorphic versions of show
and readEither
in
Relude.String.Conversion
, as well as various type classes for converting
between string types. This does not encourage using Show
and Read
instances with syntactically valid Haskell syntax, and it encourages the using
of the String
data type.
The rio library has a Display
type class with a similar goal as
TTC.Render
. Since the library encourages a uniform usage of textual data
types, Display
only provides functions for rendering to Text
and a builder
format. It does not have a type class similar to TTC.Parse
.
The text-display library defines a Display
type class intended to render
user-facing text. It uses a Builder
type internally and renders to a Text
value.
Harry Garrood has an interesting series of blog posts about type classes and
Show
:
- Down with Show! Part 1: Rules of thumb for when to use a type class
- Down with Show! Part 2: What’s wrong with the Show type class
- Down with Show! Part 3: A replacement for Show
Validating Constants
The qq-literals library creates a QuasiQuoter
from a parse function of
type String -> Either String a
. The functionality is similar to TTC’s
mkUntypedValidQQ
function. The mkUntypedValidQQ
function allows the user
to choose the name of the QuasiQuoter
because a name like valid
is
preferred when used via a qualified import while a name like username
may be
preferred when not using qualified imports. Note that mkUntypedValidQQ
also
splices in an explicit type signature.
The validated-literals library has a Validate
type class that is similar
to TTC.Parse
but supports conversion between arbitrary types, not just from
textual data types. Template Haskell functions are provided to perform
validation at compile-time. Result types must either have Lift
instances or
equivalent implementations.
Chris Done posted a gist about implementing statically checked overloaded strings.
String Type Conversion
There are a number of libraries that simplify conversion between string types.
The following libraries provide type classes with two type variables. The
primary benefit of this approach is that one can add support for any string
type. The drawback of this approach is that implementations of Render
and
Parse
using such a type class would have to be done via a fixed type,
resulting in unnecessary conversion when using other types.
The following library provide type classes with a single type variable, but conversion is done via a fixed type.
- hxt-regex-xmlschema has a
StringLike
type class and does conversion via theString
type - ListLike has a
StringLike
type class and does conversion via theString
type - monoid-subclasses provides a
TextualMonoid
type class that provides an abstract API over textual types, usingString
as the underlying type - stringlike converts via the
Text
type - tagsoup has a
StringLike
type class that provides an abstract API over textual types and acastString
function that converts via theString
type - text-conversions converts via the
Text
type - textual (deprecated) converts via the
String
type
Arbitrary Type Conversion
There are also a number of libraries that provide type classes for conversion between arbitrary types, including string types.
- basement provides type classes for conversion that may fail as well as conversion that cannot fail
- convertible
- witch provides type classes for conversion that may fail as well as conversion that cannot fail
Project
Links
- Hackage: https://hackage.haskell.org/package/ttc
- Stackage: https://stackage.org/package/ttc
- Flora: https://flora.pm/packages/@hackage/ttc
- GitHub: https://github.com/ExtremaIS/ttc-haskell
- GitHub Actions CI: https://github.com/ExtremaIS/ttc-haskell/actions
Branches
The main
branch is reserved for releases. It may be considered stable, and
HEAD
is always the latest release.
The develop
branch is the primary development branch. It contains changes
that have not yet been released, and it is not necessarily stable.
Hackage revisions are made for metadata changes, such as relaxation of
constraints when new versions of dependencies are released. The ttc.cabal
metadata in the main
branch may therefore not match that of Hackage. The
ttc.cabal
metadata in the develop
branch may match, unless work is being
done on a new release that contains other changes.
Tags
All releases are tagged in the main
branch. Release tags are signed using
the [email protected]
GPG key.
Contribution
Issues and feature requests are tracked on GitHub: https://github.com/ExtremaIS/ttc-haskell/issues
Issues may also be submitted via email to [email protected].
License
This project is released under the MIT License as specified in the
LICENSE
file.
Changes
ttc-haskell
Changelog
This project follows the Haskell package versioning policy, with
versions in A.B.C.D
format. A
may be incremented arbitrarily for
non-technical reasons, but semantic versioning is otherwise
followed, where A.B
is the major version, C
is the minor version, and D
is the patch version. Initial development uses versions 0.0.0.D
, for which
every version is considered breaking.
The format of this changelog is based on Keep a Changelog, with the following conventions:
- Level-two heading
Unreleased
is used to track changes that have not been released. - Other level-two headings specify the release in
A.B.C.D (YYYY-MM-DD)
format, with newer versions above older versions. - Level-three headings are used to categorize changes as follows:
- Breaking
- Non-Breaking
- Changes are listed in arbitrary order and present tense.
1.4.0.0 (2023-12-03)
Breaking
- Add support for
ShortText
Non-Breaking
- Bump
base
dependency version upper bound - Bump
template-haskell
dependency version upper bound
1.3.0.0 (2023-09-17)
Breaking
- Add typed Template Haskell expression
IsString
orphan instance - Add
parseOrFail
functions
Non-Breaking
- Bump
bytestring
dependency version upper bound - Bump
tasty
dependency version upper bound - Bump
text
dependency version upper bound
1.2.1.0 (2023-03-21)
Non-Breaking
- Bump
template-haskell
dependency version upper bound - Adjust dependency constraints to match tested versions
1.2.0.0 (2022-03-18)
Breaking
- Add
withError
functions - Add
prefixError
functions
1.1.1.1 (2022-03-01)
Non-Breaking
- Refactor
Makefile
1.1.1.0 (2021-12-25)
Non-Breaking
- Bump
text
dependency version upper bound
1.1.0.2 (2021-08-23)
Non-Breaking
- Bump
template-haskell
dependency version upper bound - Add CPP macro around
BSB.Builder
Show
instance in test code
1.1.0.1 (2021-06-25)
Non-Breaking
- Refactor Nix configuration
1.1.0.0 (2021-06-10)
Breaking
- Add
Textual
TLB.Builder
instance and related functions - Add
Textual
BSB.Builder
instance and related functions - Add
Textual
SBS.ShortByteString
instance and related functions - Add
RenderDefault
andParseDefault
type classes and instances - Remove
Data.TTC.Instances
Non-Breaking
- Add
HasCallStack
to unsafe functions
1.0.0.0 (2021-06-03)
Non-Breaking
- Add Cabal support to
Makefile
0.4.0.0 (2021-03-27)
Breaking
- Add support for GHC 9
- Add
renderTLB
,renderBSB
, andrenderSBS
functions - Use
Textual
error messages forparseEnum'
Non-Breaking
- Add
@since
annotations - Rename Git default branch to
main
- Use GitHub Actions instead of Travis CI
- Add Cabal tests to GitHub Actions
- Add stan static analysis
0.3.0.0 (2020-11-03)
Breaking
- Use
Textual
error messages - Add
maybeParseWithRead
function
0.2.3.0 (2020-09-25)
Non-Breaking
- Bump
bytestring
dependency version upper bound
0.2.2.0 (2020-05-17)
Non-Breaking
- Bump
template-haskell
dependency version upper bound- Update
lift
example for compatibility withtemplate-haskell 2.16.0.0
- Update
0.2.1.0 (2020-05-11)
Non-Breaking
- Update examples to support older libraries
- Refactor
Makefile
, addSTACK_NIX_PATH
support - Add
test-all
command toMakefile
- Bump
tasty
dependency version upper bound
0.2.0.0 (2019-12-15)
Non-Breaking
- Add untyped validation functions
- Move examples to a separate package
- Refactor examples and add more
0.1.0.1 (2019-12-02)
Non-Breaking
- Bump
time
dependency version upper bound
0.1.0.0 (2019-12-01)
Non-Breaking
- Update Cabal file in preparation for release to Hackage
0.0.0.4 (2019-11-30)
Non-Breaking
- Update Cabal file in preparation for release to Hackage
- Update documentation
- Add examples
0.0.0.3 (2019-11-28)
Non-Breaking
- Add continuous integration support
0.0.0.2 (2019-11-28)
Non-Breaking
- Update Cabal metadata
- Update README
0.0.0.1 (2019-11-23)
Breaking
- Initial public release