Pull to refresh

Comments 28

Отличный пример того, что TypeScript не справляется с банальными задачами :)

Можно пример того, как это делается в других языках? :)


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

В Java, C# это же просто
class C implements A, B

или я чего-то не понимаю?
Здесь же проба понятия того, что это за тип, отобьёт желание дальше читать код

И в чём вы видите связь между между наследованием интерфейсов в классах и объединением произвольных вложенных структур данных? Вы понимаете разницу между структурной типизацией и номинативной? А ещё вы точно уверены в том, что знаете что такое "рекурсия"?


Ваш пример на Typescript выглядит символ в символ точно также. Единственная проблема — ваш пример никак не связан с темой статьи.

Рекурсия? Расскажи :)
Ну если совсем простым языком, то эта задача похожа решение задачи по глубокому объединению объектов в js (deepMerge), только здесь объединяются не js объекты, а ts типы. Эта задача решается с помощью рекурсии.

Отличный пример поверхностного комментария человека, который решил не заморачиваться с погружением в статью, и совсем не знает темы

argumentum ad hominem, так сказать


Убеди меня, что LOC для решения задачи адекватно самой задаче

Спасибо за статью! Недавно столкнулся с более сложной версией задачи. Надо объединить типы объектов, которые находятся в массиве для описания результата работы такой функции:
const result = merge([{k1: 1, k2: 2}, {k3: 3, k4: 4}, {k5: 5}])

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


1-ая иконка справа сразу после аватарки :) Я серьёзно, если вы разобрались в том, как этот generic из ts-toolbelt работает — напишите статью. Нужно больше годных статей про Typescript. Подавляющее большинство typescript-программистов очень сильно "плавают" во всём, что выходит за рамки самых простых задач. Больше хороших статей — больше годных типов.


Очень рекомендую блог от Dr. Alex Rauschmayer. У него классные разборы всяких typescript нюансов.

В то время как мы ожидали чего-то вроде этого:

type ExpectedType = {
  key1: string | null,
  key2: string,
  key3: string
}

Да как бы нет. С чего бы пересечение должно давать вдруг объединение? То, что хочет автор реализуется куда проще:


type DeepMerge< A, B > =
    & Partial< Omit< A, keyof B > >
    & Partial< Omit< B, keyof A > >
    & {
        [ key in keyof (A|B) ]: DeepMerge< A[key], B[key] >
    }
То, что хочет автор реализуется куда проще:

Я тоже вначале такое написал. Почти символ в символ. Но нет. Задача более сложная. Он там отдельно рассматривает массивы, фильтрует объекты, обрабатывает nullable.

Ну ладно, чуть сложнее:


type DeepMerge< A, B > =
    (A|B) extends {} ?
        & Partial< Omit< A, keyof B > >
        & Partial< Omit< B, keyof A > >
        & (A|B)
        & { [ key in keyof (A|B) ]: DeepMerge< A[key], B[key] > }
    : (A|B)

Нет. Контр-пример:


type A = { a: { c: null } };
type B = { a: string };

У автора ['a'] даст { c: null } | string
А у вас получится каша:


a.a = 'qwe';
a.a = { c: null }; // error

В целом & (A|B) было слишком дерзко :)

Это я забыл убрать. Ну ок, пустой интерфейс был слишком прост.


type DeepMergeTwoTypes< A, B > =
    (A|B) extends Record< string, unknown > ?
        & Partial< Omit< A, keyof B > >
        & Partial< Omit< B, keyof A > >
        & { [ key in keyof (A|B) ]: DeepMergeTwoTypes< A[key], B[key] > }
    : (A|B)

У вас по-разному обрабатываются массивы. У автора будет (string | number)[], а у вас string[] | number[]. Впрочем ваша версия мне кажется более логичной.


(A | B) extends Record<string, unknown>

А вот тут хорошо. Кажется получается тоже самое, но без tuple.

А ещё похоже, что манёвр с extends {} не работает. Во всяком случае в v4.0.5.
Судите сами:


type L<A> = A extends {} ? 'obj' : 'none-obj';
type L1 = L<string> // 'obj'

type N<A> = A extends { [key: string]: unknown } ? 'obj' : 'none-obj';
type N1 = N<string> // non-obj
type N2 = N<{}> // obj

Вообще, было бы проще, если бы автор написал типотесты, как тут.

Делал подобную вещь с рекурсией, когда мне надо было вывести тип объектов хоста (Java) с которыми скрипт работал напрямую. Чтоб не возникало желание напрямую присваивать JS объекты с тем же интерфейсом, кроме примитивов.

TS — это почти JS, только типы указывать надо, говорили они… :)
Написание типов для TS — это, похоже, отдельная профессия

Типы в тс — это отдельный по настоящему функциональный язык программирования.

К сожалению, это не совсем так. У вывода типов в TS есть ограничение на глубину рекурсивного спуска — то ли 50, то ли 52 шага, не помню точно, — после чего возвращается any. Отсутствие HKT тоже не добавляет радости — те костыли, которые я описывал, нельзя назвать удобным для повседневного использования решением. Но если говорить в общем, то система типов в строгом подмножестве TS достаточно неплоха среди мейнстримных ЯП.

Что именно "не совсем так"? Это не мощный, не удобный, и даже не согласованный язык, но вполне себе фп. А вот то, что вы там описываете — это как раз фп на честном слове ('мамой клянусь в глобальные переменные не лажу').

Пожалуйста, покажите, где в моей статье есть хоть слово про глобальные переменные. И в статье, и в комментарии выше я писал исключительно про систему типов тайпскрипта.
ФП без типов высшего порядка — это только первая ступень в лестнице абстракции, по сути — simply typed lambda calculus, самая нижняя вершина в лямбда-кубе Барендрегта. И даже до STLC система типов TS не дотягивает — в частности, из-за ограничения на глубину рекурсивного спуска. До версии 2.6 она была даже (почти) Тьюринг-полной, и могла с большой натяжкой считаться функциональным ЯП. В третьей ветке команда разработки компилятора затянула гайки по возможности хаков компилятора, так что система типов TS это уже не совсем язык программирования как таковой. Хотя, признаюсь честно, мне хотелось бы иметь возможность писать тайплевел-функции на подобии type families в хаскеле, но расширение системы типов не является ценностью для проекта TypeScript.

Пожалуйста, покажите, где в моей статье есть хоть слово про глобальные переменные

Я думаю nin-jin имел ввиду глобальный lookup интерфейс, а не переменную.

У вывода типов в TS есть ограничение на глубину рекурсивного спуска — то ли 50, то ли 52 шага

Кстати, не знаете, они планируют это поменять, или так будет всегда?

Тут не подскажу, к сожалению — такие факты обычно всплывают где-нибудь в GitHub Issues (например), и в публичном роадмапе нечасто появляются. Если этот вопрос интересует, то есть смысл его задать в тех же Issues, команда разработки обычно достаточно доброжелательно относится. Я больше переживал про HKT, про которые им регулярно напоминают тут, но подвижек к имплементации нет.

Sign up to leave a comment.

Articles