Haskell 为所有高阶类型声明一个类型

Haskell 为所有高阶类型声明一个类型,haskell,type-families,higher-kinded-types,Haskell,Type Families,Higher Kinded Types,我有一种感觉,我在问不可能的事情,但事情是这样的 我想将类型构造函数与一个完全应用的版本相关联,该版本使用自然数表示类型级别的参数。以下是一个ghci会话示例及其预期用途: ghci> :kind! MKNumbered Maybe MKNumbered Maybe :: * = Maybe (Proxy Nat 1) ghci> :kind! MKNumbered Either MKNumbered Either :: * = Either (Proxy Nat 1) (Proxy

我有一种感觉,我在问不可能的事情,但事情是这样的

我想将类型构造函数与一个完全应用的版本相关联,该版本使用自然数表示类型级别的参数。以下是一个ghci会话示例及其预期用途:

ghci> :kind! MKNumbered Maybe
MKNumbered Maybe :: *
= Maybe (Proxy Nat 1)
ghci> :kind! MKNumbered Either
MKNumbered Either :: *
= Either (Proxy Nat 1) (Proxy Nat 2)
为了减少上面的噪音,我得到了如下的结果

Maybe  >----> Maybe 1
Either >----> Either 1 2 
事实证明,我可以接近以下类型的族。它们实际上使用了一个额外的参数,指定了参数的总数,但这没关系

type MkNumbered f n = UnU (MkNumbered_ (U f) 1 n)
type family MkNumbered_ (f :: k) (i::Nat) (n::Nat) :: j where
  MkNumbered_ (U f) i i = U (f (Proxy i))
  MkNumbered_ (U f) i n = MkNumbered_ (U (f (Proxy i))) (i+1) n

data U (a::k)
type family UnU f :: * where
  UnU (U f) = f
U
类型是另一个代理,它似乎是获得我想要的行为所必需的。如果我有一个完全应用的
U
,即
U(a::*)
我可以用
UnU
打开它

上面的缺点是,由于代理i:*,
MkNumbered
只能处理带有
*
变量的构造函数。编号

data A (f :: * -> *) a = ...
如果退出,
A(代理1)(代理2)
Proxy 1
参数中不起作用。我应该能够通过引入一些特定的编号代理来增强
MkNumbered

data NPxy1 (n :: Nat)
data NPxy2 (n :: Nat) (a :: i)
data NPxy3 (n :: Nat) (a :: i) (b :: j)
...
这会让我有如下行为:

ghci> :kind! MKNumbered A
MKNumbered A :: *
= A (NPxy2 Nat 1) (NPxy1 Nat 2)
这很有帮助,仅仅这三个NPxy定义就可能涵盖了大多数高阶的情况。但我想知道是否有一种方法可以增强这一点,这样我就可以涵盖所有
k->j->…->*案例


顺便说一句,我并不真的希望处理像这样的类型

data B (b::Bool) = ...   
我需要这样的非法定义:

data NPxyBool (n :: Nat) :: Bool
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PolyKinds #-}

import GHC.TypeLits hiding ( (*) )
import Data.Kind

class HasNProxyK j where
  data NProxyK (n :: Nat) (a::j) :: k
instance HasNProxyK Type where
  data NProxyK n a = NProxyK0
instance HasNProxyK k => HasNProxyK (j -> k) where
  data NProxyK n f = NProxyKSuc -- or NProxyKS (ProxyK n (f a))
在任何情况下,所有的
Bool
类型似乎都已经被采用了。更进一步,我很高兴得知有一种方法可以创建一些数据

data UndefinedN (n :: Nat) :: forall k . k
我称之为
UndefinedN
,因为它似乎是种类级别的底部


编辑:预期用途

我预期用途的关键是查询代理参数的类型

type family GetN s (a :: k) :: k 

GetN (Either Int Char) (Proxy 1) ~ Int
但是,我还要求,如果代理索引是除
Proxy n
之外的其他特定类型,则只返回该类型

GetN (Either Int Char) Maybe ~ Maybe
但是,
Proxy n
的任何类型族解决方案都会使在lhs上使用
Proxy n
GetN
编写族实例成为非法。我对基于类型类的解决方案持开放态度,我们可以:

instance (Proxy n ~ pxy, GetNat s n ~ a) => GetN s pxy a where... 
但是,我还需要将具体的值解析为它们自己,这会导致冲突的实例定义,我也很难解析这些定义

剩下的部分只是为了提供信息,但是有了上面的内容,我应该能够从我的代理参数类型派生子数据。例如,在上面填写我对
A
的定义:

data A f a = A { unA :: f (Maybe a) }
unA
处的子数据(作为编号参数)如下所示:

type UnANums = (Proxy 1) (Maybe (Proxy 2))
我想派生一个类型族(或其他方法),它基于超级数据的一个示例创建一个具体的子数据

type family GetNs s (ns :: k) :: k
GetNs (A [] Int) UnANums ~ [Maybe Int]
GetNs (A (Either String) Char) UnANums ~ Either String (Maybe Char)
最终,这将导致一般性地导出遍历签名。给定一个源和目标上下文,例如
afa
agb
,在
K1
节点的通用表示中,我将具有类似
unums
的类型,我可以从中导出要遍历的源和目标。

这是如何的:

{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
module SO56047176 where
import GHC.TypeLits
import Data.Functor.Compose -- for example

type family Proxy (n :: Nat) :: k

type Maybe_ = Maybe (Proxy 0)
type Either__ = Either (Proxy 0) (Proxy 1)
type Compose___ = Compose (Proxy 0) (Proxy 1) (Proxy 2)

Data.Functor.Compose
接受两个类似于
(>)
的参数,但是
代理0
代理1
仍然有效。

我通过组合类型和数据族找到了一个解决方案。从数据定义开始:

data NPxyBool (n :: Nat) :: Bool
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PolyKinds #-}

import GHC.TypeLits hiding ( (*) )
import Data.Kind

class HasNProxyK j where
  data NProxyK (n :: Nat) (a::j) :: k
instance HasNProxyK Type where
  data NProxyK n a = NProxyK0
instance HasNProxyK k => HasNProxyK (j -> k) where
  data NProxyK n f = NProxyKSuc -- or NProxyKS (ProxyK n (f a))
我声明一个类型类
HasNProxyK
,其中的种类将是实例。相关数据,
NProxyK
需要一个
Nat
和一些适当类型的变量,
j
。此数据族的返回类型将是其他类型,
k

然后,我为
类型
(又名
*
)创建了一个基本情况,并为所有更高类型创建了一个归纳情况,最终导致了一个具有
HasnProxy
的类型

在GHCI会话中检查此问题:

> :kind! NProxyK 3 Int
NProxyK 3 Int :: k
= NProxyK * k 3 Int

> :kind! NProxyK 3 (,,,,)
NProxyK 3 (,,,,) :: k
= NProxyK (* -> * -> * -> * -> * -> *) k 3 (,,,,)
我们看到这个代理几乎准备好了。返回的lhs显示该类型有一个kind
k
,但是rhs上的第一个kind参数(我相信它对应于class参数)有一个适当的kind

我们可以在调用站点为k指定适当的种类,而我只是创建了一个类型族,以确保
NProxyK
种类与类种类匹配

type family ToNProxyK (n :: Nat) (a :: k) :: k where
  ToNProxyK n (a :: Type) = NProxyK n a
  ToNProxyK n (a :: j -> k) = NProxyK n a

>:kind! ToNProxyK 1 (,,,,)
ToNProxyK 1 (,,,,) :: * -> * -> * -> * -> * -> *
= NProxyK
  (* -> * -> * -> * -> * -> *) (* -> * -> * -> * -> * -> *) 1 (,,,,)
现在,可以使用类似以下系列的方法恢复
Nat

type family LookupN (x :: k) :: Maybe Nat where
  LookupN (NProxyK n a) = Just n
  LookupN x             = Nothing

>:kind! (LookupN (ToNProxyK 3 Maybe))
(LookupN (ToNProxyK 3 Maybe)) :: Maybe Nat
= 'Just Nat 3
>:kind! (LookupN Maybe)
(LookupN Maybe) :: Maybe Nat
= 'Nothing Nat

你看过Haskell仿制药吗?GHC仿制药?对我认为这在这里没有帮助。或者是别的什么,或者是我错过了什么。谢谢。我尝试了一些类似的方法,但需要在某个时候恢复nat。例如,我将如何在实例头/上下文中匹配代理n并在主体中使用它?如果有必要的话,我可以在明天尝试对此进行扩展。在身体中使用n,也就是说,不是代理nI重新访问了这个,它似乎起到了作用。在接受之前,我打算再玩一会儿。为了完整性,我还希望看到一个Nat恢复的用例,例如通过等式约束。如果合适的话,我可以更新答案。@trevorcook是的,如果你能展示你想如何使用
Nat
,那会很有用。我不知道有哪一个用例仅仅通过一个arity类型的家族就不能更好地服务。我补充了我的问题。你所提议的是“家庭型”吗?谢谢