如何使用单例库中的依赖对类型Sigma?

时间:2018-04-14 05:23:36

标签: haskell types dependent-type singleton-type

如何使用Sigma库中的从属对类型singletons

假设存在以下类型索引列表和复制函数:

data Vect :: Nat -> Type -> Type where
  VNil :: Vect 0 a
  VCons :: a -> Vect n a -> Vect (n + 1) a

replicateVec :: forall n a. Sing n -> a -> Vect n a

(您可以在this question)中找到replicateVec的几种不同实现。

我想创建一个函数replicateVecSigma,在依赖对中返回Vect。理想情况下,它将如下所示:

replicateVecSigma :: Natural -> Sigma Nat (\n -> Vect n String)

如何使用Sigma来编写此函数?如何写出函数的类型?

实际上,我可以按照以下方式实施replicateVecSigma,但它看起来并不干净:

data Foo a b (m :: TyFun Nat Type)

type instance Apply (Foo a b) (n :: Nat) = a n b

replicateVecSigma :: Natural -> Sigma Nat (Foo Vect String)
replicateVecSigma i =
  case toSing i of
    SomeSing snat -> snat :&: replicateVec snat "hello"

我不得不声明这个Foo类型和Apply实例只是为了使用Sigmasingletons库是否提供了更轻松地使用Sigma的方法?

您可以找到我的完整代码here

为了完整起见,这里是Sigma

的定义
data Sigma (s :: Type) :: (s ~> Type) -> Type where
  (:&:) :: forall s t fst. Sing (fst :: s) -> t @@ fst -> Sigma s t

以下是~>

type a ~> b = TyFun a b -> *

data TyFun :: * -> * -> *

instance (SingKind k1, SingKind k2) => SingKind (k1 ~> k2)

type instance Demote (k1 ~> k2) = Demote k1 -> Demote k2

class SingKind k where
  type family Demote k :: * = r | r -> k
  fromSing :: forall (a :: k). Sing a -> Demote k
  toSing :: Demote k -> SomeSing k

以下是@@

type a @@ b = Apply a b

type family Apply (f :: k1 ~> k2) (x :: k1) :: k2

singletonsApply定义了一堆实例。

TyFunApply如何运作?

以上是上述三个问题:

  1. 如何使用Sigma编写replicateVecSigma :: Natural -> Sigma Nat (\n -> Vect n String)等函数?
  2. 我可以使用上面的replicateVecSigma类型编写Foo,但似乎有太多额外的工作。有更简单的方法吗?
  3. TyFunApply如何运作?

2 个答案:

答案 0 :(得分:3)

在这种特定情况下,我们想要计算类型级lambda

\n -> Vect n String

不幸的是,我们在类型级别没有lambdas。至多,我们可以像OP那样定义类型族。但是,我们可以用pointfree样式重写lambda:

\n -> flip Vect String n    -- pseudocode
flip Vect String

我们可以使用singletons类型级Flip类型函数将此想法变为实际类型。在这里,我们希望部分地应用Flip两个参数(在饱和调用中需要三个),因此我们使用" defunctionalized"而是FlipSym2变体。

编译:

replicateVecSigma :: Natural -> Sigma Nat (FlipSym2 (TyCon Vect) String)
replicateVecSigma i =
  case toSing i of
    SomeSing snat -> snat :&: replicateVec snat "hello"

如果我们从最开始的位置得到n参数,我们就可以避免使用Flip

data Vect2 :: Type -> Nat -> Type where
  VNil2 :: Vect2 a 0
  VCons2 :: a -> Vect2 a n -> Vect2 a (n + 1)

replicateVec2 :: forall n a. Sing n -> a -> Vect2 a n
replicateVec2 = undefined

replicateVecSigma2 :: Natural -> Sigma Nat (TyCon (Vect2 String))
replicateVecSigma2 i =
  case toSing i of
    SomeSing snat -> snat :&: replicateVec2 snat "hello"

TyCon Vect2 @@ String也适用于此处,在defunctionalized级别使用显式应用程序@@,而不是实际类型applciation。)

非常粗略地说,除了FlipSym0, FlipSym1, FlipSym2允许你使用它们这一事实之外,你可以将Apply等功能化变体视为基本标签(不是函数!),没有内在含义。 em>假装他们是功能。通过这种方式,我们可以使用非函数("标记"),就像它们是函数一样。这是必要的,因为在Haskell中,我们没有类型级别的功能,所以我们必须使用这些"假装"版本

这种方法很通用,但它确实需要程序员做更多的工作。

我不知道任何模板Haskell实用程序可以自动将类型级lambda转换为pointfree形式,然后对它们进行去功能化。这将非常有用。

答案 1 :(得分:3)

Defunctionalization 是解决缺少第一类函数的一般方法(事实上,它最初是作为一种编译技术来摆脱它们),将它们编码为符号由单个一阶Apply函数解释。

例如,您的Foo a b是对函数\n -> a n b进行编码的符号。

获得等效符号的另一种方法是遵循(\n -> a n b) = flip a b

的直觉

请注意,像Vec这样的类型构造函数本身不是可以传递给Apply的符号,但必须包含在“符号构造函数”中:TyCon2 Vec是{{1}的符号}}

单身人士\n -> \b -> Vec n b的符号,flip(符号可以代表更高阶的函数,方法是将其他符号作为参数)。

FlipSym0

请注意,部分应用程序会缩减为其他符号:

Foo a b = Apply (Apply FlipSym0 (TyCon2 a)) b

= Apply (FlipSym1 (TyCon2 a)) b = FlipSym2 (TyCon2 a) b 类型是一种为类功能化符号提供类型的方法。我看到的主要好处是类型推断;没有它,类型系列可能会陷入困惑的状态。

另请参阅Richard Eisenberg关于Haskell类型的功能化的博客文章:https://typesandkinds.wordpress.com/2013/04/01/defunctionalization-for-the-win/