show-combinators-0.1.1.0: src/Text/Show/Combinators.hs
-- | Combinators to write 'Show' instances.
--
-- The following type illustrates the common use cases.
--
-- @
-- data MyType a
-- = C a a -- a regular constructor
-- | a :+: a -- an infix constructor
-- | R { f1 :: a, f2 :: a } -- a record
--
-- infixl 4 :+:
--
-- instance 'Show' a => 'Show' (MyType a) where
-- 'showsPrec' = 'flip' precShows where
-- precShows (C a b) = 'showCon' \"C\" '@|' a '@|' b
-- precShows (c :+: d) = 'showInfix'' \":+:\" 4 c d
-- precShows (R {f1 = e, f2 = f}) =
-- 'showRecord' \"R\" (\"f1\" '.=.' e '&|' \"f2\" '.=.' f)
-- @
module Text.Show.Combinators
( module Text.Show
, PrecShowS
-- * Simple constructors and applications
, showCon
, showApp
, (@|)
-- * Infix constructors
, showInfix
, showInfix'
-- ** Combinators for associative operators
-- | Use with care, see 'showInfixl'.
, showInfixl
, showInfixl'
, showInfixr
, showInfixr'
-- * Records
, ShowFields
, showRecord
, showField
, (.=.)
, noFields
, appendFields
, (&|)
) where
import Text.Show
-- | Type of strings representing expressions, parameterized by the surrounding
-- precedence level.
--
-- This is the return type of @'flip' 'showsPrec'@.
type PrecShowS = Int -> ShowS
-- | Show a constructor.
--
-- Possible constructor names are:
--
-- - regular constructors (e.g., @\"Left\"@);
-- - parenthesized infix constructors (e.g., @\"(:)\"@);
-- - smart constructors, for abstract types (e.g., @\"Map.fromList\"@).
--
-- === __Example with smart constructor__
--
-- @
-- instance (Show k, Show v) => Show (Map k v) where
-- showsPrec = 'flip' precShows where
-- precShows m = 'showCon' \"Map.fromList\" '@|' Map.toList m
--
-- -- Example output:
-- -- > Map.fromList [(33, True), (55, False)]
-- @
showCon :: String -> PrecShowS
showCon con _ = showString con
infixl 2 `showApp`, @|
-- | Show a function application.
showApp :: PrecShowS -> PrecShowS -> PrecShowS
showApp showF showX d = showParen (d > appPrec)
(showF appPrec . showSpace . showX appPrec1)
-- | Show a function application.
--
-- This is an infix shorthand for 'showApp' when the argument type is an
-- instance of 'Show'.
--
-- > showF @| x = showApp showF (flip showsPrec x)
(@|) :: Show a => PrecShowS -> a -> PrecShowS
(@|) showF x = showApp showF (flip showsPrec x)
-- | Show an applied infix operator with a given precedence.
showInfix :: String -> Int -> PrecShowS -> PrecShowS -> PrecShowS
showInfix op prec = showInfix_ op prec (prec + 1) (prec + 1)
-- | Show an applied infix operator with a given precedence.
--
-- This is a shorthand for 'showInfix' when the arguments types are instances
-- of 'Show'.
--
-- > showInfix' op prec x y =
-- > showInfix op prec (flip showsPrec x) (flip showsPrec y)
showInfix' :: (Show a, Show b) => String -> Int -> a -> b -> PrecShowS
showInfix' op prec x y = showInfix op prec (flip showsPrec x) (flip showsPrec y)
-- | Show an applied infix operator which is left associative (@infixl@).
-- Use with care.
--
-- This combinator assumes that, if there is another infix operator to the
-- left, it is either left associative with the same precedence, or it has a
-- different precedence.
-- An expression containing two operators at the same level with different
-- associativities is ambiguous.
--
-- By default, prefer 'showInfix' and 'showInfix''.
showInfixl :: String -> Int -> PrecShowS -> PrecShowS -> PrecShowS
showInfixl op prec = showInfix_ op prec prec (prec + 1)
-- | Show an applied infix operator which is left associative (@infixl@).
-- Use with care, see 'showInfixl'.
--
-- This is a shorthand for 'showInfixl' when the arguments types are instances
-- of 'Show'.
--
-- By default, prefer 'showInfix' and 'showInfix''.
showInfixl' :: (Show a, Show b) => String -> Int -> a -> b -> PrecShowS
showInfixl' op prec x y = showInfixl op prec (flip showsPrec x) (flip showsPrec y)
-- | Show an applied infix operator which is right associative (@infixr@).
-- Use with care.
--
-- This combinator assumes that, if there is another infix operator to the
-- right, it is either right associative with the same precedence, or it has a
-- different precedence.
-- An expression containing two operators at the same level with different
-- associativities is ambiguous.
--
-- By default, prefer 'showInfix' and 'showInfix''.
--
-- === __Example usage__
--
-- @
-- showList :: Show a => [a] -> PrecShowS
-- showList [] = showCon "[]"
-- showList (x : xs) = showInfixr ":" 5 (flip showsPrec x) (showList xs)
--
-- -- Example output:
-- -- > 0 : 1 : 2 : 3 : []
-- @
showInfixr :: String -> Int -> PrecShowS -> PrecShowS -> PrecShowS
showInfixr op prec = showInfix_ op prec (prec + 1) prec
-- | Show an applied infix operator which is right associative (@infixr@).
-- Use with care, see 'showInfixr'.
--
-- This is a shorthand for 'showInfixr' when the arguments types are instances
-- of 'Show'.
--
-- By default, prefer 'showInfix' and 'showInfix''.
showInfixr' :: (Show a, Show b) => String -> Int -> a -> b -> PrecShowS
showInfixr' op prec x y = showInfixr op prec (flip showsPrec x) (flip showsPrec y)
-- | An internal combinator for infix operators, to explicitly update the
-- precedence levels on each side.
showInfix_ :: String -> Int -> Int -> Int -> PrecShowS -> PrecShowS -> PrecShowS
showInfix_ op prec precX precY showX showY d = showParen (d > prec)
(showX precX . showSpace . showString op . showSpace . showY precY)
-- | Strings representing a set of record fields separated by commas.
-- They can be constructed using ('.=.') and ('@|'), or using 'showField' and
-- 'appendFields'.
type ShowFields = ShowS
-- | Show a record. The first argument is the constructor name.
-- The second represents the set of record fields.
showRecord :: String -> ShowFields -> PrecShowS
showRecord con showFields _ =
showString con . showSpace . showChar '{' . showFields . showChar '}'
-- | Show a single record field: a field name and a value separated by @\'=\'@.
showField :: String -> PrecShowS -> ShowFields
showField field showX =
showString field . showString " = " . showX 0
infixr 8 .=.
-- | Show a single record field: a field name and a value separated by @\'=\'@.
--
-- This is an infix shorthand for 'showField' when the value type is an
-- instance of 'Show'.
--
-- > field .=. x = showField field (flip showsPrec x)
(.=.) :: Show a => String -> a -> ShowFields
field .=. x = showField field (flip showsPrec x)
-- | Empty set of record fields.
noFields :: ShowFields
noFields = id
infixr 1 `appendFields`, &|
-- | Separate two nonempty sets of record fields by a comma.
appendFields :: ShowFields -> ShowFields -> ShowFields
appendFields showFds1 showFds2 = showFds1 . showString ", " . showFds2
-- | An infix synonym of 'appendFields'.
(&|) :: ShowFields -> ShowFields -> ShowFields
(&|) = appendFields
-- Helpers
showSpace :: ShowS
showSpace = (' ' :)
appPrec, appPrec1 :: Int
appPrec = 10
appPrec1 = 11