TTC Parse Error Helper Functions
I changed the TTC
(Textual Type Classes) Parse
API to use Textual
error messages long ago. With that change, library users were no longer
forced to use the String
data type for error messages, but
it made parse
implementations a bit more verbose. I have
been thinking about adding some helper functions for some common cases.
Today, I was working on an experimental project that uses TTC, and I
decided to go ahead and add those helper functions.
Please note that these changes have been pushed to the
develop
branch but have not been released yet. I plan on
including the changes in the next release (version
1.2.0.0
), which I will likely work on next week.
withError
The withError
helper function provides a convenient way
to return the same error message for any parse error. It is implemented
as follows.
withError :: (Textual e', Textual e)
=> e'
-> Maybe a
-> Either e a
= maybe (Left $ convert err) Right withError err
In this function, e'
is the type of the error message in
the parse
implementation, e
is the type of the
error message where parse
is called, and a
is
the type of the parsed value. Functions with type suffixes
(withErrorS
, …) call this function with concrete
e'
types.
For example, the following Currency
implementation uses
a Parse
instance as a smart constructor to ensure that all Currency
values are valid. The withErrorS
helper function returns
the same error message for any validation error.
... -- language pragmas not shown
module Currency (Currency) where
... -- imports not shown
-- | Currency identifier represented by ISO 4217 alpha code
--
-- The code must be three uppercase ASCII letters.
--
-- Examples:
--
-- * @USD@
-- * @JPY@
--
-- Reference:
--
-- * <https://en.wikipedia.org/wiki/ISO_4217>
newtype Currency = Currency { unCurrency :: Text }
deriving newtype (Eq, Ord)
deriving stock (Show)
instance IsString Currency where
= TTC.parseUnsafe
fromString
instance TTC.Parse Currency where
= TTC.asT $ \t -> TTC.withErrorS "invalid currency code" $ do
parse $ T.compareLength t 3 == EQ
guard $ T.all isAsciiUpper t
guard pure $ Currency t
instance TTC.Render Currency where
= TTC.fromT . unCurrency render
prefixError
The prefixError
helper function provides a convenient
way to add a common prefix to any parse error. It is implemented as
follows.
prefixError :: (Monoid e', Textual e', Textual e)
=> e'
-> Either e' a
-> Either e a
= either (Left . convert . mappend prefix) Right prefixError prefix
Like the withError
function, e'
is the type
of the error message in the parse
implementation,
e
is the type of the error message where parse
is called, and a
is the type of the parsed value. Functions
with type suffixes (prefixErrorS
, …) call this function
with concrete e'
types.
The Currency
implementation can be changed to use the
prefixErrorS
function and provide more error detail.
... -- language pragmas not shown
module Currency (Currency) where
... -- imports not shown
-- | Currency identifier represented by ISO 4217 alpha code
--
-- The code must be three uppercase ASCII letters.
--
-- Examples:
--
-- * @USD@
-- * @JPY@
--
-- Reference:
--
-- * <https://en.wikipedia.org/wiki/ISO_4217>
newtype Currency = Currency { unCurrency :: Text }
deriving newtype (Eq, Ord)
deriving stock (Show)
instance IsString Currency where
= TTC.parseUnsafe
fromString
instance TTC.Parse Currency where
= TTC.asT $ \t -> TTC.prefixErrorS "invalid currency code: " $ do
parse 3 == EQ) $ Left "not three characters long"
unless (T.compareLength t $ Left "not all uppercase ASCII"
unless (T.all isAsciiUpper t) pure $ Currency t
instance TTC.Render Currency where
= TTC.fromT . unCurrency render
Notice how the prefixErrorS
function makes the type of
the error prefix as well as all error messages in the do
body String
. No calls to convert
or from
conversion functions are required in the implementation of
parse
.