type-reflection-1.0: src/Type/Reflection/Name.hs
{-# LANGUAGE OverloadedStrings #-}
{-|
Description : Utilities for controlling how to serialize type representations into strings
Copyright : Richard Eisenberg
License : MIT
Maintainer : rae@richarde.dev
Stability : experimental
This module provides three different ways of serializing a 'TypeRep' into a 'Text':
qualifying no names, qualifying all names, or package-qualifying all names. In order
to support meaningful notions of equality on these string representations (for e.g.
using them as keys in a map), @newtype@s are provided for each format.
-}
module Type.Reflection.Name (
-- * Unqualified names
Unqualified(..), getUnqualified, showUnqualified,
-- * Qualified names
Qualified(..), getQualified, showQualified,
-- * Package-qualified names
PackageQualified(..), getPackageQualified, showPackageQualified,
-- * Class-based access
TypeText(..), defaultRenderTypeRep,
) where
import Data.String ( IsString(..) )
import Data.Hashable ( Hashable )
import Data.Text ( Text )
import Type.Reflection
------------------------------------------------------
-- TypeText, a class that describes how TyCons are rendered
-- | The 'TypeText' class describes how a 'TyCon' is rendered
-- into a textual representation, and it is used to render a 'TypeRep'
-- by 'renderTypeRep'. The 'IsString' superclass is needed to insert
-- connectives like spaces, dots, and parentheses, and the 'Monoid'
-- superclass is needed to stitch these pieces together.
--
-- Only 'renderTyCon' is needed for instances; if 'renderTypeRep' is
-- omitted, then the default rendering, via 'defaultRenderTypeRep' is
-- used. See that function for more details.
--
-- The only consistency law for 'TypeText' is this, holding for all
-- @tr@ of type 'TypeRep' :
--
-- > case tr of Con' tc [] -> renderTypeRep tr == renderTyCon tc
-- > _ -> True
--
-- In other words, rendering a 'TypeRep' consisting only of a 'TyCon'
-- is the same as rendering the 'TyCon' itself. If the 'TypeRep' is not
-- a plain 'TyCon' (with no kind arguments), then there are no applicable
-- laws. Note that the law uses '==', even though 'Eq' is not a superclass
-- of 'TypeText'.
class (IsString tt, Monoid tt, Ord tt) => TypeText tt where
renderTyCon :: TyCon -> tt
renderTypeRep :: TypeRep t -> tt
renderTypeRep = defaultRenderTypeRep
{-# MINIMAL renderTyCon #-}
------------------------------------------------------
-- Unqualified
-- | A rendering of a type where no components have module or package qualifications.
-- Data constructors used in types are preceded with a @'@, and infix operator types
-- are printed without parentheses. Uses 'defaultRenderTypeRep' to render types; see
-- that function for further details.
newtype Unqualified = MkUnqualified Text
deriving (Eq, Ord, Show, Hashable, Semigroup, Monoid, IsString)
instance TypeText Unqualified where
renderTyCon = fromString . tyConName
-- | Extract the contents of an 'Unqualified' rendering
getUnqualified :: Unqualified -> Text
getUnqualified (MkUnqualified str) = str
-- | Convert a 'TypeRep' into an 'Unqualified' representation
showUnqualified :: TypeRep t -> Unqualified
showUnqualified = renderTypeRep
----------------------------------------------------------
-- Qualified
-- | A rendering of a type where all components have module qualifications.
-- Data constructors used in types are preceded first with their module qualification,
-- and then with a @'@, and infix operator types
-- are printed without parentheses. So we have @Data.Proxy.'Proxy@ for the v'Proxy' constructor.
-- Uses 'defaultRenderTypeRep' to render types; see
-- that function for further details.j
--
-- Note that module qualifications arise from the module that defines a type, even if this
-- is a hidden or internal module. This fact means that internal-structure changes in
-- your libraries may affect how your types are rendered, including changes in e.g. @base@.
newtype Qualified = MkQualified Text
deriving (Eq, Ord, Show, Hashable, Semigroup, Monoid, IsString)
instance TypeText Qualified where
renderTyCon tc = fromString (tyConModule tc) <> "." <> fromString (tyConName tc)
-- | Extract the contents of a 'Qualified' rendering
getQualified :: Qualified -> Text
getQualified (MkQualified str) = str
-- | Convert a 'TypeRep' into a 'Qualified' representation
showQualified :: TypeRep t -> Qualified
showQualified = renderTypeRep
------------------------------------------------------------
-- PackageQualified
-- | A rendering of a type where all components have package and module qualifications
-- Data constructors used in types are preceded first with their package qualification,
-- and then their module qualification,
-- and then with a @'@, and infix operator types
-- are printed without parentheses. So we have @base.Data.Proxy.'Proxy@ for the v'Proxy' constructor.
-- Uses 'defaultRenderTypeRep' to render types; see
-- that function for further details.
--
-- Note that module qualifications arise from the module that defines a type, even if this
-- is a hidden or internal module. This fact means that internal-structure changes in
-- your libraries may affect how your types are rendered, including changes in e.g. @base@.
newtype PackageQualified = MkPackageQualified Text
deriving (Eq, Ord, Show, Hashable, Semigroup, Monoid, IsString)
instance TypeText PackageQualified where
renderTyCon tc = fromString (tyConPackage tc) <> "." <>
fromString (tyConModule tc) <> "." <>
fromString (tyConName tc)
-- | Extract the contents of a 'PackageQualified' name
getPackageQualified :: PackageQualified -> Text
getPackageQualified (MkPackageQualified str) = str
-- | Convert a 'TypeRep' into a 'PackageQualified' representation
showPackageQualified :: TypeRep t -> PackageQualified
showPackageQualified = renderTypeRep
----------------------------------------------
-- Worker
-- | Render a 'TypeRep' into an instance of 'TypeText'. This follows the following
-- rules for how to render a type:
--
-- * Function types are rendered infix. If a function takes another function as an
-- argument, the argument function is rendered in parentheses (as required by the
-- right-associative @->@ operator).
--
-- * All other type constructors are rendered prefix, including the list constructor @[]@
-- and the tuple constructors e.g. @(,,)@, along with any other type operators.
--
-- * All kind arguments to type constructors are rendered, with \@ prefixes. Any
-- non-atomic (that is, not simply a type constructor with no kind arguments) kinds
-- are enclosed in parentheses.
--
-- * Non-atomic arguments are enclosed in parentheses.
--
-- * Type synonyms are always expanded. This means that we get @[] Char@, not @String@,
-- and we get @TYPE ('BoxedRep 'Lifted)@, not @Type@.
defaultRenderTypeRep ::
forall tn outer_t.
TypeText tn =>
TypeRep outer_t -> tn
defaultRenderTypeRep = go
where
go :: TypeRep t -> tn
go (Fun arg@(Fun {}) res) = "(" <> go arg <> ") -> " <> go res
go (Fun arg res) = go arg <> " -> " <> go res
go (App t1 t2@(Con' _ [])) = go t1 <> " " <> go t2
go (App t1 t2) = go t1 <> " (" <> go t2 <> ")"
go (Con' tc kinds) = renderTyCon tc <> foldMap go_kind kinds
go_kind :: SomeTypeRep -> tn
go_kind (SomeTypeRep kind_rep@(Con' _ [])) = " @" <> go kind_rep
go_kind (SomeTypeRep kind_rep) = " @(" <> go kind_rep <> ")"