MIT licensed and maintained by Travis Cardwell

Module documentation for 0.2.3.0

This version can be pinned in stack with:ttc-0.2.3.0@sha256:b01afb64c2d7e0a8ca0f8adc583a31f4227ac059d7146116cca885bc7006ef3d,1636

TTC: Textual Type Classes

Build Status Hackage Stackage LTS Stackage Nightly

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:

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

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 security@extrema.is 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 bugs@extrema.is.

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:
    1. Breaking
    2. Non-Breaking
  • Changes are listed in arbitrary order and present tense.

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 with template-haskell 2.16.0.0

0.2.1.0 (2020-05-11)

Non-Breaking

  • Update examples to support older libraries
  • Refactor Makefile, add STACK_NIX_PATH support
  • Add test-all command to Makefile
  • 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