【问题标题】:Can Idris support row-polymorphism?Idris 可以支持行多态吗?
【发布时间】:2018-04-18 15:26:00
【问题描述】:

由此我可以构建一个匿名的临时记录;那是可编辑的、可附加的、可修改的,其中每个值可以具有不同的异构类型,并且编译器会检查消费者的类型期望是否与所有给定键处生成的记录的类型一致?

类似于 Purescript 所支持的。

【问题讨论】:

    标签: idris


    【解决方案1】:

    可以,但标准库中没有模块,gonzaw/extensible-recordsjmars/Records 两个 github 项目似乎并不成熟/过时。

    您可能需要自己实现它。粗略的想法是:

    import Data.Vect
    
    %default total
    
    data Record : Vect n (String, Type) -> Type where
      Empty : Record []
      Cons : (key : String) -> (val : a) -> Record rows -> Record ((key, a) :: rows)
    
    delete : {k : Vect (S n) (String, Type)} -> (key : String) ->
           Record k -> {auto prf : Elem (key, a) k} -> Record (Vect.dropElem k prf)
    delete key (Cons key val r) {prf = Here} = r
    delete key (Cons oth val Empty) {prf = (There later)} = absurd $ noEmptyElem later
    delete key (Cons oth val r@(Cons x y z)) {prf = (There later)} =
      Cons oth val (delete key r)
    
    update : (key : String) -> (new : a) -> Record k -> {auto prf : Elem (key, a) k} -> Record k
    update key new (Cons key val r) {prf = Here} = Cons key new r
    update key new (Cons y val r) {prf = (There later)} = Cons y val $ update key new r
    
    get : (key : String) -> Record k -> {auto prf : Elem (key, a) k} -> a
    get key (Cons key val x) {prf = Here} = val
    get key (Cons x val y) {prf = (There later)} = get key y
    

    有了这个,我们可以编写处理字段而不知道完整记录类型的函数:

    rename : (new : String) -> Record k -> {auto prf : Elem ("name", String) k} -> Record k
    rename new x = update "name" new x
    
    forgetAge : Record k -> {auto prf : Elem ("age", Nat) k} -> Record (dropElem k prf)
    forgetAge k = delete "age" k
    
    getName : Record k -> {auto prf : Elem ("name", String) k} -> String
    getName r = get "name" r
    
    S0 : Record [("name", String), ("age", Nat)]
    S0 = Cons "name" "foo" $ Cons "age" 20 $ Empty
    
    S1 : Record [("name", String)]
    S1 = forgetAge $ rename "bar" S0
    
    ok1 : getName S1 = "bar"
    ok1 = Refl
    
    ok2 : getName S0 = "foo"
    ok2 = Refl
    

    当然,你可以通过语法规则来简化和美化它。

    【讨论】:

    • 这太酷了!谢谢你写下来。我很高兴看到它可以进行类型检查。我将稍微测试一下这个概念,并尝试理解它。我希望它不会受到与 Haskell 的 Bookkeeper/rawr/superrecord 和类似的可扩展记录库类似的限制,例如支持非常有限的键数,并且编译时间受到超线性的影响。你会碰巧知道吗?
    • 我猜他们实现它的方式类似:使用(键,类型)的链表。类型惩罚主要来自编译器需要构造{auto prf : Elem …},即 (key, type) 实际上在列表中,然后使用此证明来查找值。所以它遍历列表两次并为其构建中间证明。我猜这可以优化,所以它实际上使用键来查找值。无论如何,我不确定 Haskell 的库有多慢,但 getName $ rename "test" SS 是一个有 100 个键的记录需要一分钟才能编译,10 个键不到一秒。
    • RecordVect n (String, Type) -> Type 更改为 List (String, Type) -> Type 使得这显然是线性的,即使是 100 行,编译器也只需几秒钟即可检查,尽管您必须为 List 重新实现 Elem。跨度>
    • 嗯,秒听上去确实比分钟好,尽管我有点担心它甚至需要这么多。是不是要检查这么多东西?
    • 我只是在这里猜测:这个实现基本上是一个列表,所以如果你保留它,它就不会变成亚线性的。但是您可以将记录实现为树,使事情变得 O(log n)。时间主要花在类型检查器上,但 Idris 还没有用于调试/优化速度的工具。因此,如果您需要一个快速的库来在编译时推断大记录,您将不得不等待。 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 2011-05-22
    • 2016-04-30
    • 1970-01-01
    • 2016-04-16
    相关资源
    最近更新 更多