fse*_*art 3 c++ ocaml haskell scala typescript
在 TypeScript 中,有类型级 函数允许根据给定的文字 类型/规范创建新类型(参见映射类型、条件类型等)。
例如,这里有这样一个函数,假设由 lib 作者提供:
type FromSpec<S> = {
[K in keyof S]: S[K] extends "foo" ? ExampleType : never
};
Run Code Online (Sandbox Code Playgroud)
它的目的是,给定一个S字符串键和任意文字的映射形式的规范,它以映射的形式创建一个具有相同键集和值转换的新类型。如果 a 值是文字,"foo"则它成为类型ExampleType,否则通过将其转换为底部类型来拒绝该值never。
然后,最终用户可以使用此功能按照上述说明创建新类型:
type Example = FromSpec<{some_key: "foo", another_key: "bar"}>
// = {some_key: ExampleType, another_key: never}
Run Code Online (Sandbox Code Playgroud)
值得注意的是,lib 作者不知道给定的最终用户可能想要什么确切类型,因此为他提供了一个函数来创建他需要的类型。另一方面,最终用户只要符合函数的能力,就可以创建无限的新类型集。
您可以在这里玩这个简单的例子。
问题是如何在其他类型语言(例如 ReasonML/OCaml、Scala、Haskell)中表达这种“动态”。或者,作为最终用户,如何在编译时通过使用由 lib 作者提供的类型级函数来创建新类型(就像通常在运行时使用值级函数所做的那样)?
重要的是要注意,问题不在于哪种语言更好等等。而是要找到最直接、最明确的方式来表达这些功能。在这里我们看到了一个 TypeScript 的例子,但是在其他语言中还有更自然的方式吗?
鉴于 Scala 是标记语言之一,这里是 Dotty(又名 Scala 3)中的解决方案。对此持保留态度,因为 Dotty 仍在开发中。使用 Dotty 版本 0.24.0-RC1 进行测试,这里有一个 Scastie 证明这实际上可以编译.
Scala 没有与 TypeScript 相同的用于操作记录的内置类型机制。不要害怕,我们可以自己动手!
import deriving._
// A field is literally just a tuple of field name and value
type Field[K, V] = (K, V)
// This just helps type-inference infer singleton types in the right places
def field[K <: String with Singleton, V <: Singleton](
label: K,
value: V
): Field[K, V] = label -> value
// Here is an example of some records
val myRec1 = ()
val myRec2 = field("key1", "foo") *: field("key2", "foo") *: ()
val myRec3 =
field("key1", 1) *: field("key2", "foo") *: field("key3", "hello world") *: ()
Run Code Online (Sandbox Code Playgroud)
然后,FromSpec可以使用match-type来实现。neverTypeScript 中的类型在NothingScala/Dotty 中被调用。
// Could be defined to be useful - `trait` is just an easy way to bring a new type in
trait ExampleType
val exampleValue = new ExampleType {}
type FromSpec[S <: Tuple] <: Tuple = S match {
case Field[k, "foo"] *: rest => Field[k, ExampleType] *: FromSpec[rest]
case Field[k, v] *: rest => Field[k, Nothing] *: FromSpec[rest]
case Unit => Unit
}
Run Code Online (Sandbox Code Playgroud)
最后,让我们使用FromSpec:
def myRec1Spec: FromSpec[myRec1.type] = ()
def myRec2Spec: FromSpec[myRec2.type] =
field("key1", exampleValue) *: field("key2", exampleValue) *: ()
def myRec3Spec: FromSpec[myRec3.type] = ??? // no non-diverging implementation
Run Code Online (Sandbox Code Playgroud)