导出位置显示
Derive positional Show
注意 T 5
在
中的显示方式
> newtype T = T { getT :: Int } deriving Show
> T 5
T {getT = 5}
对于使用记录语法声明的类型,是否有某种方法可以派生出 Show 的位置、非记录语法变体?
(顺便说一句,T
只是一个简单的例子来解释这个问题,我正在寻找用记录语法定义的任何类型的一般答案)
我会满意的一些选项:
- 图书馆提供的第 TH 代
- 基于
Generic
的推导(其中手动实例指的是现有函数)
- 手动实施
Show
个实例的简单方法/指南
- 我没有想到的任何其他想法
对于更复杂的示例,我有 this hand-written instance:
instance ... where
showsPrec p (FuncType i o) =
showParen (p > 0)
(("FuncType " <>) . showsPrec 1 i . (" " <>) . showsPrec 1 o)
我希望答案能够避免这种样板文件。
手动执行显示
实现 Show
的默认方式需要大量样板文件。这是由 show-combinators 处理的,将所需的代码减少到最基本的部分:
instance Show ... where
showPrec = flip (\(FuncType i o) -> showCon "FuncType" @| i @| o)
我认为这个解决方案是最简单的。没有扩展,引擎盖下没有类型类魔法。只是简单的函数式编程。
(免责声明:我编写了本文中提到的两个库 post。)
使用 GHC 泛型
generic-data
中有一个 Show
的通用实现:gshowsPrec
(link to source)。但它将使用记录语法声明的类型显示为记录。
重做实施
一种方法当然是复制实现并删除记录的特殊处理。
{- 1. The usual boilerplate -}
class GShow p f where
gPrecShows :: p (ShowsPrec a) -> f a -> PrecShowS
instance GShow p f => GShow p (M1 D d f) where
gPrecShows p (M1 x) = gPrecShows p x
instance (GShow p f, GShow p g) => GShow p (f :+: g) where
gPrecShows p (L1 x) = gPrecShows p x
gPrecShows p (R1 y) = gPrecShows p y
{- 2. A simplified instance for (M1 C), that shows all constructors
using positional syntax. The body mostly comes from the instance
(GShowC p ('MetaCons s y 'False) f). -}
instance (Constructor c, GShowFields p f) => GShow p (M1 C c f) where
gPrecShows p x = gPrecShowsC p (conName x) (conFixity x) x
where
gPrecShowsC p name fixity (M1 x)
| Infix _ fy <- fixity, k1 : k2 : ks <- fields =
foldl' showApp (showInfix name fy k1 k2) ks
| otherwise = foldl' showApp (showCon cname) fields
where
cname = case fixity of
Prefix -> name
Infix _ _ -> "(" ++ name ++ ")"
fields = gPrecShowsFields p x
类型手术
(以 my blogpost 命名的部分,但此线程的情况要简单得多。)
另一种方法是转换我们类型的通用表示,假装它不是使用记录语法声明的。幸运的是,唯一的区别是幻像类型参数,因此转换可以像 运行 时的 coerce
一样简单。
unsetIsRecord ::
Coercible (f p) (UnsetIsRecord f p) => Data f p -> Data (UnsetIsRecord f) p
unsetIsRecord = coerce
-- UnsetIsRecord defined at the end
Data
newtype 基本上是从通用表示中创建数据类型(在某种意义上,这与 Generic
所做的相反)。我们可以使用 toData :: a -> Data (Rep a) p
.
将正常声明的类型映射到 Data
类型
最后,我们可以直接将generic-data
库中的gshowsPrec
函数应用到unsetIsRecord
的输出。
instance Show T where
showsPrec n = gshowsPrec n . unsetIsRecord . toData
UnsetIsRecord
理想情况下应该在 generic-data
中,但由于它还没有,这里是一个可能的实现:
type family UnsetIsRecord (f :: * -> *) :: * -> *
type instance UnsetIsRecord (M1 D m f) = M1 D m (UnsetIsRecord f)
type instance UnsetIsRecord (f :+: g) = UnsetIsRecord f :+: UnsetIsRecord g
type instance UnsetIsRecord (M1 C ('MetaCons s y _isRecord) f) = M1 C ('MetaCons s y 'False) f)
注意 T 5
在
> newtype T = T { getT :: Int } deriving Show
> T 5
T {getT = 5}
对于使用记录语法声明的类型,是否有某种方法可以派生出 Show 的位置、非记录语法变体?
(顺便说一句,T
只是一个简单的例子来解释这个问题,我正在寻找用记录语法定义的任何类型的一般答案)
我会满意的一些选项:
- 图书馆提供的第 TH 代
- 基于
Generic
的推导(其中手动实例指的是现有函数) - 手动实施
Show
个实例的简单方法/指南 - 我没有想到的任何其他想法
对于更复杂的示例,我有 this hand-written instance:
instance ... where
showsPrec p (FuncType i o) =
showParen (p > 0)
(("FuncType " <>) . showsPrec 1 i . (" " <>) . showsPrec 1 o)
我希望答案能够避免这种样板文件。
手动执行显示
实现 Show
的默认方式需要大量样板文件。这是由 show-combinators 处理的,将所需的代码减少到最基本的部分:
instance Show ... where
showPrec = flip (\(FuncType i o) -> showCon "FuncType" @| i @| o)
我认为这个解决方案是最简单的。没有扩展,引擎盖下没有类型类魔法。只是简单的函数式编程。
(免责声明:我编写了本文中提到的两个库 post。)
使用 GHC 泛型
generic-data
中有一个 Show
的通用实现:gshowsPrec
(link to source)。但它将使用记录语法声明的类型显示为记录。
重做实施
一种方法当然是复制实现并删除记录的特殊处理。
{- 1. The usual boilerplate -}
class GShow p f where
gPrecShows :: p (ShowsPrec a) -> f a -> PrecShowS
instance GShow p f => GShow p (M1 D d f) where
gPrecShows p (M1 x) = gPrecShows p x
instance (GShow p f, GShow p g) => GShow p (f :+: g) where
gPrecShows p (L1 x) = gPrecShows p x
gPrecShows p (R1 y) = gPrecShows p y
{- 2. A simplified instance for (M1 C), that shows all constructors
using positional syntax. The body mostly comes from the instance
(GShowC p ('MetaCons s y 'False) f). -}
instance (Constructor c, GShowFields p f) => GShow p (M1 C c f) where
gPrecShows p x = gPrecShowsC p (conName x) (conFixity x) x
where
gPrecShowsC p name fixity (M1 x)
| Infix _ fy <- fixity, k1 : k2 : ks <- fields =
foldl' showApp (showInfix name fy k1 k2) ks
| otherwise = foldl' showApp (showCon cname) fields
where
cname = case fixity of
Prefix -> name
Infix _ _ -> "(" ++ name ++ ")"
fields = gPrecShowsFields p x
类型手术
(以 my blogpost 命名的部分,但此线程的情况要简单得多。)
另一种方法是转换我们类型的通用表示,假装它不是使用记录语法声明的。幸运的是,唯一的区别是幻像类型参数,因此转换可以像 运行 时的 coerce
一样简单。
unsetIsRecord ::
Coercible (f p) (UnsetIsRecord f p) => Data f p -> Data (UnsetIsRecord f) p
unsetIsRecord = coerce
-- UnsetIsRecord defined at the end
Data
newtype 基本上是从通用表示中创建数据类型(在某种意义上,这与 Generic
所做的相反)。我们可以使用 toData :: a -> Data (Rep a) p
.
Data
类型
最后,我们可以直接将generic-data
库中的gshowsPrec
函数应用到unsetIsRecord
的输出。
instance Show T where
showsPrec n = gshowsPrec n . unsetIsRecord . toData
UnsetIsRecord
理想情况下应该在 generic-data
中,但由于它还没有,这里是一个可能的实现:
type family UnsetIsRecord (f :: * -> *) :: * -> *
type instance UnsetIsRecord (M1 D m f) = M1 D m (UnsetIsRecord f)
type instance UnsetIsRecord (f :+: g) = UnsetIsRecord f :+: UnsetIsRecord g
type instance UnsetIsRecord (M1 C ('MetaCons s y _isRecord) f) = M1 C ('MetaCons s y 'False) f)