ttc
Textual Type Classes
https://github.com/ExtremaIS/ttc-haskell#readme
Version on this page: | 0.2.0.0 |
LTS Haskell 22.39: | 1.4.0.0@rev:1 |
Stackage Nightly 2024-10-31: | 1.4.0.0@rev:1 |
Latest on Hackage: | 1.4.0.0@rev:1 |
ttc-0.2.0.0@sha256:402a9e45120ba613ff0b82e35865649ee7904c56c67969ece155949a4730b5a2,1616
Module documentation for 0.2.0.0
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 following is a brief overview of the type classes provided by this
library. See the
API documentation on Hackage
for details and the examples
directory for usage examples.
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
Parse
The Parse
type class parses a data type from a Textual
data
type:
class Parse a where
parse :: Textual t => t -> Either String 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-> do
unless (T.all isAsciiLower t) $ Left "username has invalid character(s)"
let len = T.length t
when (len < 3) $ Left "username has fewer than 3 characters"
when (len > 12) $ Left "username has more than 12 characters"
pure $ Username t
If a username needs to be parsed from a String
, conversion is automatic:
case TTC.parse "tcard" :: Either String Username of
Right uname -> putStrLn $ "valid username: " ++ TTC.render uname
Left err -> putStrLn $ "invalid username: " ++ err
It is common to create data types that have “smart constructors” to ensure
that all constructed values are valid. If the Username
constructor were
exported, it would be possible to create values with arbitrary Text
, such
as Username T.empty
, which is not a valid Username
. Smart constructors
can be inconvenient when constructing constants, however, as neither runtime
error handling nor failure are desired. This library provides Template
Haskell functions that use Parse
instances to validate such constants at
compile-time.
Textual
The Textual
type class is used to convert between the following textual data
types:
String
- Strict
Text
- Lazy
Text
- Strict
ByteString
- Lazy
ByteString
Conversion between any of these types is direct; it is not done through a
fixed textual data type (such as String
or Text
). The key feature of this
type class is that it has a single type variable, making it easy to write
functions that accept arguments and/or returns values that may be any of the
supported textual data types.
Functions are provided to convert to/from the following other textual data types:
Text
Builder
ByteString
Builder
ShortByteString
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
.
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
Constant Validation
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 string-conversions and string-conv libraries have type classes with two type variables. The primary benefit of this approach is that one can add support for any string type.
The text-conversions
library converts between string types via the Text
type, using FromText
and ToText
type classes. This works well in most cases, but it not optimal
when converting between ByteString
types.
The textual library
(deprecated) converts between string types via the String
type, using a
Textual
type class (which provides a toString
function) as well as the
standard IsString
type class (which provides the fromString
function).
Project
Links
- Hackage: https://hackage.haskell.org/package/ttc
- Stackage: https://stackage.org/package/ttc
- GitHub: https://github.com/ExtremaIS/ttc-haskell
- Travis CI: https://travis-ci.com/ExtremaIS/ttc-haskell
Dependencies
Dependency version bounds are strictly specified according to what versions have been tested. If upper bounds need to be bumped when a new package is released or the package has been tested with earlier versions, feel free to submit an issue.
Releases
All releases are tagged in the master
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.
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