Показать сообщение отдельно
  #360 (permalink)  
Старый 31.12.2018, 05:17
Аватар для Malleys
Профессор
Отправить личное сообщение для Malleys Посмотреть профиль Найти все сообщения от Malleys
 
Регистрация: 20.12.2009
Сообщений: 1,714

Я бы хотел предложить вариант, как могла бы выглядеть типизация в JavaScript.

Мне кажется, что предложенные варианты неявной типизации 1) синтаксически усложнены и 2) не учитывают некоторые особенности JavaScript

Тип объекта — ссылка на функцию, при помощи которой сконструирован объект (явно или неявно). null и undefined, а также объекты вида { __proto__: null } не имеют конструктора. Тип может быть простым и составным.

Простой тип объекта o может быть выведен при помощи o.constructor

Например, в следующем примере
const PI = 3.14159265;
const re = /a/gi;
const IS_PROXY = Symbol("is proxy");
const now = new Date();

простой тип констант PI, re, IS_PROXY и now выводится неявно как Number, RegExp, Symbol и Date соответственно.

Далее в рассуждениях я буду обозначать вид простого типа при помощи звёздочки *. Т. е. в примере выше объекты имели вид *.

Рассмотрим вычисленное выражение Promise.resolve(5). С одной стороны можно сказать, что оно имеет простой тип Promise. Однако начав работать с этим объектом, можно заметить, что оно не полностью описывает всё, что достижимо в этом объекте. Например, можно извлечь значение, которое хранится в Promise. На самом деле это тип Promise, который содержит тип Number. Это составной тип.
Рассмотрим вычисленное выражение ["id", 60]. Оно похоже на простой тип Array. Однако при помощи перебора элементов этого массива можно установить, что оно содержит достижимые объекты, которые тоже имеют свой тип. Это составной тип.
Рассмотрим вычисленное выражение (a, p) => `€ ${a * (1 - p / 100)}`. Оно похоже на простой тип Function. Однако исследуя аргументы и возвращаемый результат функций, т. е. то, что достижимо в любой функции, мы видим, что они имеют свой тип. Функция — составной тип.

Такие виды типов можно обозначить так:
Вид *<*> для типа, который зависит от другого типа, например тип Promise<Number>
Вид *<*, *> для типа, который зависит от двух других типов, например тип Array<String, Number>
Вид *<*, *, *> для типа, который зависит от трёх других типов, например тип Function<Number, Number, String>
Составной тип может зависеть от любого кол-ва типов. Зависящие типы — аргументы вида.
Также для составного типа можно определить тип хвоста. Например,
вид *<...*> показывает, что тип зависит от любого кол-ва одного и того же другого типа, например тип Set<...Number> для множества содержащего любое кол-во чисел.
вид *<*, ...*> показывает, что тип зависит от другого типа и любого кол-ва одного и того же третьего типа, например тип Array<RegExp, ...String> для массива, содержащего рег. выр. и любое кол-во строк.

Функция без аргумента имеет вид Function<*>, т. е. у её зависящего типа указывается только возвращаемый тип
Функция с одним аргументом имеет вид Function<*, *>, т. е. у её зависящих типов указывается тип аргумента и возвращаемый тип
Функция с двумя аргументами имеет вид Function<*, *, *>, т. е. у её зависящих типов указываются типы аргументов и возвращаемый тип и т. д.

Поскольку у видов Function последний аргумент указывает на тип возвращаемого значения, то тип хвоста списка аргументов указывается в списке типов вида предпоследним.
Вид Function<...*, *>, например тип Function<...Number, Boolean> для функции, которая принимает в качестве аргументов любое кол-во чисел и возвращает true или false.
Вид Function<*, ...*, *>, например тип Function<Array<String>, ...String, String> для функции, которая принимает массив строк и любое кол-во строк и возвращает строку и т. д.

В JavaScript может быть реализован достаточно мощный механизм вывода типов, который позволит не указывать типы функций. Можно будет указать тип функции явно для того, чтобы ограничить её использование только для конкретных типов данных, либо для структурированного оформления исходного кода.

Синтаксис типов (определение BindingIdentifier взято из 9-ого издания ECMAScript)

Код:
Type :
	BindingIdentifier
	BindingIdentifier < TypeParameters >

TypeParameters :
	TypeRestParameter
	TypeRestParameter , Type
	TypeParameterList
	TypeParameterList , TypeRestParameter
	TypeParameterList , TypeRestParameter , Type

