派生位置展示

时间:2018-12-20 08:28:56

标签: haskell metaprogramming typeclass template-haskell deriving

注意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)

我希望答案能够避免出现这种样板。

1 个答案:

答案 0 :(得分:3)

手工表演

实现Show的默认方法需要大量样板。 show-combinators可以解决这一问题,将所需的代码简化为基本要点:

instance Show ... where
  showPrec = flip (\(FuncType i o) -> showCon "FuncType" @| i @| o)

我认为这种解决方案是最简单的。没有扩展,没有引擎盖下的类型类魔术。只是普通的函数式编程。

(免责声明:我写了这篇文章中提到的两个库。)

使用GHC泛型

Show中有generic-data的通用实现:gshowsPreclink 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的反作用)。我们可以使用Data将正常声明的类型映射为toData :: a -> Data (Rep a) p类型。

最后,我们可以将gshowsPrec库中的generic-data函数直接应用于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)