用值填充链表,该链表包含有关在类型中编码的长度的信息

尼古拉斯·海曼(Nicolas Heimann)

我目前在进行类型级编程时很有趣。考虑以下版本的链表

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

module ExpLinkedList where

import           GHC.TypeLits (Nat, KnownNat , type (-), type (+))
import           Data.Proxy   (Proxy(..))
import           Data.Kind    (Type)
import           Fcf          (TyEq, If, Eval)

data LinkedList (n :: Nat) (a :: Type) where
  Nil  :: LinkedList 0 a
  (:@) :: a -> LinkedList n a -> LinkedList (n + 1) a

infixr 5 :@

someList :: LinkedList 2 String
someList = "test" :@ "list" :@ Nil

我想知道是否可以定义一个extendsa的函数LinkedList

例如

extend :: forall m n a . LinkedList n a -> a -> LinkedList (n + m) a
extend vec elem = undefined

example :: LinkedList 5 String
example = extend @3 ("foo" :@ "bar" :@ Nil) "hi"
-- could be: "hi" :@ "hi" :@ "hi" :@ "foo" :@ "bar" :@ Nil

我想出了不同的方法,迟早都会陷入困境……这是其中两种:

递归方法

在这种方法中,结束条件由重叠的typeclass实例编码

class Extend (b :: Nat) where
  ex :: a -> LinkedList n a -> LinkedList (n + b) a