TypeRestParameter :
	... Type

TypeParameterList :
	Type
	TypeParameterList , Type
В 9-ом издании ECMAScript определённые синтаксические правила дополняются:

Код:
FormalParameter[Yield, Await] :
	BindingElement[?Yield, ?Await]
	Type WhiteSpace BindingElement[?Yield, ?Await]

MethodDefinition[Yield, Await] :
	PropertyName[?Yield, ?Await] ( UniqueFormalParameters[~Yield, ~Await] ) { FunctionBody[~Yield, ~Await] }
	Type WhiteSpace PropertyName[?Yield, ?Await] ( UniqueFormalParameters[~Yield, ~Await] ) { FunctionBody[~Yield, ~Await] }
	GeneratorMethod[?Yield, ?Await]
	AsyncMethod[?Yield, ?Await]
	AsyncGeneratorMethod[?Yield, ?Await]
	get PropertyName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] }
	get Type WhiteSpace PropertyName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] }
	set PropertyName[?Yield, ?Await] ( PropertySetParameterList ) { FunctionBody[~Yield, ~Await] }

Expression[In, Yield, Await] :
	AssignmentExpression[?In, ?Yield, ?Await]
	AssignmentExpression[?In, ?Yield, ?Await] :: Type
	Expression[?In, ?Yield, ?Await] , AssignmentExpression[?In, ?Yield, ?Await]
	Expression[?In, ?Yield, ?Await] , AssignmentExpression[?In, ?Yield, ?Await] :: Type
Хотя можно ещё более подробно указать в правилах синтаксиса, как можно использовать неявные типы, но я думаю, что для начала хватит такого определения. В этих правилах говорится, что у метода/сеттера может быть указан тип каждого аргумента. Также может быть указан возвращаемый тип метода/геттера.
Также любое выражение может быть снабжено типом. (так называемое типизированное выражение)

Возможно ключевое void стоит указывать там, где ожидается null или undefined? Например, функция alert может иметь тип Function<Object, void>

Посмотрите на примеры:

1. Деструктуризация по типу.
class Vector {
	constructor(Number x, Number y, Number z) {
		this.x = x
		this.y = y
		this.z = z
	}

	Vector add(Vector { x, y, z }) {
		return new Vector(
			this.x + x,
			this.y + y,
			this.z + z
		)
	}
}


Код выше работает так(это не значит, что он должен работать именно так, возможны оптимизации на уровне движка)
class Vector {
	constructor() {
		if(
			Object(arguments[0]) instanceof Number &&
			Object(arguments[1]) instanceof Number &&
			Object(arguments[2]) instanceof Number
		) {
			const [x, y, z] = arguments;
			this.x = x
			this.y = y
			this.z = z
		}

		else
			throw new TypeError("No method arises from provided arguments");
	}

	add() {
		if(
			Object(arguments[0]) instanceof Vector
		) {
			const [{x, y, z}] = arguments;
			return new Vector(
				this.x + x,
				this.y + y,
				this.z + z
			)
		}

		else
			throw new TypeError("No method arises from provided arguments");
	}
}




2. Перегрузка
class SomeClass {
	Number add(Number num1, Number num2) {
		return num1 + num2;
	}

	String add(String str1, String str2) {
		return str1 + str2;
	}
}


Это выполняется так
class SomeClass {
	add() {
		if(
			Object(arguments[0]) instanceof Number &&
			Object(arguments[1]) instanceof Number
		) {
			const [num1, num2] = arguments;
			return num1 + num2;
		} else

		if(
			Object(arguments[0]) instanceof String && 
			Object(arguments[1]) instanceof String
		) {
			const [str1, str2] = arguments;
			return str1 + str2;
		}

		else
			throw new TypeError("No method arises from provided arguments");
	}
}



Поскольку типы имеют свой собственный контекст, где они могут быть использованы, то я думаю, что можно ввести также литералы для типов.

Array или []
Array<Number> или [Number]
Array<...RegExp> или [...RegExp]
Array<String, Number> или [String, Number]
Array<String, ...String> или [String, ...String]

Function<Response, Array<Number>> или Response => [Number]
Function<Number> или () => Number
Function<...Number, Number> или (...Number) => Number
Function<Function<...Object, Array<Object>>, String> или ((...{}) => [{}]) => String
Ответить с цитированием