для створення API
наприклад ви хочете створити route loader для ReactRouter
і у вас таке api:
createBreadcrumbRouteLoader = (route: R, params: InferredRouteParams) => Promise
в рантаймі React Router буде в парамс класти значення path params, але по замовчуванню ви не отримуєте автокомпліт, бо params будуть Record, а цей тип дозволить отримувати type safety + приємний автокомпліт
Можна обійтись без цього, зробивши прямий assertion params, але знову ж таки — це не значить що InferredPathParams «бєсполєзний».
Ще раз: коли ви хочете створити ± просте та приємне API, щоб правильно описати його типо виведення потрібно буде трохи напрягтись і базовим TS тут не обійтись
Ви б так і почали з того, що ви просто не любите TS і заощадили кучу б часу мені)
В статті прямо сказано, що до EnumArray немає вимоги до унікальності і пояснено чому
І це жодним чином не ставіть цінність самої статті під питання, адже стаття має за мету створити ExhaustiveArray
Ви не відповілі на запитання:
є задача: визначити порядок письма в програмі
моє рішення: рішенням цієї задачі є чиста функція (lang: Language) => RTL | LTR
ви кажете, що «Language» писати не можна. Відповідно яке ваше рішення?
ви знаєте таку функцію: () => RTL | LTR? чи я маю написати (lang: string) => ...?
Називати «будь-яку декларацію enum-у» в коді (а відповідно і enum-и як концпецію) антипатерн — це принаймні дуже екстравагантне твердження, а на ділі — неправдиве
Правда в тому, що enum-и не завжди потрібні і не завжди використовуються за призначенням, але казати що enum як концепція це антипатерн — це не правда
а стосовно функціонального програмування дуже просто:
розуміння того, що в функціональному програмуванні циклічні алгоритми реалізується за допомогою рекурсії допоможе зрозуміти, що в TS без рекурсивних типів не можна жити. Відповідно це допоможе відійти від справедливого патерну мислення в «традиційному» програмуванні, що «рекурсивні функції краще уникати без необхідности», і прийняти те, в TS типах рекурсія — це обовʼязкова база
Також розуміння концепції чистоти функцій допомогає вам зрозуміти, що ви можете цю функцію повністю на типах написати, адже результати чистих функцій можна статично передбачити, так само як і типи TS статично обчислюються:
наприклад уявимо функцію inferPathParams(route: string) => string[], яка примає рядок в якому записані path параметри: /myRoute/:id/myMusic/:songName/etc/etc2 а повертає масив [«id», «songName»]. Ця функція є чистою, відповідно можна спробувати написати тип InferredPathParams, який буде робити те ж саме але на рівні типів
Давайте так: теорія множин для будь-якого розуміння TS не потрібна якщо і тільки «якщо представляти типи як множини, то жодне з положень теорії множин не можна буде застосувати до цих множин»; відповідно, «якщо представити типи як множини, і є хоча б одно положення теорії множин яке можна буде застосувати до цих множин, то теорія множин є потрібною для розуміння TS». Забавно, що щоб скласти ці 2 твердження, також потрібно розуміння кванторів з теорії множин)
Якщо ставитись до типів як до множин, то можна побачити, що багато положень застосовані і до типів:
1. Концепція union-типів
2. Концепція intersection-типів
3. Концепція привласнень на поняттях під множина та супер множина
4. Концепція never як пустої множини
5. unknown як універсум
Можна побачити і місця, де типи проявляють себе складніше або відмінно від множин: наприклад string & number це never, хоча строго кажучи, і там і там є метод toString, тому строго до теорії множин логічно очікувати принаймні {toString: () => string}, а в TS це не так
Проте навіть в тих місцях, де TS типи розходяться з теорією множин, все одно цікаво їх порівнювати: в чому саме розбіжність і чому розробники TS вирішили так
Ще раз: тезіс «типи в TS це множини з алгебри» — хибний, проте тезіс «якщо я буду ставитись до типів як до множин, то теорія множин мені зовсім ніяк не зможе допомогти» також хибний — зможе допомогти)
Дякую за відгук!
Особисто я в якості головної цінності цієї статті бачу наступне:
1. Ця стаття дуже гармонійно та природньо поєднує між собою зовсім різні та складні/просунуті TS концепції
2. Дуже добре демонструє глибину та комплексність мови TS, що має, на мою думку, великий популяризаторський ефект технології
3. Ця стаття дуже добре демонструє роботу принципу «що іноді щоб вирішити дуже просту (на рівні формулювання) задачу необхідно зануритись та дійти до занадто абстрактних абстракцій» — що правда про Тайпскрипт
Питання про практичну цінність залишається відкритим, в статті не має заклику негайно пушити цей код собі в проєкти. 100% можу сказати, що створити повноцінний ExhaustiveArray неможливо в загальному випадку. Як альтернативу можна розглядати покриття тестами на рівні типів або навіть поєднати ці 2 підходи
не зрозумів проблеми: цей фрагмент і має компілюватись
Ніде не написав, що типи в тс є множинами) Написав, що для розуміння TS необхідні принаймні базові знання теорії множин. Те ж саме і про функціональне програмування: ніде не сказав, що TS додав нові інструменти для функціонального програмування в JS. Сказав, що для глибшого розуміння TS знання теорії функціонального програмування є обовʼязковим). Хоча навіть тезис про те, що TS нічого нового для ФП не приніс це також дискусійно, адже можливості тайпскрипта та його переваги розкриваються на повну саме тоді, коли пишеш код в функціональному стилі, відповідно можна справедливо казати, що ТS мотивує та заохочує розробника писати саме в такому стилі
«Його декларація в коді» — це і значить що сам enum є аніпатерн, бо що ще можна робити з enum-ами, окрім як їх декларувати? Хіба що дивитись на них в чужому коді)
І я так і не зрозумів — як мені визначити напрямок письма *не* знаючи мови? Це ж проста чиста функція: мова => напрямок письма?
до кінця не зрозуміло, що саме є обʼєктом критики: те, що це саме нативний тайпскриптовий enum, чи сам факт використання якогось обʼєкту за своєю семантикою виконуючого функції enum-у
якщо перший поїнт, то я можу сказати наступне: замінить enum на const obj = {...} as const — валідність сказаного в статті не зміниться. Більше того, ви можете взагалі відмовитись від рантайм сутностей і тримати все на тайп левелі в union-і: type Language = ... | ... | ...; по аналогії з EnumArray можна дуже легко створити UnionArray, який працює так само, лише відрізняється спосіб опису вхідних даних: в EnumArray це літерал обʼєкту, а в UnionArray літерал union-у
якщо другий поїнт, то це щось цікаве) Я ніколи не чув, що enum-и та їх аналоги як концепція це антипатерн) Розкажіть будь-ласка про альтернативи тоді, і як за допомогою цих альтернатив мені створити union з назвами пород поняшек:type PonyBreed = 'Pegassus' | 'Unicorn' | 'Alicorn' | 'Megacorn' | 'Ponycorn'<code>
Стосовно другого поїнту, що рішення в статті не підходіть, оскільки не перевіряє на дублікати -
в статті у висновках покрите це питання: в статті розглядається розробка так званого вичерпного масиву
Заважає, що порядок ключів обʼєкта не гарантується при декларації згідно до специфікації і тим паче не гарантується при обході ключів обʼєкту. Той факт, що існують міжбраузерні домовленості про те, як браузери де-факто реалізовують порядок ключів, не є достатнім аргументом покладатись на ці домовленності через низку причин:
1. по-перше це не фіча, а те, що існує з історичних причин і не прибрано з міркувань зворонтої сумісності
2. по-друге ці «домовленності» є forgettable, а все що можна забути — це все не type safe
3. по-третє це просто анти-патерн, бо з коду інша людина та навіть сам автор ніколи не зрозуміє/не згадає з часом «так, а це я впорядковано ключі вказав чи я можу змінювати порядок», якщо не вказати коментар, що також псує якість коду. Будь-який розробник має знати, що якщо він змінить порядок будь-якої властивості в обʼєкті, то код його не зламається і тести не почнуть ломатись
4. по-четверте ніде нема гарантій, що певний браузер/певне середовище виконання буде дотримуватись цих домовленостей, і що вас код не зламається через це.
Обʼєкти в JS невпорядковані по своїй природі та згідно до специфікації, відповідно їх не можна використовувати для опису впорядкованих сутностей.
тим, що ми втрачаємо контроль за можливістю вказувати порядок мов) Навіть у другому варіанті, далі покажу чому
В першому варіанті здається це очевидно: бо `Object.values` не гарантує ніякого порядку. Також (це вже не стосується теми статті та зовсім з іншого питання), особисто я не за те, щоб на TS enum-ах, викликати Object.values або виконувати над ними інші схожі маніпуляції через саму природу TS enum-ів з купою pitfall-ів та нюансів, про які треба памʼятати, а все, що можна забути — це все не type safe
В другому варіанті здається все ідеально: тепер у нас source of truth не обʼєкт, а масив з фіксованим порядком. Але це не працює так, бо коли я захочу в
Якщо уважно подивитись в статтю, то можна побачити, що enum Language оголошено в окремому файлі від `languageDisplayNameMap` та `languageOptions`, бо в `languageOptions` вказаний порядок саме для конкретного компоненту `` (і він і оголошений поруч з компонентом), а в компоненті `` в мене може бути інший порядок
Ви праві в тому, що можна сказати «а в мене буде тільки один селект з одним порядком на весь проєкт» і тоді дійсно другий варіант вирішить проблемо. Але потрібно памʼятати, що це лише окремий випадок, а в загальному випадку це вже не спрацює
Так пишуть ті, хто розробляє якесь API або бібліотеки, тобто код, кінцевим споживачем якого є розробники, а не продакти, бо тайпскрипт це в також про DX. В повсякденних проєктах, де кінцеві споживачі це люди, дійсно просто важко знайти застосування для складних типоманіпуляцій та типовиведень, але в складних проєктах все одно можна)
Зі свого досвіду роботи та досвіду спілкування з TS розробниками можу зробити наступні тези:
Ці типи дійсно мʼяко кажучи складніше, ніж наші повсякденні типи, але це тільки на перший погляд. По-перше усі типи тут, окрім самого EnumArray існували та активно використовувались і до цієї статті, особливо тип Intersection (в різних проєктах може називатись по-різному: Intersection, UnionToIntersection, IntersectionFrom, U2I — навіть таку назву зустрічав)
По-друге, також справедливо зауважити, що такі більш «просунуті» типи рідко зустрічаються саме в повсякденній розробці, бо в більшості нас наша повсякденна розробка спрямована на створення коду, кінцевими споживачами якого будуть звичайні користувачі; проте коли наші кінцеві споживачі — це розробники (тобто коли ми пишемо власну лібу або створюємо якесь ± не саме тривіальне API), то одразу зʼявляється потреба в якихось більш складних інструментах для здійснення нетривіальних маніпуляцій та трансформацій з типами.
В принципі це проблема Typescript — що щоб створити щось дійсно прикольно потрібно писати напів-езотеричний код та добряче так зануритись в теорію множин та функціональне програмування, але в цьому є і фіча: цей езотеричний код у всіх однаковий і дуже розповсюджений, і коли один раз його розумієш — далі вже можеш його закритими очима створювати. Короче кажучи — ті хто один раз це побачив, запамʼятовують це назовні)
Конкретно в цій статті немає призову негайно це затягувати в свої кодові бази і ви правильно сказали, що це красива інтелектуальна гімнастика) Мета цієї статті — 1) показати, що тайпскрипт це глибше ніж просто `as any` тa `props: any`; 2) показати, що дуже «складні» на перший погляд речі випливають з дуже простої на перший погляд проблеми
Замінить перелік мов в коді на перелік пород кошенят) Тематика контенту в enum-у не впливає на зміст статті та на тему статті в цілому
Прикольний підхід, мені подобається,
але все одно ж у вас direction буде типізовано як ltr | rtl
і знову ж таки id мов ви би також типізували як union допустимих значень — мапінг нікуди не дівається де-факто
при цьому знову ж таки, мені в різних UI можуть бути потрібні різні варіанти тайтлов, і більш того — інший порядок мов, то мені все одно доведеться використовувати мапінг
Мапінг — це лише окремий випадок чистої функції. Я не вважаю справедливим та коректним вважати цілу підмножину функцій «безглуздим рішенням» та «антіпатерном»