如何使用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
实例只是为了使用Sigma
。 singletons
库是否提供了更轻松地使用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
singletons
为Apply
定义了一堆实例。
以上是上述三个问题:
Sigma
编写replicateVecSigma :: Natural -> Sigma Nat (\n -> Vect n String)
等函数?replicateVecSigma
类型编写Foo
,但似乎有太多额外的工作。有更简单的方法吗?TyFun
和Apply
如何运作?答案 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/