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
withError err = maybe (Left $ convert err) RightIn 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
fromString = TTC.parseUnsafe
instance TTC.Parse Currency where
parse = TTC.asT $ \t -> TTC.withErrorS "invalid currency code" $ do
guard $ T.compareLength t 3 == EQ
guard $ T.all isAsciiUpper t
pure $ Currency t
instance TTC.Render Currency where
render = TTC.fromT . unCurrencyprefixError
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
prefixError prefix = either (Left . convert . mappend prefix) RightLike 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
fromString = TTC.parseUnsafe
instance TTC.Parse Currency where
parse = TTC.asT $ \t -> TTC.prefixErrorS "invalid currency code: " $ do
unless (T.compareLength t 3 == EQ) $ Left "not three characters long"
unless (T.all isAsciiUpper t) $ Left "not all uppercase ASCII"
pure $ Currency t
instance TTC.Render Currency where
render = TTC.fromT . unCurrencyNotice 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.