通常确定数据类型的一致性

fre*_*fox 1 haskell

我想计算某类数据类型的“ arity” 。即具有单个构造函数和一定数量字段的数据类型。例如data T a = T Int () String a。然后,“ arity”将是字段数。对于T a这将是4。我设想一个带有如下签名的函数:

forall a . C a => Int
Run Code Online (Sandbox Code Playgroud)

对于一些适当的选择C。我知道如果我有Generic a某种类型的话,a我会得到from :: a -> Rep a x,但是请注意,这将需要一个具体的值a,我对静态地进行计算很感兴趣。这有可能吗?我也考虑过了Typeable,但是我不太了解API。

Li-*_*Xia 7

我们可以使用泛型。在整个答案中使用了很多扩展,这些扩展对于各种元编程都很常见。我将第一次使用它们,但有关更多详细信息,请参阅其他资源,例如GHC用户指南(扩展列表)或Haskell Wiki。

data T = T Int Bool String deriving Generic

-- Used extension: DeriveGeneric
Run Code Online (Sandbox Code Playgroud)

派生实例包括类型家庭实例Rep,用于构造类型的通用表示形式T。Rep T使用一组固定的中发现的多种类型的GHC.Generics模块:

type Rep T = M1 D _ ((M1 C _ (K1 _ Int) :*: M1 C _ (K1 _ Bool)) :*: M1 C _ (K1 _ String))
--
-- Irrelevant details hidden in underscores.
-- There's actually a few more M1's as well
--
-- You can see the full and real details in a ghci session with this command
--   :kind! Rep T
Run Code Online (Sandbox Code Playgroud)

Arity功能

我们将定义一个类型级别的函数来检查该结构并计算字段数。这是它的签名:

type family Arity (f :: Type -> Type) :: Nat
-- If T is a type with one constructor (C x1 ... xn),
-- Arity (Rep T) is the arity n of that constructor

-- Used extensions: TypeFamilies, DataKinds
Run Code Online (Sandbox Code Playgroud)

当涉及泛型表示时,我们可以假设它TT = (Type->Type)类似于ADT,具有以下构造函数:

-- We can pretend that there is this data type TT
-- such that Arity is a function (TT -> Nat)
data TT
  = M1 Type Meta TT
  | (:+:) TT TT
  | V1
  | (:*:) TT TT
  | U1
  | K1 Type Type
Run Code Online (Sandbox Code Playgroud)

非常(太?)简短的概述。M1包含诸如类型名称(包括模块和包),构造函数名称,构造函数是否使用记录符号,字段严格性等信息,V1并且(:+:)用于零个或多个构造函数的类型,因此它们与我们无关。U1代表null构造函数,而(:*:)拆分n进制构造函数,在任一侧代表一半字段。K1标记一个构造函数字段。

我们Arity通过为函数指定类型实例来定义函数。但是,实际上,对于最初的理解,忽略type instance关键字,并假装Arity是通常通过模式匹配定义的功能。

查看Rep T上面的表示,我们首先遇到一个M1节点,我们将忽略它并递归调用Arity它的内容。

type instance Arity (M1 i c f) = Arity f
Run Code Online (Sandbox Code Playgroud)

然后,我们(:*:)将把一组字段分为两部分。我们以递归方式计算它们的arities并将它们加起来。

type instance Arity (f :*: g) = Arity f + Arity g

-- Used extensions: TypeOperators, UndecidableInstances
Run Code Online (Sandbox Code Playgroud)

U1 代表无效构造函数,

type instance Arity U1 = 0
Run Code Online (Sandbox Code Playgroud)

并且K1是单个字段。

type instance (K1 i a) = 1
Run Code Online (Sandbox Code Playgroud)

现在,给定通用类型T(即带有的实例Generic)的Arity (Rep T)是它的Arity,作为类型级别Nat。在ghci中,我们可以使用

:kind! Arity (Rep T)
Run Code Online (Sandbox Code Playgroud)

使用GHC.TypeNats.natVal将其转换为一个Natural值(如Integer,但非负)。

-- Calculate the arity of the constructor of a generic type `a`.
-- `a` must have a single constructor.
arity :: forall a. (Generic a, KnownNat (Arity (Rep a))) => Natural
arity = natVal (Proxy @(Arity (Rep a)))

-- Used extensions:
--   ScopedTypeVariables,
--   AllowAmbiguousTypes, TypeApplications,
--   FlexibleContexts
Run Code Online (Sandbox Code Playgroud)

我们将任何泛型类型的arity T用作value arity @T,可以使用fromIntegral :: Natural -> Integer例如进行转换。

main = print (arity @T)
Run Code Online (Sandbox Code Playgroud)

完整要点:https : //gist.github.com/Lysxia/10f1da354f051b2d2eb24f6aace1bf9c


luq*_*qui 5

为了在评论中回答我的问题,下面是一个示例,说明如何查找函数的稀疏性。

{-# LANGUAGE ScopedTypeVariables, FlexibleInstances #-}

import Data.Proxy

class Arity a where
    arityP :: Proxy a -> Int

instance {-# OVERLAPPABLE #-} Arity a where
    arityP _ = 0

instance {-# OVERLAPPING #-} Arity b => Arity (a -> b) where
    arityP f = 1 + arityP (Proxy :: Proxy b)

arity :: forall a. Arity a => a -> Int
arity _ = arityP (Proxy :: Proxy a)
Run Code Online (Sandbox Code Playgroud)

如果您对所涉及的习语感到满意,我觉得这是不言而喻的。对于您要查询的用例,在试图找到数据类型/构造函数的泛用的情况下,这将很好地工作。

ghci> arity T
4
Run Code Online (Sandbox Code Playgroud)

如果您尝试在多态函数上使用它,那么它不起作用。

ghci> arity id
<interactive>:2:1: error:
• Overlapping instances for Arity a0 arising from a use of ‘arity’
  Matching instances:
    instance [overlappable] [safe] Arity a -- Defined at arity.hs:10:31
    instance [overlapping] [safe] Arity b => Arity (a -> b)
      -- Defined at arity.hs:13:30
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为id根据实例化的位置,它可能具有多个arity

id :: Int -> Int
id :: (Int -> Int) -> Int -> Int
Run Code Online (Sandbox Code Playgroud)

这实际上增加了我对这种方法的信心。让我知道它是如何工作的。

  • 一个类型可能有许多构造函数,每个构造函数的类型不同。GHC不知道您的类型只有一个构造函数,除非您使用`Generics`-参见夏丽瑶的答案。因此,如果需要构造函数的方便性,则必须提供一个构造函数,即:一个值,而不仅仅是数据类型。 (2认同)