使用泛型静态分析类型

Statically analyze type with Generics

为了在简单产品上为 FromRow-种类class 通用派生实例,我想静态分析一个类型而不实际提供任何具体术语。

示例:

class FromRow a where
  rowrep :: Proxy a -> [Maybe NativeType]
  fromRow :: Statement -> IO a

data User = User
  { name :: String
  , uid :: Int
  , active :: Bool
  } deriving (Show, Generic)

“技巧”是我在获取任何数据之前需要 rowrep - 可能会覆盖某些甚至所有列的默认值。在我想使用 rowrep 的时间点,我还没有术语,因此 Proxy。编写 FromRow 的实例可能会变得非常乏味且容易出错,所以我想我会为 Generic 类型添加一个 default 实现。然而,它似乎得到了我需要提供给定类型的术语的通用表示(from :: a -> Rep a),类型本身的知识是不够的。

事实上,我们可以看到这不仅仅是 API 的噱头,而且通用表示确实具有值:

> from (User "foo" 1 True)
M1 {unM1 = M1 {unM1 = M1 {unM1 = K1 {unK1 = "foo"}} :*: (M1 {unM1 = K1 {unK1 = 1}} :*: M1 {unM1 = K1 {unK1 = True}})}}

有没有办法使用 Generic 来分析事物的结构和类型,即我们不关心实际值的地方?如果做不到这一点,TH 会涵盖这个用例吗?

您无需提供术语。您不需要 Rep a 的值,您只需要将其作为 类型 进行检查,而无需使用 from.

就此而言,您也不需要 Proxy,在 TypeApplications 出现之前,这始终只是弥补 Haskell 不足的一个丑陋的 hack。

{-# LANGUAGE TypeFamilies, TypeApplications, AllowAmbiguousTypes
           , ScopedTypeVariables, UnicodeSyntax, DefaultSignatures #-}

import Data.Kind (Type)

data NativeType = Intish | Floatish

class FromRow a where
  rowrep :: [Maybe NativeType]

instance FromRow Int where
  rowrep = [Just Intish]

现在,为了编写泛型实例,我们首先需要一个助手 class 来对 Rep:

进行类型级检查
class GFromRow (g :: k -> Type) where
  gRowrep :: [Maybe NativeType]

instance ∀ i c f . GFromRow f => GFromRow (M1 i c f) where
  gRowRep = gRowRep @f
instance ∀ i c . FromRow c => GFromRow (K1 i c) where
  gRowRep = rowRep @c
...  -- instances for U1 and f:*:g

那么默认实现将是

class FromRow a where
  rowrep :: [Maybe NativeType]
  default rowrep :: GFromRow (Rep a) => [Maybe NativeType]
  rowrep = gRowrep @(Rep a)