Несмотря на то, что понятие внутренних типов может сбить с толку читателя, я не буду определять их сразу, так как хочу представить концепцию на практическом примере. Я бы хотел начать статью со следующего вопроса, который я задавал себе в разных формах на протяжении многих лет:

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

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

Решение задания

То, как я написал вышеупомянутое присвоение, дает большую свободу в выборе используемой системы типов. Однако необходимо признать неявные требования к определенным функциям, таким как обобщенные типы, строковые литералы или наследование. Поскольку упорядоченные списки символов образуют строки, вопрос «какая кодировка применяется к символам?» требует мгновенного ответа.

Я бы назвал тип CapitalizedString и определил его как параметризованный тип (или универсальный тип, зависит от языка предметной области выбранной системы типов) с одним ограниченным параметром T к строковому литералу. Все символы в строках должны соответствовать кодировке UTF-8. Его можно было легко записать с помощью псевдокода следующим образом:

type CapitalizedString<T extends string literal> = ...

По определению, если рассматриваемый строковый литерал не имеет символов, операция, очевидно, на него не влияет. В противном случае использование заглавных букв изменяет только первый символ рассматриваемой строки, оставляя остальные без изменений. Чтобы ввести символ в верхний регистр, система типов должна поддерживать его ad hoc, поскольку его явное определение в системе типов звучит непрактично для символов UTF-8 из-за сложности такого алгоритма (я считаю это нецелесообразным даже для символов ASCII).

Следовательно, сам компилятор должен обеспечивать реализацию заглавных букв для любого символа UTF-8, что делает операцию в верхнем регистре ad hoc или, скорее, внутренним свойством системы типов. Существует несколько способов объявления такого свойства в определениях типов, например:

intrinsic type Uppercase<T extends character>; 
type Uppercase<T extends character> = intrinsic;

Заядлый читатель может заметить, что в вышеприведенных определениях явно не указывается тип возвращаемого значения - поскольку компилятор выполняет операцию, никто заранее не знает тип возвращаемого значения. Система типов может определять верхний регистр как унарный оператор, принимающий один символ, как показано в следующем фрагменте:

type UppercaseA = uppercase 'a';

В конце концов, тип CapitalisedString сводится к следующему определению:

type CapitalisedString<T extends string literal> =
    T === '' ? '' : Uppercase<T[0]> + T[1...];

Чтобы определение работало, рассматриваемая система типов требует:

  • возможность сравнения двух строковых литералов на равенство,
  • возможность доступа как к n-му символу, так и к диапазону символов любого строкового литерала,
  • разрешение на использование условных операторов в определениях типов.

Внутренние типы в TypeScript

Начиная с TypeScript 4.1, существует 4 внутренних типа: Lowercase, Uppercase, Capitalize и Uncapitalize, все они определены с помощью ключевого слова intrinsic. Я не нашел операторов, связанных с управлением делами на этом языке. В качестве упражнения я бы посоветовал читателю изучить эти типы и подумать о другом способе определения Capitalize и Uncapitalize (подсказка: ключевое слово infer). Кроме того, какие еще строковые операции можно было придумать?

Переход от логики с нижним регистром к логике с верхним регистром составляет один из многих вариантов использования, в котором используется система типов для проверки правильности строковых литералов при работе в новой системе - мне лично очень понравилось использовать свою инструментальную цепочку для автоматического выполнения повседневных задач. чеки. Некоторые системы, такие как базы данных SQL, не заботятся о регистре (в определенной степени), но нет такой гарантии в отношении других программ.

Другое наблюдение показывает, что некоторые языки программирования, фреймворки и библиотеки ожидают, что имена переменных будут соответствовать некоторым самоуверенным стандартам, таким как camel-case или snake-case. Для сетевых целей все микросервисы в системе должны придерживаться предопределенного способа сериализации данных для надежной связи. Использование внутренних типов для генерации правильных структур во время компиляции обеспечивает еще один уровень проверок безопасности, которые могут выявить проблемы перед отправкой одной строки кода в производственную систему.

Резюме

Само существование систем типов не гарантирует простоты, казалось бы, простых операций, таких как регистр верхнего или нижнего регистра. Поскольку я считаю, что создатели языков программирования должны разрабатывать их с учетом современных потребностей разработчиков программного обеспечения, я не ожидаю, что системы типов будут полными по Тьюрингу сами по себе. Может случиться так, что выражение простых концепций в спроектированной системе потребует значительного объема работы, и поэтому некоторые люди могут склоняться к перемещению концепций непосредственно в область компилятора.

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

Пост скриптум

Чтобы удовлетворить свое интеллектуальное любопытство, я написал эту статью на E-Prime, подмножестве английского языка без глагола «быть». После того, как я создам больше таких работ, я могу опубликовать статью обо всех моих связанных открытиях.