Skip to main content

New TTC Features

Today I released TTC version 1.3.0.0, which includes two new features.

Minimal Compile-Time Validation Syntax

TTC provides a number of ways to use Parse instances to validate constants at compile-time. They are all functionally equivalent, and developers may use whatever is most convenient for their project (supported GHC versions) and specific types. The valid function is usually a great choice when the parsed typed has a Lift instance. It uses typed Template Haskell, so a type annotation determines how the string is parsed. The following works in all supported versions of GHC:

alice :: Username
alice = $$(TTC.valid "alice")

This release adds an IsString instance for typed Template Haskell expressions so that string syntax automatically calls valid. The OverloadedStrings extension must be enabled in the module where this functionality is used. The following works in all supported versions of GHC:

alice :: Username
alice = $$("alice")

The following works from GHC 9:

alice :: Username
alice = $$"alice"

As noted in the documentation, a drawback to using the minimal syntax is that it can make the code more difficult to understand by somebody who is not already familiar with this technique. Explicit TTC.valid usage provides developers a way to check the documentation and understand what is happening, while the minimal syntax may leave some developers scratching their head. As with all of the validation methods, developers should use what is most appropriate for their project. Perhaps minimal syntax is a good choice in a project where all developers are assumed to be advanced, while explicit TTC.valid usage may be more appropriate in more beginner-friendly projects.

Thank you to Tal Walter for showing me Chris Done’s Statically checked overloaded strings gist, which inspired me to add support for this.

parseOrFail Functions

The parse method returns an Either type, and TTC provides a number of convenience functions for parsing to other types. The parseMaybe functions return a Maybe type, and the parseUnsafe functions call error on error. The following is an abridged overview. Note that the internal parse' helper function simply sets the error type to String, for use when the error is ignored.

class Parse a where
  parse :: (Textual t, Textual e) => t -> Either e a

parse' :: (Parse a, Textual t) => t -> Either String a
parse' = parse

parseMaybe :: (Parse a, Textual t) => t -> Maybe a
parseMaybe = either (const Nothing) Just . parse'

parseUnsafe :: (HasCallStack, Parse a, Textual t) => t -> a
parseUnsafe = either (error . ("parseUnsafe: " ++)) id . parse

This release adds a set of parseOrFail functions that fail using MonadFail on error.

parseOrFail :: (MonadFail m, Parse a, Textual t) => t -> m a
parseOrFail = either fail pure . parse

I have been on the fence about including this for years, because it is so trivial. I decided to go ahead and add it because it seems to fit, regardless of the API bloat. MonadFail is in Prelude after all. Note that Maybe has a MonadFail instance, but I am keeping the parseMaybe functions for backwards compatibility as well as because parseMaybe may be easier to understand than parseOrFail in some contexts.

Author

Travis Cardwell

Published

Tags