instance {-# OVERLAPPING #-} Extend 0  where
  ex _ vec = vec

instance Extend n where
  ex a vec = nextEx newVec
  --                ^
  --  • Couldn't match type ‘(n1 + 1) + (n - 1)’ with ‘n1 + n’
  --    Expected type: LinkedList (n1 + n) a
  --      Actual type: LinkedList ((n1 + 1) + (n - 1)) a
    where
      newVec = a :@ vec
      nextEx = ex @(n - 1) a

归纳法

type NextElement (n :: Nat) = Just (n - 1)

class BuildHelper (v :: Maybe Nat) (a :: Type) where
  type CNE v a :: Type
  buildNext :: Proxy v -> a -> CNE v a

instance BuildHelper 'Nothing a where
  type CNE 'Nothing a = LinkedList 0 a
  buildNext _ a = Nil

instance BuildHelper ('Just m) a where
  type CNE ('Just m) a = LinkedList (m + 1) a
  buildNext _ a = a :@ buildNext proxy a
--                     ^
-- • Couldn't match expected type ‘LinkedList m a’
--                  with actual type ‘CNE
--                                      (If (TyEq m 0) 'Nothing ('Just (m - 1))) 
    where
      proxy = Proxy @(NextElement m)

用纸和笔进行评估似乎可行

-- buildNext (Proxy @(Just 2) True) :: proxy -> Bool -> Vector 3 Bool
-- = a :@ buildNext @(NextElement 2) a
-- = a :@ buildNext @(Just 1) a
-- = a :@ a :@ buildNext @(NextElement 1) a
-- = a :@ a :@ buildNext @(Just 0) a
-- = a :@ a :@ a :@ buildNext @(NextElement 0) a
-- = a :@ a :@ a :@ buildNext @(Nothing) a
-- = a :@ a :@ a :@ Nil

基本上,GHC无法证明m匹配(m - 1) + 1

Li-yao Xia

这是单例的典型用例。

而且,此解决方案依赖于算术属性,该属性在GHC的类型检查器中本身不可用,但由ghc-typelits-natnormalise插件提供。

关于Nat推理的插件

具体而言,附加长度索引列表利用了关联性的(+):在的情况下m = p + 1,输出列表中的签名的类型extend就是LList (n + m) = LList (n + (p + 1))这需要关联等于LList ((n + p) + 1)使得构造(:@)都可以使用。我们还需要可交换除非我们在我们的代码谨慎和证明不混淆1 + pp + 1例如。无论如何,安装该软件包并添加以下行将向GHC教授一些基本的算法:

{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}  -- from the package ghc-typelits-natnormalise

注意,我们不必在代码中显式地进行任何此类推理;该插件在类型检查期间向编译器提供知识。

单身人士

该函数extend :: forall n m a. a -> LList m a -> LList (n + m) a需要查看的值m来知道a要插入多少s。我们必须更改其类型extend以提供必要的运行时信息。单例提供了一个通用的解决方案。具体来说,我们可以定义以下单类型Nat种类,其具有类型的值的运行时间表示的特性SNat n(即,看仅在构造SZSS)唯一地确定所述指标n

data SNat (n :: Nat) where
  SZ :: SNat 0
  SS :: SNat n -> SNat (1 + n)

的定义 extend

然后,想法是将签名从更改为,extend :: forall n. ...从而extend :: SNat n -> ...增加Natforall n上的量化,并SNat n使用具有具体运行时表示形式参数在运行时将其删除然后可以通过对SNat n参数进行模式匹配来定义函数:

extend :: SNat n -> a -> LList m a -> LList (n + m) a
extend SZ _ ys = ys
extend (SS n) x ys = x :@ extend n x ys

请注意,如果我们忽略类型,则此定义与extend使用简单Peano自然的简单列表(未按其长度索引)上的变体相同该函数extend是带有索引类型的许多示例之一,这些索引类型只是未索引程序的更精确类型的版本:

-- Peano representation of natural numbers
data PNat where
  Z :: PNat
  S :: PNat -> PNat

-- Non-indexed variant of extend
extendP :: PNat -> a -> [a] -> [a]
extendP Z _ ys = ys
extendP (S n) x ys = x : extendP n x ys

使用示例extend

example :: LList 5 String
example = extend (SS (SS (SS SZ))) "hi" ("foo" :@ "bar" :@ Nil)

我们必须用一元数写数字,这不是很有趣。我们可以使用类型类将Nat文字转换SNat单例值。

SNat隐式构造

class ISNat n where
  snat :: SNat n

如您所料,将会有两个实例,0分别是for和后继。0显而易见的是:

instance ISNat 0 where
  snat = SZ

对于后继者,术语级部分很简单(snat = SS snat),但是类型需要一些技巧。

instance {-# OVERLAPPABLE #-} (ISNat p, n ~ (1 + p)) => ISNat n where
  snat = SS snat

首先OVERLAPPABLE在语法上没有简单的方法将类型参数标识n为“ not 0”,因此我们使用一个OVERLAPPABLE实例。(当重叠不可接受时,还有其他一些方法,但是它们并不那么方便。)遇到ISNat n约束时,类型检查器将始终选择最具体的实例:如果n0,它将选择该0实例;如果n是非零文字,它将为后继者选择此可重叠实例,因为该0实例不适用,并且如果n不等于文字(因此它是不受约束的类型变量或某些阻塞类型族应用程序),则0 实例可能适用,我们并不十分了解,因此类型检查器将保守地不选择这两个实例中的任何一个,而是将在其上下文中的其他地方寻找合适的约束,如果找不到任何实例,则会引发编译时错误。

其次SS要使其结果类型为形式SNat (1 + p)因此,我们添加了一个约束n ~ (1 + p)

请注意,要解决该约束(使用时 snat),GHC将需要猜测p,natnormalise插件将在此处进行处理;否则,我们也可以添加一个约束p ~ (n - 1)

这样,我们最终可以使用Nat文字更方便地编写示例

extend (snat :: SNat 3) "hi" ("foo" :@ "bar" :@ Nil)

snat :: SNat位似乎有点松脆。留给读者的是练习。

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何找到有关在sql server 2008中更新或插入sql表行的信息?

来自分类Dev

有关在CDI / Weld和DI / Guice中@Inject的信息

来自分类Dev

用数组中包含的信息填充矩阵

来自分类Dev

C中的链表的链表

来自分类Dev

获取有关boost :: any中包含的类型的信息

来自分类Dev

与链表有关的算法问题

来自分类Dev

用Node打印链表包含空指针

来自分类Dev

git是否包含有关已使用的ID /组ID更改的信息?

来自分类Dev

C加号有关在头文件或源文件中定义静态数据成员的信息

来自分类Dev

g ++中的哪个内部文件包含有关程序编译时间计算的信息?

来自分类Dev

用数组中包含的信息填充矩阵

来自分类Dev

在哪里放置有关填充长度的信息?

来自分类Dev

扭曲和adbapi-结果是否包含有关列名的信息?

来自分类Dev

如何获取有关在Spring MVC中创建的会话的请求信息?

来自分类Dev

用C语言从另一个链表中填充空链表

来自分类Dev

Deezer API端点,其中包含有关parnter /联名订阅的详细信息

来自分类Dev

哪些文件包含有关物理分区的信息?

来自分类Dev

问有关在javascript中添加日期的信息?

来自分类Dev

Python有关在不使用全局或重新分配的情况下更改值的信息

来自分类Dev

测试有关在Javascript中创建对象的信息

来自分类Dev

我的链表有问题,我可以显示链表的内容,但是找不到节点或链表的长度

来自分类Dev

使用 python 保存包含有关数据系列信息的交互式绘图

来自分类Dev

如何输出有关在 Laravel 5.4 中执行的测试的信息

来自分类Dev

回显有关在 codeigniter 中不起作用的产品的用户信息

来自分类Dev

无需密钥即可获取数据。仅包含有关子节点的信息

来自分类Dev

交换链表中的值

来自分类Dev

jtable 只用链表中的值填充一行

来自分类Dev

Barcode/Magswipe Track 数据是否包含有关驾驶执照的照片/图像信息?

来自分类Dev

如何释放链表中的信息?

Related 相关文章

  1. 1

    如何找到有关在sql server 2008中更新或插入sql表行的信息?

  2. 2

    有关在CDI / Weld和DI / Guice中@Inject的信息

  3. 3

    用数组中包含的信息填充矩阵

  4. 4

    C中的链表的链表

  5. 5

    获取有关boost :: any中包含的类型的信息

  6. 6

    与链表有关的算法问题

  7. 7

    用Node打印链表包含空指针

  8. 8

    git是否包含有关已使用的ID /组ID更改的信息?

  9. 9

    C加号有关在头文件或源文件中定义静态数据成员的信息

  10. 10

    g ++中的哪个内部文件包含有关程序编译时间计算的信息?

  11. 11

    用数组中包含的信息填充矩阵

  12. 12

    在哪里放置有关填充长度的信息?

  13. 13

    扭曲和adbapi-结果是否包含有关列名的信息?

  14. 14

    如何获取有关在Spring MVC中创建的会话的请求信息?

  15. 15

    用C语言从另一个链表中填充空链表

  16. 16

    Deezer API端点,其中包含有关parnter /联名订阅的详细信息

  17. 17

    哪些文件包含有关物理分区的信息?

  18. 18

    问有关在javascript中添加日期的信息?

  19. 19

    Python有关在不使用全局或重新分配的情况下更改值的信息

  20. 20

    测试有关在Javascript中创建对象的信息

  21. 21

    我的链表有问题,我可以显示链表的内容,但是找不到节点或链表的长度

  22. 22

    使用 python 保存包含有关数据系列信息的交互式绘图

  23. 23

    如何输出有关在 Laravel 5.4 中执行的测试的信息

  24. 24

    回显有关在 codeigniter 中不起作用的产品的用户信息

  25. 25

    无需密钥即可获取数据。仅包含有关子节点的信息

  26. 26

    交换链表中的值

  27. 27

    jtable 只用链表中的值填充一行

  28. 28

    Barcode/Magswipe Track 数据是否包含有关驾驶执照的照片/图像信息?

  29. 29

    如何释放链表中的信息?

热门标签

归档