可扩展记录和高阶函数

Rob*_*her 3 records functional-programming compiler-errors higher-order-functions elm

我想为具有类型为a的id字段的记录指定类型Uuid.所以,我正在使用可扩展记录.类型如下:

type alias WithId a = { a | id : Uuid }
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但后来我为这种类型创建了一个Json.Encoder- 也就是类型的函数WithId a -> Value- .我需要一种方法来编码底层值,所以我想采用类型的第一个参数a -> Value.由于我知道a是一个记录,我可以放心地假设它被编码为JSON对象,即使Elm没有公开数据类型Value.

但是,当我创建这样的函数时,我得到一个编译错误:

    The argument to function `encoder` is causing a mismatch.

27|             encoder a
                        ^
Function `encoder` is expecting the argument to be:

    a

But it is:

    WithId a

Hint: Your type annotation uses type variable `a` which means any type of value
can flow through. Your code is saying it CANNOT be anything though! Maybe change
your type annotation to be more specific? Maybe the code has a problem? More at:
<https://github.com/elm-lang/elm-compiler/blob/0.18.0/hints/type-annotations.md>

Detected errors in 1 module.
Run Code Online (Sandbox Code Playgroud)

我糊涂了.是不是某种类型WithId a会包含所有字段a,因此不WithId a应该a通过type alias结构类型?

我错过了什么?为什么WithId a不允许我使用a WithId a作为实例的类型别名a,即使它被定义为{a|...}

附录

我在下面标出了一个答案(相当于"你不能这样做,但这就是你应该做的事情."),但我还是有点不满意.我想我对这个词的使用感到困惑type alias.我的理解是记录总是一个type alias,因为它们明确指定了所有可能字段中的字段子集......但底层类型仍然是统一记录类型(类似于JS对象).

我想我不明白为什么我们说:

type alias Foo = { bar : Int } 
Run Code Online (Sandbox Code Playgroud)

代替

type Foo = { bar : Int }
Run Code Online (Sandbox Code Playgroud)

前者告诉我,任何记录{ bar : Int }都是a Foo,这对我来说意味着与a和a {a|bar:Int}相同.我错在哪里?我糊涂了.我觉得我不是在记录.aFoo

附录2

我的目标不只是有一个类型WithId,指定有一个.id在外地,而是有两种类型:FooWithId Foo其中Foo具有结构{ bar:Int }WithId Foo具有结构{ bar:Int, id:Uuid}.然后,我想要有一个WithId Baz,WithId Quux等等,具有单个编码器和单个解码器功能WithId a.

基本问题是我有持久化和非持久化数据,它们具有完全相同的结构,除了一旦持续存在,我有一个id.并希望我在某些情况下保持记录持久的类型级保证,因此Maybe不起作用.

Kev*_*ank 5

关于你对某个WithId a值的了解以及你应该如何处理它的原因(即将其视为一个a)的理由在逻辑上是合理的,但是Elm对可扩展记录的支持并不是以类型系统的方式使用允许这样做.事实上,榆木的可扩展的记录类型是专门设计不会允许访问的完整记录.

设计的可扩展记录是声明函数只访问记录的某些字段,然后编译器强制执行该约束:

type alias WithId a =
    { a | id : Uuid }

isValidId : WithId a -> Bool
isValidId withId =
    -- can only access .id

updateId : WithId a -> WithId a
updateId withId =
    { withId |
        id = -- can only access .id
    }
Run Code Online (Sandbox Code Playgroud)

例如,当用于编写在大型程序模型的一小部分上工作的函数时,这为给定函数能够执行的操作提供了强大的保证.updateUser例如,您的函数可以仅限于访问user模型的字段,而不是自由统计以访问模型中的许多字段.当您稍后尝试调试对模型的意外更改时,这些约束可帮助您快速排除许多函数,因为它们负责更改.

对于上述更具体的例子,我推荐Richard Feldman的elm-europe 2017谈话https://youtu.be/DoA4Txr4GUs(该视频中的23:10显示可扩展记录的部分).

当我第一次学习可扩展记录时,我有同样的兴奋,然后是困惑的失望,但事后我认为这来自于我根深蒂固的面向对象编程本能.我看到"可扩展",我的大脑直接跳到"继承".但是Elm没有以支持类型继承的方式实现可扩展记录.

如上例所示,可扩展记录的真正目的是将函数的访问限制为仅仅是大记录类型的子集.

至于您的具体用例,我建议从Encoder每种数据类型开始,如果您想重用ID编码器的实现,请idEncoder从每个编码器中调用共享函数.