如何使用类型级函数,动态地创建静态类型?

问题描述 投票:1回答:3

在TypeScript中,有 类型级 职能 允许根据给定的 字面 类型规格 (见 映射类型, 条件类型等)。)

例如,这里有这样一个 功能,比方说由一个lib作者提供。

type FromSpec<S> = { 
  [K in keyof S]: S[K] extends "foo" ? ExampleType : never 
};

它的目的是,给定一个规格 S 以字符串键和任意字元映射的形式创建一个新类型,它以映射的形式创建一个具有相同键集和值转换的新类型。如果a的值是文字 "foo" 就成了 ExampleType,否则该值将被拒绝,将其转换为底层类型的 never.

然后,终端用户可以按照上面的解释利用这个函数来创建新的类型。

type Example = FromSpec<{some_key: "foo", another_key: "bar"}>
//           = {some_key: ExampleType, another_key: never} 

值得注意的是,lib的作者并不知道某个终端用户到底想要什么类型,因此为他提供了一个函数来创建他需要的类型。另一方面,终端用户只要符合函数的功能,就可以创建一组无限的新类型。

你可以玩转这个简单的例子。此处.


问题是这种 "动态性 "如何在其他类型化语言中表现出来(比如ReasonMLOCaml、Scala、Haskell)。或者说,作为一个终端用户,如何创建新的类型。在编译时通过使用类型级函数,由lib作者提供(就像通常在运行时使用值级函数一样)?

需要注意的是,问题不在于哪种语言更好,等等。重要的是要找到最直接的和 明确 的方式来表达这种能力。在这里我们看到了TypeScript中的一个例子,但是有没有更多的 自然 的方式来创建新类型?

typescript scala ocaml reason
3个回答
4
投票

鉴于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") *: ()

那么 FromSpec 可以用 匹配型. 该 never 类型在TypeScript中被称为 Nothing 在ScalaDotty中。

// 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
}

最后,让我们在ScalaDotty中使用 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

4
投票

是否可以用另一种类型语言(如ReasonMLOCaml、Scala、Haskell)来表达同样的 "动态性 "或与之接近的东西。

是的,动态类型被OCamlReasonML类型系统完全支持,并且被广泛使用。你可以表达相当复杂的动态类型规则,例如,建立你的层次结构,实现特设的多态性等等。解决方案的主要成分是使用可扩展的GADT、一流的模块和存在性。请看这个 回答 作为例子之一,或本次讨论为 普世价值的一般情况在OCaml中,也有多个库提供各种动态类型功能。另一个例子是BAP的 核心理论 库,它有一个非常复杂的值排序的类型层次结构,其中包括各种数字表示法的精确类型规范,包括浮点数字、存储器等。

为了使答案更完整,你可以这样实现你的 fromSpec 在OCaml中,首先我们定义类型,它将带有动态类型的标签,在下面,它只是一个整数,但有相关的类型,它是 目击,

type 'a witness = ..

要创建一个新的证人(基本上是增量这个id),我们将使用第一类模块,并附加一个新的构造函数,使用 +=

module type Witness = sig 
     type t 
     type _ witness += Id : t witness
end

type 'a typeid = (module Witness with type t = 'a)

let newtype (type u) () =
  let module Witness = struct
    type t = u
    type _ witness += Id : t witness
  end in
  (module Witness : Witness with type t = u)

类型平等证明(向编译器证明两个类型是相同的,因为它们都在使用具有相同标识的构造函数),通常表示为 ('a,'b) eq 种类:

type ('a,'b) eq = Equal : ('a,'a) eq

这就是我们实现投射函数的方法。

let try_cast : type a b. a typeid -> b typeid -> (a,b) eq option =
  fun x y ->
  let module X : Witness with type t = a = (val x) in
  let module Y : Witness with type t = b = (val y) in
  match X.Id with
  | Y.Id -> Some Equal
  | _ -> None

最后,你 fromSpec,

type spec {
   data : 'a;
   rtti : 'a typeid
}

let example_type = newtype ()

let example = {
   data = 42;
   rtti = example_type; (* witnesses that data is `int` *)
}

let fromSpec = try_cast example_type 

1
投票

声明:我不是C++程序员,所以不要把这个答案当成是C++的正确做法。这只是一种极度脆皮的方法,可能大部分是错误的。

//I've used char pointers below, because it's not possible to directly write string //literals in templates without doing some more complex stuff that isn't relevant here

//field1 and field2 are the names of the fields/keys
const char field2[] = "field2";
const char field1[] = "field1";
//foo and bar are the strings that determine what the
//type of the fields will be
const char foo[] = "foo";
const char bar[] = "bar";

//This represents a key and the determining string (foo/bar)
template <const char * name, const char * det>
struct Named {};

//What the type of the field will be if it maps to "foo"
struct ExampleType {
  std::string msg;
};

//The end of a cons structure
struct End{};

//A cons-like structure, but for types
template <typename T, typename N>
struct Cons {
  typedef T type;
  typedef N Next;
};

//This'll be used to create new types
//While it doesn't return a type, per se, you can access the
//"created" type using "FromSpec<...>::type" (see below)
template <typename T>
struct FromSpec;

//This will handle any Named template where the determining string
//is not "foo", and gives void instead of ExampleType
template <const char * name, const char * det, typename rest>
struct FromSpec<Cons<Named<name, det>, rest>> {
  //Kinda uses recursion to find the type for the rest
  typedef Cons<void, typename FromSpec<rest>::type> type;
};

//This will handle cases when the string is "foo"
//The first type in the cons is ExampleType, along with the name
//of the field
template <const char * name, typename rest>
struct FromSpec<Cons<Named<name, foo>, rest>> {
  typedef Cons<ExampleType, typename FromSpec<rest>::type> type;
};

//This deals with when you're at the end
template <>
struct FromSpec<End> {
  typedef End type;
};

现在你可以这样使用。

typedef Cons<Named<field1, foo>, Cons<Named<field2, bar>, End>> C;

//Notice the "::type"
typedef FromSpec<C>::type T;

T 相当于 Cons<ExampleType, Cons<void, End>>

然后你可以像这样访问里面的类型。

typedef T::type E; //Equivalent to ExampleType
typedef T::type::Next N; //Equivalent to Cons<void, End>
typedef N::type v; //Equivalent to void

使用示例

int main() {
  ExampleType et = { "This is way too complicated!" };
  //You can kinda have values of type "void", unfortunately,
  //but they're really just null
  //             v
  N inner = { nullptr, new End() };
  T obj = { &et, &inner };
  Cons<ExampleType, Cons<void, End>> obj2 = obj;
  std::cout << et.msg << std::endl;
}

打印 "这太复杂了!"

链接到 repl.it

如果我的答案有错误或者可以改进,欢迎编辑我的答案。我主要是想翻译 答案@Alec 到C++中。

© www.soinside.com 2019 - 2024. All rights reserved.