Я хотел бы поговорить, так это полиморфизм, точно Ак-хок-полиморфизм и точнее неправильное использование специального полиморфизма. Специальный полиморфизм в использовании, когда некоторые функции f Имеет другое поведение для данного аргумента A Быть другим типом. Чтобы показать, что я имею в виду, я покажу пример мономорфной и полиморфной функции:
[Pseudo Code TS flavor]
function monoF(a: number): number => { /* implement. */ }
// overloaded function with two implementations:
function poliF(a: string): string => { /* implement. */ }
function poliF(a: number): number => { /* implement. */ }
Как видите моноф позволяет пройти только номер, и эта функция также возвращает один тип – номер Анкет Полиф имеет две реализации, он перегружен для строка и номер тип.
Проблемы с специальным полиморфизмом
Какова проблема с таким специальным полиморфизмом? Проблема в том, что это часто приводит к неправильному дизайну. В перегрузке функций TypeScript еще сложнее, поскольку TS не допускает во многих реализациях, реализация может быть единственной и единственной, которая заставляет нас функционировать с несколькими ветвями.
[JS]
function isAdult(u){
if (typeof u === 'number') {
return u >= 18;
} else {
return u.age >= 18;
}
}
Из вычета реализации мы можем понять, что она работает для двух возможных типов, один из них номер и второй объект с возраст свойство. Чтобы увидеть это более четким, давайте добавим типов типовых произведений.
[TS]
function isAdult(u: number | {age: number}): boolean {
if (typeof u === 'number') {
return u >= 18;
} else {
return u.age >= 18;
}
}
isAdult(19)
isAdult(user)
Хорошо, теперь мы видим больше, наша функция в Хиндли Милнер Нотация имеет тип номер | {AGE: number} -> Boolean Анкет
Считайте, что наш Исадулт Функция способна покрыть два отдельных типа и составить их на логический Анкет Из -за этих двух типов мы были вынуждены добавить условие в реализацию, так как функция довольно проста, это все еще является дополнительной сложностью. Я могу сказать Исадулт Функция, объединенная из двух номер -> строка и {возраст: номер} -> строка . И какова цель этого? Ах – Гибкость, эта функция может использоваться в двух разных случаях. Но давайте рассмотрим более простую версию.
[TS]
function isAdult(u: number): boolean {
return u >= 18;
}
// usage
isAdult(19)
isAdult(user.age)
Единственная разница – это необходимость пройти user.age вместо Пользователь Анкет Но такой подход удалял большую часть кода внутри функции, также с самого начала единственной вещей, о которой заботится эта функция, была возраст, представленная как номер Анкет
Давайте посмотрим на специальное полиморхизм, который также включает в себя тип возврата.
[TS]
function add(a: string, b: string): number
function add(a: number, b: number): number
function add(a: string | number, b: string | number) {
if (typeof a === 'string' && typeof b === 'string') {
return parseInt(a) + parseInt(b)
}
if (typeof a === 'number' && typeof b === 'number'){
return a + b;
}
return a; // the dead code part
}
const a = add(1, 2)
const b = add("1", "2")
Поскольку это видимый код довольно ужасен. Нам нужно проверить типы переменных по времени выполнения тип , также мы представили часть мертвого кода, приняв во внимание перегрузки, на самом деле нет другого случая, тогда пары (номер, номер) и (строка, строка) , но наша реализация видит все возможные случаи, а также пары (строка, номер) и (номер, строка) Анкет
TS не может иметь перегрузки функций в форме собственных реализаций, как, например, C ++, потому что TS не может проверить, какой у нас действительно тип, так как тип Только аннотация не существует во время выполнения
Честно говоря, мы можем немного изменить реализацию, но единственный способ – использовать здесь утверждение типа.
function add(a: string | number, b: string | number) {
if (typeof a === 'string') {
return parseInt(a) + parseInt(b as string) // type assertion
}
return a + (b as number); // type assertion
}
Это лучше, не уверен. Утверждение типа всегда рискованно, безопасность типа здесь свободно.
Давайте подумаем, почему мы вообще это делаем, зачем нам два типа ввода? Мы абстрагируем от разработчика необходимость анализа строки для int. Стоит ли эта игра свечи? Нет это не так.
Некоторые из вас могут сказать, что Союз создает новый тип, как мы можем сказать Тип | номер Да, это правда, и в этом термине функция T -> t не полиморфно
Меньшая мономорфная версия
function add(a: string, b: string) {
return parseInt(a) + parseInt(b)
}
И для чисел у тебя уже + оператор. Больше ничего не нужно.
Реальный пример неправильного дизайна
Следующий пример из реального кода и вопрос из Stackoverflow – Как обеспечить TypeScript, эта строка | string [] является строкой без использования как?
Мы хотим иметь функцию, которая перегружена таким образом, что для строка Возвращает строка и для массив струн , вернуть массив струн Анкет Реальная цель иметь эту двойственность – дать разработчикам лучший опыт, вероятно, лучше …
В мире JS также очень распространено, чтобы дать специальное полиморфизм во всех местах, чтобы упростить границу. Эта историческая практика, которую я считаю неправильной.
function f(id: string[]): string[];
function f(id: string): string;
function f(id: string | string[]): string | string[] {
if (typeof id === 'string') {
return id + '_title';
}
return id.map(x => x + '_title');
}
const title = f('a'); // const title: string
const titles = f(['a', 'b', 'c']); // const titles: string[]
То, что мы получаем здесь, ах да, разработчик может поместить один элемент в форму простой строки или многое внутри массива. Из -за этого мы ввели сложность в форме:
- Условия внутри реализаций
- Три определения типа функции
Что мы получаем:
- Используйте строку для одного элемента:)
ОК, но что не так произойдет, если функция будет переработана в мономорфную форму:
function f(id: string[]): string[] {
return id.map(x => x + '_title');
}
const title = f(['a']); // brackets oh no :D
const titles = f(['a', 'b', 'c']);
Реальная разница в том, что нам нужно добавить кронштейны вокруг нашей строки, это такая большая проблема? Не думаю. У нас есть предсказуемая мономорфная функция, которая является простой и чистой в реализации.
Как насчет ELM
Давайте переключим язык на Вяз , ELM – это язык, который простой и следит очень строгим правилам. Как здесь решается специальная полиморфизм? И ответ – нет такой вещи. ELM допускает параметрический полиморфизм, который должен быть знаком для вас в форме общих типов на многих языках, но Нет возможности перегружать функции в ELM Анкет
Кроме того, такие профсоюзы, как строка | string [] невозможно в системе типа ELM, единственный способ, которым мы можем быть близки к такому, – это пользовательский тип суммы. Рассмотрим следующий пример ELM:
[ELM]
type UAge = Age Int | UAge { age: Int } -- custom type
isAdult : UAge -> Bool
isAdult str = case str of
Age age -> age >= 18
UAge u -> u.age >= 18
-- using
isAdult (UAge {age = 19})
isAdult (Age 19)
Чтобы достичь того же самого в ELM, нам нужно ввести пользовательский тип, пользовательский тип моделирует номер | {возраст: номер} из TypeScript. Этот пользовательский тип является Тип суммы Другими словами, мы можем считать, что наша функция действительно мономорфна, поскольку тип определяется как Uage -> bool . Такая практика в ELM – это просто бремя, и это бремя, потому что не предпочтительнее следовать таким идеям. Вся реализация должна выглядеть как:
[ELM] isAdult : Int -> Bool isAdult age = age >= 18 -- using isAdult user.age isAdult 19
И если вам действительно нужно позвонить Исадулт Для записи пользователя, затем используйте функциональную состав
[ELM]
isUserAdult: { age: Int } -> Bool
isUserAdult u = isAdult u.age
Функция isuseradult просто звонит Исадулт Анкет Оригинальная функция не содержит пользовательского контекста, ее более гибко используется, является идеальным компонентом, и мы можем использовать Исадулт Для других объектов не только с возраст свойство.
Ап-хок-полиморфизм всегда неправильный
Нет, но мы должны быть осторожны с этим инструментом. Такой полиморфизм дает большую гибкость, мы можем перегружать функции для работы с различным типом объектов. Вся система типа Haskell основана на параметрическом и специальном полиморфизме, а затем реализуется там в форме Typeclasses Анкет Благодаря такому вы можете, например, использовать операторы, такие как <$> или >> = для разных случаев. Это очень мощный инструмент, но также и одна из основных причин, по которой код Haskell так сложно понять, уровень абстракции часто очень высок, и это также потому, что когда вы смотрите на функции или операторы, они могут иметь различную реализацию для разных типов.
Более низкий уровень и очень полезный пример специального полиморфизма-это функция C ++, как TO_STRING функция, которая имеет много перегрузки для многих типов. Такое использование очень полезно. Подумайте, какое бремя было бы, если вам нужно будет создать другое имя для вашего log функция полезности для каждого другого типа.
Функции и операторы перегрузки также являются очень удобным инструментом для введения собственных алгебр, если вы хотите больше информации по этой теме, рассмотрим серию статей о алгебраических структурах.
Вывод. Тщательно используйте функции перегрузки, не ставят сложности, если она не нужна, нет проблем в размещении значения в скобки, гибкость функций не всегда является хорошей вещью. Рассмотрим композицию по многочисленным функциям.
Пса Извините за заголовок ClickBait
Оригинал: “https://dev.to/macsikora/function-flexibility-considered-harmful-447n”