microsoft / microsoft/TypeScript

Tag types

Đang mở
#4,895 57 bình luận 56 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Discussion
Ngôn ngữ chính
Go
Star
111k
Fork
14.4k
Merge trung bình
1 ngày 19 giờ
Pull request đã merge (30 ngày)
117

Mô tả

Problem
  • There is no straight way to encode a predicate (some checking) about a value in its type.
Details

There are situations when a value has to pass some sort of check/validation prior to being used. For example: a min/max functions can only operate on a non-empty array so there must be a check if a given array has any elements. If we pass a plain array that might as well be empty, then we need to account for such case inside the min/max functions, by doing one of the following:

  • crashing
  • returning undefined
  • returning a given default value

This way the calling side has to deal with the consequences of min/max being called yet not being able to deliver.

function min<a>(values: a[], isGreater: (one: a, another: a) => boolean) : a {
   if (values.length < 1) { throw Error('Array is empty.'); }
   // ...
}
var found;
try {
   found = min([]);
} catch (e) {
   found = undefined;
}
Solution

A better idea is to leverage the type system to rule out a possibility of the min function being called with an empty array. In order to do so we might consider so called tag types.

A tag type is a qualifier type that indicates that some predicate about a value it is associated with holds true.


const enum AsNonEmpty {} // a tag type that encodes that a check for being non-empty was passed

function min<a>(values: a[] & AsNonEmpty) : a {
   // only values that are tagged with AsNonEmpty can be passed to this function
   // the type system is responsible for enforcing this constraint
   // a guarantee by the type system that only non-empty array will be passed makes
   // implementation of this function simpler because only one main case needs be considered
   // leaving empty-case situations outside of this function
}

min([]); // <-- compile error, tag is missing, the argument is not known to be non-empty
min([1, 2]); // <-- compile error, tag is missing again, the argument is not know to be non-empty

it's up to the developer in what circumstances an array gets its AsNonEmpty tag, which can be something like:

// guarntee is given by the server, so we always trust the data that comes from it
interface GuaranteedByServer {
    values: number[] & AsNonEmpty;
}

Also tags can be assigned at runtime:


function asNonEmpty(values: a[]) : (a[] & AsNonEmpty) | void {
    return values.length > 0 ? <a[] & AsNonEmpty> : undefined;
}

function isVoid<a>(value: a | void) : value is void {
  return value == null;
}

var values = asNonEmpty(Math.random() > 0.5 ? [1, 2, 3] : []);
if (isVoid(values)) {
   // there are no  elements in the array, so we can't call min
   var niceTry = min(values); // <-- compile error; 
} else {
    var found = min(values); // <-- ok, tag is there, safe to call
}

As was shown in the current version (1.6) an empty const enum type can be used as a marker type (AsNonEmpty in the above example), because

  • enums might not have any members and yet be different from the empty type
  • enums are branded (not assignable to one another)

However enums have their limitations:

  • enum is assignable by numbers
  • enum cannot hold a type parameter
  • enum cannot have members

A few more examples of what tag type can encode:

  • string & AsTrimmed & AsLowerCased & AsAtLeast3CharLong
  • number & AsNonNegative & AsEven
  • date & AsInWinter & AsFirstDayOfMonth

Custom types can also be augmented with tags. This is especially useful when the types are defined outside of the project and developers can't alter them.

  • User & AsHavingClearance

ALSO NOTE: In a way tag types are similar to boolean properties (flags), BUT they get type-erased and carry no rutime overhead whatsoever being a good example of a zero-cost abstraction.

UPDATED:

Also tag types can be used as units of measure in a way:

  • string & AsEmail, string & AsFirstName:

var email = <string & AsEmail> 'aleksey.bykov@yahoo.com';
var firstName = <string & AsFirstName> 'Aleksey';
firstName = email; // <-- compile error
  • number & In<Mhz>, number & In<Px>:

var freq = <number & In<Mhz>> 12000;
var width =   <number & In<Px>> 768;
freq = width; // <-- compile error

function divide<a, b>(left: number & In<a>, right: number & In<b> & AsNonZero) : number & In<Ratio<a, b>> {
     return <number & In<Ratio<a, b>>> left  / right;
}

var ratio = divide(freq, width); // <-- number & In<Ratio<Mhz, Px>>

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Văn bản issue là điểm bắt đầu hiện có; trước tiên hãy nghiên cứu hành vi của enum, kiểu giao, generic và type-erasure của TypeScript được mô tả trong đó. So sánh các ví dụ đề xuất về tag và đơn vị đo với hành vi hiện tại của hệ thống kiểu. Công việc chỉ được xem là hoàn tất khi có thiết kế đã được thống nhất và hành vi tương ứng của compiler cho branded predicate types, bao gồm các quy tắc rõ ràng về việc tạo và khả năng gán.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
typescript
Lĩnh vực
compilers
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.