为所有高阶种类声明类型

时间:2019-05-08 18:44:21

标签: 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 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对其进行包装。

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

data A (f :: * -> *) a = ...

不可用,A (Proxy 1) (Proxy 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

无论如何,所有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上用GetN来为Proxy n编写族实例成为非法。我愿意输入基于类的解决方案,我们可以在其中提供:

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)

最终,这导致一般性地得出遍历签名。给定源上下文和目标上下文,例如A f aA g b,在通用表示中,我将在K1节点上拥有UnANums之类的类型,从中我可以得出源和要遍历的目标。

2 个答案:

答案 0 :(得分:1)

如何?

{-# 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带有两个(->)类型的参数,但是Proxy 0Proxy 1仍然有效。

答案 1 :(得分:0)

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

{-# 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

然后,我为Type(又名*)创建一个基本案例,并为所有更高种类的归纳案例创建最终导致HasNProxyK的一个案例。

在GHCI会话中进行检查:

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

> :kind! NProxyK 3 (,,,,)
NProxyK 3 (,,,,) :: k
= NProxyK (* -> * -> * -> * -> * -> *) k 3 (,,,,)

我们看到此代理几乎已准备就绪。返回的lhs表明该类型具有种类k,但是rhs上的第一个kind参数(我认为对应于class参数)具有适当的种类。

我们可以在呼叫站点为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