<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 17:18:13 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="C15vI9JhQLMjcowsz1HWnXKhWiWm9ltxcVCqQwhR6Xnkj6QUIB_t05UxqLTDXibqsqh3j67BpdPMsDAXWlYOFw";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Диспетчеризация по типу. Аддитивность. | JS: Программирование, управляемое данными</title>
<meta name="description" content="Диспетчеризация по типу. Аддитивность. / JS: Программирование, управляемое данными: Понять, что такое аддитивные программы и как динамическая диспетчеризация поможет нам строить такие программы. Разобраться, что такое диспетчеризация в контексте программирования и типов.">
<link rel="canonical" href="https://ru.hexlet.io/courses/ddp/lessons/dispatch/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Диспетчеризация по типу. Аддитивность.">
<meta property="og:title" content="JS: Программирование, управляемое данными">
<meta property="og:description" content="Диспетчеризация по типу. Аддитивность. / JS: Программирование, управляемое данными: Понять, что такое аддитивные программы и как динамическая диспетчеризация поможет нам строить такие программы. Разобраться, что такое диспетчеризация в контексте программирования и типов.">
<meta property="og:url" content="https://ru.hexlet.io/courses/ddp/lessons/dispatch/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="crJggR_aTMN9sJ-Ebnnj6dVDnku_lI5cVYU4DuYvlwmdY6u27aTho8vzuxxidhOeFUqz4bejcP7oZaJatChwZw" />
<script src="/vite/assets/inertia-INZxX8jp.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-6pOtQ3OW.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T17:18:13.849Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"pvOqCy39wGcNzineEhm3meHImY-9Te0dIWPOZazI2ItJImE834NtB7uNDUYeFkfuIcG0JbV6E7-cg1Qx_s8_5Q","topics":[{"id":19166,"title":"Добрый вечер, помогите, пожалуйста. Не могу понять как правильно выдать функцию наружу из getMethod. Получается вечный цикл.\n\n`// removed`","plain_title":"Добрый вечер, помогите, пожалуйста. Не могу понять как правильно выдать функцию наружу из getMethod. Получается вечный цикл. // removed ","creator":{"public_name":"Михаил Закатов","id":184896,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40509,"body":"> Нашел ошибку.\n\nГуд! :)","topic_id":19166},{"creator":{"public_name":"Михаил Закатов","id":184896,"is_tutor":false},"id":40501,"body":"Нашел ошибку. Была в другом месте.","topic_id":19166}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":9024,"title":"В теории до 10й минуты всё было понятно, а потом чет пошло поехало =D\nВот что я понял: функция getMethod принимает два аргумента, self и 'имя функции'. Из self мы получаем тип карты, и находим для этого типа соответствующую функцию, название которой было передано во втором аргументе getMethod. Далее найдя эту функцию - она возвращается и мы просто вызываем её вторыми скобками в которые передается \"пара\" самой карты с её названием и функцией (урон). \n\nДалее рассказывается про какой-то `defmethod = definer('PercentCard')`, немного не пойму зачем это нужно и чем является функция definer, какой у нее внутри код? Тут же говорится что новый тип = новый модуль, тоже немного не понял при чем здесь модули, ведь говорили только о какой-то таблице. То есть та самая \"таблица\" как-то реализована именно на модулях? И насколько я сейчас понял, внутренняя реализация функции defmethod скрыта абстракцией и нам просто нужно знать о том, что `defmethod('getName', (card) => car(card))` добавляет в эту \"таблицу\" функцию `(card) => car(card)` под именем 'getName', при этом тип карты привязан где-то внутри `defmethod`. Правильно ли я понял? Заранее большое спасибо за ответ.","plain_title":"В теории до 10й минуты всё было понятно, а потом чет пошло поехало =D Вот что я понял: функция getMethod принимает два аргумента, self и 'имя функции'. Из self мы получаем тип карты, и находим для этого типа соответствующую функцию, название которой было передано во втором аргументе getMethod. Далее найдя эту функцию - она возвращается и мы просто вызываем её вторыми скобками в которые передается \"пара\" самой карты с её названием и функцией (урон). Далее рассказывается про какой-то defmethod = definer('PercentCard'), немного не пойму зачем это нужно и чем является функция definer, какой у нее внутри код? Тут же говорится что новый тип = новый модуль, тоже немного не понял при чем здесь модули, ведь говорили только о какой-то таблице. То есть та самая \"таблица\" как-то реализована именно на модулях? И насколько я сейчас понял, внутренняя реализация функции defmethod скрыта абстракцией и нам просто нужно знать о том, что defmethod('getName', (card) => car(card)) добавляет в эту \"таблицу\" функцию (card) => car(card) под именем 'getName', при этом тип карты привязан где-то внутри defmethod. Правильно ли я понял? Заранее большое спасибо за ответ. ","creator":{"public_name":"sayo Bye","id":141465,"is_tutor":false},"comments":[{"creator":{"public_name":"sayo Bye","id":141465,"is_tutor":false},"id":18207,"body":"Прочитал, но всё равно почему-то осталось чувство, будто я что-то упустил или неправильно понял. \n","topic_id":9024},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":18172,"body":"Добрый день!\n\nДавайте начнём вот с чего: вы читали текстовую часть под видео урока? Там как раз подробно рассматриваются вопросы (про методы и таблицу), аналогичные вашим - всё без кавычек и без магии :)","topic_id":9024},{"creator":{"public_name":"sayo Bye","id":141465,"is_tutor":false},"id":18227,"body":"Подсмотрел пример учителя, думал что дело в целом в неправильной реализации, оказывается всё точно также, просто забыл return в модуле card.js перед getMethod...","topic_id":9024},{"creator":{"public_name":"sayo Bye","id":141465,"is_tutor":false},"id":18209,"body":"По какой-то причине данная функция зацикливается, не пойму в чем причина. \n\n```\n// removed\n```","topic_id":9024}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":15834,"title":"Может, до меня под вечер туго доходит, но понять как реализовать getMethod из теоретической части так и не получилось, реализовал методом подбора. В итоге получилось, возможно чуть менее эффективно, зато покороче, чем РУ - с одним ифом: https://ru.hexlet.io/code_reviews/49747#file-1\n\n----\n\nЧто-то мне кажется, что есть какая-то проблема в этой реализации, то ли в `damage`, то ли во всей этой типизации. Не могу пока уловить нить сомнений.","plain_title":"Может, до меня под вечер туго доходит, но понять как реализовать getMethod из теоретической части так и не получилось, реализовал методом подбора. В итоге получилось, возможно чуть менее эффективно, зато покороче, чем РУ - с одним ифом: https://ru.hexlet.io/code_reviews/49747#file-1 Что-то мне кажется, что есть какая-то проблема в этой реализации, то ли в damage, то ли во всей этой типизации. Не могу пока уловить нить сомнений. ","creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":33524,"body":"1. У тебя нарушены абстракции, ко всем данным идет обращение как к парам.\n2. Убрав иф ты сделал сложнее проверку, причем с точки зрения построения логики нет смысла смотреть метод если не был найден подходящий тип.\n\n","topic_id":15834}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":25962,"title":"Доброго времени суток Hexlet!\nОчень понравилась красота присвоения в решении учителя, сравнивая со своим топорным)\nНо, остался вопрос, чем при создании условий вложенные, лучше чем `условие1 && условие2`","plain_title":"Доброго времени суток Hexlet! Очень понравилась красота присвоения в решении учителя, сравнивая со своим топорным) Но, остался вопрос, чем при создании условий вложенные, лучше чем условие1 && условие2 ","creator":{"public_name":"Vladislav Nikolaenko","id":192158,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":55536,"body":"Используемый вариант зависит от смысла, иногда вложенный if лучше описывает происходящее, иногда один с составным логическим выражением.","topic_id":25962}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":35878,"title":"Здравствуйте! Как оценить какое решение лучше: [ссылка](https://ru.hexlet.io/code_reviews/187321)","plain_title":"Здравствуйте! Как оценить какое решение лучше: ссылка (https://ru.hexlet.io/code_reviews/187321) ","creator":{"public_name":"Александр Щербаков","id":253191,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":78453,"body":"**Александр**, день добрый!\n\nПопробуйте сравнить решения между собой, и проанализировать отличия между ними. В чём вы видите разницу?","topic_id":35878}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":17564,"title":"В целом понятно, получается очень расширяемо, и по сути дела, это то же самое, что дают современные ОО языки только неявно:\n1. Метка (тэг) у данных - это один из явных \"типов\" в ОО программе.\n2. Каждый \"тип\" реализует методы, в нашей программе это ```defmethod```, в ОО программе - это сам реальный метод внутри \"типа\".\n3. Таблица - у нас явная, в ОО программе - это делается за нас неявно.\n\nА насколько, правильно было бы всегда передавать вторым параметром здоровье. И прямо из метода возвращать модифицированный показатель здоровья?\n\nИли пойти еще дальше, завести \"тип\" игрок (```(name, health)```) и передавать прямо его вторым параметром. Это могло бы помочь если бы существовала карта, которая например заражает игрока, и каждый ход он теряет здоровье.\n\nТипа так:\n```\nconst damagedPlayer = damage(card, player);\n// (name, health, healthModifier => do something with health)\n```\nи каждый ход, каким то образом применять ```healthModifier```, к обоим игрокам.\n\nТут по идее кто то должен знать реализацию другого типа: либо \"игрок\" знает как сохранить в себе модификатор урона от \"карты\", либо \"карта\" знает как сохранить урон в \"игроке\" + метод который достает урон из \"игрока\" и применяет к нему же.\n\nКратко, вопрос: насколько плохо если один \"тип\" знает как работает другой, и по сути модифицирует его, как в примере с ```healthModifier```? И какая альтернатива такой реализации?\n","plain_title":"В целом понятно, получается очень расширяемо, и по сути дела, это то же самое, что дают современные ОО языки только неявно: 1. Метка (тэг) у данных - это один из явных \"типов\" в ОО программе. 2. Каждый \"тип\" реализует методы, в нашей программе это defmethod, в ОО программе - это сам реальный метод внутри \"типа\". 3. Таблица - у нас явная, в ОО программе - это делается за нас неявно. А насколько, правильно было бы всегда передавать вторым параметром здоровье. И прямо из метода возвращать модифицированный показатель здоровья? Или пойти еще дальше, завести \"тип\" игрок ((name, health)) и передавать прямо его вторым параметром. Это могло бы помочь если бы существовала карта, которая например заражает игрока, и каждый ход он теряет здоровье. Типа так: const damagedPlayer = damage(card, player); // (name, health, healthModifier => do something with health) и каждый ход, каким то образом применять healthModifier, к обоим игрокам. Тут по идее кто то должен знать реализацию другого типа: либо \"игрок\" знает как сохранить в себе модификатор урона от \"карты\", либо \"карта\" знает как сохранить урон в \"игроке\" + метод который достает урон из \"игрока\" и применяет к нему же. Кратко, вопрос: насколько плохо если один \"тип\" знает как работает другой, и по сути модифицирует его, как в примере с healthModifier? И какая альтернатива такой реализации? ","creator":{"public_name":"Александр Щербаченко","id":89234,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":37178,"body":"Да тут можно всякого наворотить). Могу порекомендовать еще сюда глянуть https://clojure.org/reference/multimethods это более мощный полиморфизм, реализованный через множественную диспетчеризацию.","topic_id":17564}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":14749,"title":"Не могу победить ту же ошибку что у людей внизу:\nTypeError: (0 , _generic.getMethod)(...) is not a function\n\nВсе прочесал, опечатки не нашел, что я делаю не так? В repl.it все работает как и должно, единственное что я не использую там экспорты из файлов.\n\nGeneric.js\n```\nimport { cons, car, cdr, toString as pairToString } from 'hexlet-pairs'; // eslint-disable-line\nimport { l, cons as consList, isEmpty, head, tail } from 'hexlet-pairs-data'; // eslint-disable-line\nimport { attach, typeTag, contents } from './type'; // eslint-disable-line\n\nlet methods = l();\n\nexport const getMethod = (obj, methodName) => {\n // BEGIN (write your solution here)\n const funcFinder = (funcList) => {\n if(isEmpty(funcList)) {\n return 'No matches in the funcList';\n }\n\n const current = head(funcList);\n const next = tail(funcList);\n\n if(typeTag(current) === typeTag(obj)) {\n if(car((contents(current))) === methodName) {\n return cdr(contents(current));\n }\n return funcFinder(next);\n } \n\n return funcFinder(next);\n }\n\n return funcFinder(methods);\n // END\n};\n\nexport const definer = type =>\n (methodName, f) => {\n methods = consList(attach(type, cons(methodName, f)), methods);\n };\n```\n\nCard.js\n```\nimport { getMethod } from './generic'; // eslint-disable-line\nimport { contents } from './type'; // eslint-disable-line\n\nexport const getName = self =>\n getMethod(self, 'getName')(contents(self));\n\n// BEGIN (write your solution here)\nexport const damage = self => getMethod(self, 'damage')(contents(self));\n// END\n```\n\nSimpleCard.js\n```\nimport { cons, car, cdr, toString as pairToString } from 'hexlet-pairs'; // eslint-disable-line\nimport { definer } from './generic'; // eslint-disable-line\nimport { attach } from './type'; // eslint-disable-line\n\n// BEGIN (write your solution here)\nconst defmethod = definer('SimpleCard');\n\nconst make = (name, damage) =>\n attach('Simplecard', cons(name, damage));\n\nexport default make;\n\ndefmethod('getName', self => car(self));\n\ndefmethod('damage', (self, health) => cdr(self));\n// END\n```","plain_title":"Не могу победить ту же ошибку что у людей внизу: TypeError: (0 , _generic.getMethod)(...) is not a function Все прочесал, опечатки не нашел, что я делаю не так? В repl.it все работает как и должно, единственное что я не использую там экспорты из файлов. Generic.js ``` import { cons, car, cdr, toString as pairToString } from 'hexlet-pairs'; // eslint-disable-line import { l, cons as consList, isEmpty, head, tail } from 'hexlet-pairs-data'; // eslint-disable-line import { attach, typeTag, contents } from './type'; // eslint-disable-line let methods = l(); export const getMethod = (obj, methodName) => { // BEGIN (write your solution here) const funcFinder = (funcList) => { if(isEmpty(funcList)) { return 'No matches in the funcList'; } const current = head(funcList); const next = tail(funcList); if(typeTag(current) === typeTag(obj)) { if(car((contents(current))) === methodName) { return cdr(contents(current)); } return funcFinder(next); } return funcFinder(next); } return funcFinder(methods); // END }; export const definer = type => (methodName, f) => { methods = consList(attach(type, cons(methodName, f)), methods); }; ``` Card.js ``` import { getMethod } from './generic'; // eslint-disable-line import { contents } from './type'; // eslint-disable-line export const getName = self => getMethod(self, 'getName')(contents(self)); // BEGIN (write your solution here) export const damage = self => getMethod(self, 'damage')(contents(self)); // END ``` SimpleCard.js ``` import { cons, car, cdr, toString as pairToString } from 'hexlet-pairs'; // eslint-disable-line import { definer } from './generic'; // eslint-disable-line import { attach } from './type'; // eslint-disable-line // BEGIN (write your solution here) const defmethod = definer('SimpleCard'); const make = (name, damage) => attach('Simplecard', cons(name, damage)); export default make; defmethod('getName', self => car(self)); defmethod('damage', (self, health) => cdr(self)); // END ``` ","creator":{"public_name":"Serge Umrikhin","id":166875,"is_tutor":false},"comments":[{"creator":{"public_name":"Serge Umrikhin","id":166875,"is_tutor":false},"id":30983,"body":"Перезалил свой код, предварительно сбросив задание на обновленную версию, теперь ситуация такая:\n\n```\n FAIL __tests__/test.js\n CardGame\n ✓ #flow 1 (10ms)\n ✕ #flow 2 (6ms)\n\n ● CardGame › #flow 2\n\n expect(received).toBe(expected) // Object.is equality\n \n Expected value to be:\n \"(2, 3)\"\n Received:\n \"(NaN, 3)\"\n\n 63 | });\n 64 | });\n > 65 | \n \n at Object.it (__tests__/test.js:65:70)\n```","topic_id":14749}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":6025,"title":"`generic.js`\n```\n // BEGIN (write your solution here)\n const searchMethod = (list) => {\n if (isEmpty(list)) {\n return null;\n }\n /*\n methods\n (\n (type, (functionName, functionBody)),\n ...\n )\n */\n if (typeTag(obj) === pairs.car(head(list))\n && methodName === pairs.car(pairs.cdr(head(list)))) {\n return pairs.cdr(pairs.cdr(head(list)));\n }\n return searchMethod(tail(list));\n };\n return searchMethod(methods);\n // END\n```\nПочему не могу из рекурсии выйти?)","plain_title":"generic.js // BEGIN (write your solution here) const searchMethod = (list) => { if (isEmpty(list)) { return null; } /* methods ( (type, (functionName, functionBody)), ... ) */ if (typeTag(obj) === pairs.car(head(list)) && methodName === pairs.car(pairs.cdr(head(list)))) { return pairs.cdr(pairs.cdr(head(list))); } return searchMethod(tail(list)); }; return searchMethod(methods); // END Почему не могу из рекурсии выйти?) ","creator":{"public_name":"","id":88562,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":10913,"body":"Добрый день! Начните с рекомендаций во [вспомогательных материалах по выполнению практики](http://help.hexlet.io/category/8-category)","topic_id":6025}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":7835,"title":"Решил задачу с большим сомнением, все время думал, что что-то не так написал. Тесты прошли на ура и я пошел смотреть решение учителя и понял в откуда были сомнения, я забыл сделать проверку имени метода.\n\n```\n // removed\n```\nЭто прошло тесты, хотя по логике, если будет больше 2 методов в один тип, то я всегда буду возвращать первый попавшийся метод.","plain_title":"Решил задачу с большим сомнением, все время думал, что что-то не так написал. Тесты прошли на ура и я пошел смотреть решение учителя и понял в откуда были сомнения, я забыл сделать проверку имени метода. // removed Это прошло тесты, хотя по логике, если будет больше 2 методов в один тип, то я всегда буду возвращать первый попавшийся метод. ","creator":{"public_name":"Никита Нафранец","id":100653,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":14944,"body":"Добрый день, Никита!\n\n> хотя по логике, если будет больше 2 методов в один тип, то я всегда буду возвращать первый попавшийся метод.\n\nтак по факту и происходит, потому что методов два: `getName` и `damage`, и в вашем случае, судя по всему, всегда отрабатывает `damage` - т.е., это ведёт к тому, что, к примеру, в константу`cardName` записывается не имя карты, а ущерб :) и в log идет некорректное сообщение (но в тестах оно не проверялось). Спасибо за замечание, я поправил тесты, можете сбросить упражнение и снова попробовать проверить!","topic_id":7835}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}},{"id":19332,"title":"Подскажите, пожалуйста, вот мой код из файла simpleCard.js\n\n`//removed`\n\n я получаю ошибку в функции getname :```Argument must be pair, but it was 'undefined'\n\n 11 | export default make;\n 12 | const getname = (self) =>{\n > 13 | return car(self)\n | ^\n 14 | }\n 15 | defmethod('getName',getname );```, я не могу понять из-за чего она возникает","plain_title":"Подскажите, пожалуйста, вот мой код из файла simpleCard.js //removed я получаю ошибку в функции getname :```Argument must be pair, but it was 'undefined' 11 | export default make; 12 | const getname = (self) =>{ > 13 | return car(self) | ^ 14 | } 15 | defmethod('getName',getname );```, я не могу понять из-за чего она возникает ","creator":{"public_name":"константин гальперин","id":180844,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40865,"body":"Добрый день!\n\nКод, который вы привели в комментарии, не отформатирован. Поэтому его тяжело читать и аналзировать. Отформатируйте его с помощью Markdown, пожалуйста. Как это делается можно посмотреть в нашем гайде: https://guides.hexlet.io/markdown/","topic_id":19332},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":41550,"body":"> я не могу понять из-за чего она возникает\n\nПохоже, что в функцию `getname` при вызове на место параметра `x` приходит значение `undefined`. Вам надо проанализировать тот участок кода, где эта функция вызывается, какой аргумент в неё передаётся.","topic_id":19332},{"creator":{"public_name":"константин гальперин","id":180844,"is_tutor":false},"id":40955,"body":"Подскажите, пожалуйста, вот мой код из файла simpleCard.js\n```// BEGIN (write your solution here)\nconst defmethod = definer('SimpleCard');\n\nconst make = (name, damage) =>\n attach('SimpleCard', cons(name, damage));\n\nexport default make;\nconst getname = (x) =>{\n return car(x)\n}\ndefmethod('getName',getname );\n\nconst simpleCardDamage = (self, health) =>{\n return cdr(self)\n}\ndefmethod('damage', simpleCardDamage)\n// END\n```\nя получаю ошибку в функции getname :Argument must be pair, but it was 'undefined'\n```11 | export default make;\n 12 | const getname = (x) =>{\n > 13 | return car(x)\n | ^\n 14 | }\n 15 | defmethod('getName',getname );```\n ```\nя не могу понять из-за чего она возникает\n","topic_id":19332}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Диспетчеризация по типу. Аддитивность.","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":465,"slug":"js_ddp_dispatch_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## card.js\n\nРеализуйте и экспортируйте обобщенную функцию `damage()` отвечающую за вычисление нанесённого урона.\n\n## generic.js\n\nРеализуйте функцию `getMethod()`, которая производит поиск конкретной реализации функции для переданного типа.\n\n## simpleCard.js\n\nРеализуйте интерфейс типа `SimpleCard`.\n\n### Подсказки\n\n* В `percentCard.js` можно подсмотреть пример использования.\n* Обратите внимание в модуле `generic.js` на следующую строчку: `let methods = l();`. Именно здесь определяется та самая виртуальная таблица, механизм работы с которой подробно описан в текстовой части этого урока.\n","prepared_readme":"## card.js\n\nРеализуйте и экспортируйте обобщенную функцию `damage()` отвечающую за вычисление нанесённого урона.\n\n## generic.js\n\nРеализуйте функцию `getMethod()`, которая производит поиск конкретной реализации функции для переданного типа.\n\n## simpleCard.js\n\nРеализуйте интерфейс типа `SimpleCard`.\n\n### Подсказки\n\n* В `percentCard.js` можно подсмотреть пример использования.\n* Обратите внимание в модуле `generic.js` на следующую строчку: `let methods = l();`. Именно здесь определяется та самая виртуальная таблица, механизм работы с которой подробно описан в текстовой части этого урока.\n","has_solution":true,"entity_name":"Диспетчеризация по типу. Аддитивность."},"units":[{"id":1387,"name":"theory","url":"/courses/ddp/lessons/dispatch/theory_unit"},{"id":1389,"name":"quiz","url":"/courses/ddp/lessons/dispatch/quiz_unit"},{"id":1388,"name":"exercise","url":"/courses/ddp/lessons/dispatch/exercise_unit"}],"links":[],"ordered_units":[{"id":1387,"name":"theory","url":"/courses/ddp/lessons/dispatch/theory_unit"},{"id":1389,"name":"quiz","url":"/courses/ddp/lessons/dispatch/quiz_unit"},{"id":1388,"name":"exercise","url":"/courses/ddp/lessons/dispatch/exercise_unit"}],"id":702,"slug":"dispatch","state":"approved","name":"Диспетчеризация по типу. Аддитивность.","course_order":500,"goal":"Понять, что такое аддитивные программы и как динамическая диспетчеризация поможет нам строить такие программы. Разобраться, что такое диспетчеризация в контексте программирования и типов.","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"172395914","theory":"Решаемая задача: реализовать диспетчеризацию по типу своими руками.\n\nРазложим весь процесс на примере библиотеки для работы с геометрическими\nфигурами. Предположим, что мы можем создавать разные фигуры, такие как\nтреугольник, круг или квадрат. Кроме специфических свойств, у фигур\nесть и общие, например, периметр или площадь. А так как мы, гипотетически,\nхотим работать с фигурами единообразно, то реализуем диспетчеризацию по типу\nна примере функции, вычисляющей общую площадь фигур, размещенных на воображаемом\nхолсте (так обычно называется область, на которой происходит рисование в графических\nредакторах)\n\n\n\nПри отсутствии готовой диспетчеризации нам придется делать ее руками\nв том месте, где требуется обобщенное поведение:\n\n```javascript\nimport { reduce } from 'js-pairs-data'\nimport * as circle from './circle'\nimport * as square from './square'\nimport * as triangle from './triangle'\nimport { typeTag } from './type'\n\nconst getTotalArea = figures => reduce((figure, total) => {\n let area\n switch (typeTag(figure)) {\n case 'square':\n area = square.getArea(figure)\n break\n case 'circle':\n area = circle.getArea(figure)\n break\n case 'triangle':\n area = triangle.getArea(figure)\n break\n };\n return area + total\n}, 0, figures)\n```\n\nС наличием автоматического механизма диспетчеризации (не важно реализован он в самом\nязыке или нами самостоятельно) код сокращается до следующего:\n\n```javascript\nimport * as circle from './circle'\nimport * as square from './square'\nimport { reduce, l } from 'js-pairs-data'\nimport { getArea } from './figures'\n\nconst getTotalArea = figures => reduce((figure, total) => getArea(figure) + total, 0, figures)\n\nconst figures = l(circle.make(2), square.make(3))\ngetTotalArea(figures)\n// 12.57 + 9\n// 21,57\n```\n\nВ примере выше функция `getArea` сама по себе не занимается вычислением площади. Это вычисление\nреализовано для каждой фигуры совершенно независимо. Все, что делает `getArea`, это перенаправляет\nзапрос на расчет площади в соответствующую функцию.\n\nАлгоритм диспетчеризации в примере выше следующий:\n\n1. `getArea` извлекает тип (его название) из фигуры.\n1. `getArea` обращается к глобальному хранилищу (виртуальная таблица) для поиска нужной реализации\n настоящей функции вычисления площади.\n1. Если реализация найдена, то `getArea` ее вызывает с нужными аргументами и возвращает результат наружу.\n\n\n\nВажное следствие этого алгоритма в том, что для работы автоматической диспетчеризации необходимо, чтобы\nреальные функции `getArea` были занесены в виртуальную таблицу, иначе до них невозможно будет достучаться.\n\n## Виртуальная таблица\n\nВыполняет две задачи, которые мы рассмотрим ниже.\n\n### Регистрация\n\nПервая задача — это регистрация функций тех типов, по которым мы планируем делать диспетчеризацию:\n\n```javascript\nexport const definer = type => (methodName, f) => { /* ... */ }\n```\n\nТогда модуль, реализующий наш тип, будет выглядеть так:\n\n```javascript\n// circle.js\nimport { definer } from './generic'\nimport { attach, contents } from './type'\n\nconst defmethod = definer('Circle')\n\nexport const make = radius => attach('Circle', radius)\n\n// Так как для определения круга не нужно ничего кроме радиуса, сам круг и есть радиус,\n// Код снаружи об этом не знает!\nexport const getRadius = circle => contents(circle)\n\nexport const getArea = circle => (getRadius(circle) ** 2) * Math.PI\ndefmethod('getArea', getArea)\n\nexport const getPerimeter = circle => 2 * getRadius(circle) * Math.PI\ndefmethod('getPerimeter', getPerimeter)\n```\n\n\n\nКак видно из примера выше, по большей части `Circle` является типичной абстракцией, за исключением\nпары моментов:\n\n1. Внутри создается привязка к типу. Соответственно все селекторы должны сначала извлечь данные и потом\n уже работать.\n1. С помощью `definer` происходит регистрация нужных (радиус специфичен для круга, по нему диспетчеризация\n не нужна) функций в нашей виртуальной таблице.\n\nНаш модуль `generic` ничего не знает про `Circle`, да и вообще ничего не знает про тех, кто его использует.\nВ общем случае, для регистрации функции ему нужно знать три значения: имя типа, имя функции и само тело\nфункции, или, другими словами, мы имеем такой интерфейс: `register('TypeName', 'funcName', funcBody)`. А код\nрегистрации выглядел бы так:\n\n```javascript\nregister('Circle', 'getArea', getArea)\nregister('Circle', 'getPerimeter', getPerimeter)\n```\n\nОбратите внимание на то, что мы находимся внутри модуля `Circle` и нам приходится в каждом вызове `register`\nпередавать его название. Это единственная причина, по которой существует функция `defmethod`. То есть мы\nсначала специфицируем имя типа для которого будем заполнять функции, а потом делаем это без повторений.\n\nС точки зрения теории мы использовали так называемое [частичное применение функции](https://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5):\n\n```javascript\nconst defmethod = partial(register, 'Circle')\n```\n\nЧто эквивалентно:\n\n```javascript\nconst defmethod = (funcName, funcBody) => register('Circle', funcName, funcBody)\n```\n\nНу и самое главное, а где же происходит регистрация? Куда записываются все эти данные о типах?\nОтвет достаточно простой. Фактически в наш прекрасный чистый код мы вводим внешнее изменяемое\nсостояние и заполняем его функцией с побочными эффектами (`definer`). Если открыть модуль\n`generic`, то можно увидеть:\n\n```javascript\nlet methods = l()\n```\n\nВ свою очередь, все функции, которым нужен доступ к таблице, получают его посредством замыкания.\nПричем только `definer` изменяет ее, а все остальные - читают.\n\nПолучается, что `methods` наполняется в тот момент, когда загружаются типы (выполняется `import`), использующие модуль\n`generic` для регистрации своих функций. Например:\n\n```javascript\n// Первый встреченный импорт модуля `circle` приведет к тому, что внутри него выполнятся все определения.\nimport * as circle from './circle'\n```\n\n### Поиск\n\nВторая задача это, собственно, поиск этих функций:\n\n```javascript\n// извлечение типа объекта происходит внутри с помощью typeTag\nexport const getMethod = (obj, funcName) => { /* ... */ }\n```\n\nДля поиска подходящей функции достаточно знать два параметра: имя типа и имя функции. Если\nфункция найдена, то `getMethod` возвращает ее вызывающему коду, который, в свою очередь,\nуже делает вызов найденной функции.\n\n```javascript\n// figures.js\nimport { getMethod } from './generic'\nimport { contents } from './type'\n\nexport const getArea = (figure) => {\n const realGetArea = getMethod(figure, 'getArea')\n // В случае с кругом эквивалентно:\n // circle.getArea(figure)\n return realGetArea(figure)\n}\n```\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1378,"name":"theory","url":"/courses/ddp/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":1378,"name":"theory","url":"/courses/ddp/lessons/intro/theory_unit"}],"id":698,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Познакомиться с курсом и с проектом карточной игры, над которым мы будем работать в течение всего курса, по ходу знакомясь с важными концепциями программирования.","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"172391980","theory":"<figure>\n <blockquote class=\"blockquote\">\n <p>Я изобрел понятие «объектно-ориентированный», но могу заявить, что не имел в виду C++ при этом.</p>\n </blockquote>\n <figcaption class=\"blockquote-footer\">\n Alan Kay\n </figcaption>\n</figure>\n\nНа текущий момент мы уже достаточно много знаем, как строить модульные программы и теперь пришло время поговорить о том, как строить расширяемые программы. В программировании существует понятие аддитивность, и эта тема будет центральной в нашем курсе.\n**Аддитивность** – это расширение возможности вашей программы без переписывания исходного кода.\n\n## Какие темы будут разобраны?\n\n- Пользовательские типы\n- Динамическая диспетчеризация (по типу)\n- Полиморфизм\n- Передача сообщений\n- Объектно-ориентированное программирование\n\nВо-первых, это пользовательские типы (типы данных, которые мы можем создавать самостоятельно).\nМы поговорим про такое важное понятие, как диспетчеризация и конкретно о динамической диспетчеризации (по типу). Разберём понятие полиморфизма. О нём многие слышали, но очень не многие понимают, что оно по-настоящему обозначает. Эту интересную тему мы разберём в одном из последних уроков. Также мы поговорим про передачу сообщений. И, наконец, придём к пониманию объектно-ориентированного программирования: что это такое, как с ним работать и почему, например, JavaScript считается объектно-ориентированным языком.\n\n## Новый синтаксис\n\n- Свойства\n- Тип Данных: Объект\n- Классы\n\nВ этом курсе появится новый синтаксис, который включает в себя несколько понятий.\nПервое – это **свойства**, которые используются в JavaScript даже для самых базовых вещей. Мы избегали этой темы, чтобы не отвлекаться от модульности. Теперь пришло время начать с ними работать.\n\nВо-вторых, мы разберём тип данных, который называется **объект**. Это не то же самое, что объект в объектно-ориентированном программировании, но иногда названия, которые используются в языках программирования несут свой смысл, и к этому нужно будет просто привыкнуть.\n\nИ последнее, с чем мы познакомимся – это **классы**. Ниже приведён небольшой пример класса, который будет использоваться в нашей программе. Мы подробно разберем его в нашем курсе.\n\n```javascript\nexport default class PercentCard {\n constructor(name, percent) {\n this.name = name\n this.percent = percent\n }\n\n damage(health) {\n return Math.round(health * (this.percent / 100))\n }\n}\n```\n\n## Проект: Карточная игра\n\n\n\nВ этом курсе мы будем работать над своей **карточной игрой**. Она будет без графического интерфейса. Мы будем выводит процесс игры в виде текста.\n\nЭта карточная игра необычная и не такая, как все привыкли видеть, играя в обычные карты. Игра будет про волшебников, в процессе которой происходит нападение друг на друга. Каждая карта описывает атаку, урон юнита и так далее. В нашем случае она будет простая, но на ней будет хорошо видна проблематика, которую мы собираемся отрабатывать.\n\n## Процесс игры\n\nДавайте посмотрим на процесс без кода. Как будет происходить вывод нашей игры:\n\n```\nНачинаем бой!\n\nИгрок 'John' применил 'Прохладный чыонг-бонг рыка'\nпротив 'Ada' и нанес урон '3'\n\nИгрок 'Ada' применил 'Воздушный змей клеветы'\nпротив 'John' и нанес урон '1'\n\nИгрок 'John' применил 'Проказливый рубитель крови'\nпротив 'Ada' и нанес урон '2'\n\nAda был убит\n```\n\nДля карт, которые представляют собой оружие, используются названия позаимствованные из Diablo. В сети существует множество генераторов названий предметов из этой игры. Они довольно прикольные, поэтому будет здорово здесь ими воспользоваться.\n\nГлядя на процесс, очевидно, что игра рассчитана на двух игроков, которые по очереди делают ходы. Сначала один игрок наносит урон, потом второй, затем снова первый и так по кругу. Урон зависит от текущей карты. И она не всегда наносит конкретный урон. Он может зависеть от текущего здоровья (например, снимать 50% здоровья) и других характеристик.\n\nПредположим, что стартовый уровень жизни у каждого игрока 10. А тот, у кого уровень жизни дойдёт до нуля или станет отрицательным, считается убитым. В примере видно, что игрок с именем Ada был убит первым.\n\n## Разработка через тесты\n\nНаш курс будет примером разработки через так называемый Test Driven Development. Это означает, что тесты пишутся до кода.\n\n```\nДизайн кода определяется тестами.\n\nВместо того, чтобы сосредотачиваться на\nвнутреннем представлении, сначала прорабатываются\nварианты использования этого кода.\n```\n\nЗачем это нужно?\n\n **Test Driven Development** – это такая методология, которая существует уже достаточно много лет. Она входит в концепцию экстремального программирования. Её главная фишка, что мы не фокусируемся на том, как будет устроен код внутри. Гораздо важнее, как им пользоваться. Именно дизайн кода (так называемый API), интерфейс, который вы отдаёте другим для использования и является определяющим. Если вы делаете хорошую абстракцию, то внутреннее содержимое не так важно. Вы всегда можете его переписать.\n\nОчень часто бывает так (это касается не только программирования), что люди делают что-то, но пользоваться этим невозможно. Это касается всего в нашей жизни: любого производства, продукта, процесса и так далее. Test Driven Development в первую очередь, заставляет нас думать о дизайне кода, о том, как его использовать, а затем строить внутреннюю структуру.\n\n## Обучение на Хекслете\n\nПовторим аспекты, которые мы используем для того, чтобы обучение было лучше и была проработка правильных вещей.\n\n- Неизменяемость (функциональный стиль)\n- СИКП\n- Обучение через рефакторинг\n- Разработка через тесты\n- Знания, которые не устаревают\n\nМы продолжаем использовать функциональный стиль. При этом постепенно начинаем вводить изменяемость.\n\nКурс также продолжает традицию основ на СИКП.\n\nМы делаем обучение через рефакторинг, то есть мы не рассказываем сразу, как правильно что-то сделать. Чтобы понять достаточно сложные темы по-настоящему, нужно пережить опыт и самостоятельно упереться в сложности. Поэтому уроки будут построены следующим образом: мы делаем какое-то решение, которое у вас (по нашей задумке) будет вызывать вопросы «А почему так?» и будете видеть, что здесь есть какие-то недоработки. В свою очередь следующий урок будет говорить об этом подробно и уже показывать следующий шаг для решения возникших проблем, и так далее. Постепенно мы будем приходить к общему решению, которое исправляет все недостатки предыдущих. Это так называемое обучение через рефакторинг.\n\nТакже напоминаю, что в этом курсе разработка будет происходить через тесты (о чём мы уже с вами говорили).\n\nЗнания текущего курса являются универсальными. Будет совсем чуть-чуть JavaScript специфики, но в основном всё, что здесь рассказывается — это ключ к пониманию, как устроены все современные языки, и какие проблемы они решают.\n"},"id":121,"slug":"ddp","challenges_count":3,"name":"JS: Программирование, управляемое данными","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы познакомитесь с программированием, управляемым данными. Вы узнаете, как использовать данные для управления логикой программы. Вы научитесь создавать динамические приложения и работать с классами, узнаете о полиморфизме и его типах, познакомитесь с диспетчеризацией по типу и аддитивностью. Знания из этого курса помогут создавать мощные и гибкие приложения, которые легко адаптируются к изменениям данных.","kind":"additional","updated_at":"2026-01-20T11:53:13.077Z","language":"javascript","duration_cache":39000,"skills":["Делать свое ООП с типами и диспетчеризацией","Использовать инверсию зависимостей для изменения поведения кода без его переписывания","Создавать классы и использовать их в JavaScript","Применять полиморфизм для уменьшения количества условных конструкций и дублирования"],"keywords":["ООП","Динамическая диспетчеризация","Типы данных","Объекты"],"lessons_count":9,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODkwOSwicHVyIjoiYmxvYl9pZCJ9fQ==--8d6ae873c203221995a5f2afe08cdb3b594e095f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[{"stack":{"id":20,"slug":"js-sicp","title":"СИКП на JS","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4050,"duration_in_months":1},"id":28,"slug":"js-sicp","title":"СИКП на JS","subtitle":"Навык понимать программы на фундаментальном уровне, уверенно проходить собеседования и решать сложные задачи","subtitle_for_lists":"Навык фундаментального программирования","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"js-sicp","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/ddp/lessons/dispatch/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">JS: Программирование, управляемое данными</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Диспетчеризация по типу. Аддитивность.</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Диспетчеризация по типу. Аддитивность.","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"JS: Программирование, управляемое данными"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Решаемая задача: реализовать диспетчеризацию по типу своими руками.</p>
<p>Разложим весь процесс на примере библиотеки для работы с геометрическими
фигурами. Предположим, что мы можем создавать разные фигуры, такие как
треугольник, круг или квадрат. Кроме специфических свойств, у фигур
есть и общие, например, периметр или площадь. А так как мы, гипотетически,
хотим работать с фигурами единообразно, то реализуем диспетчеризацию по типу
на примере функции, вычисляющей общую площадь фигур, размещенных на воображаемом
холсте (так обычно называется область, на которой происходит рисование в графических
редакторах)</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODk0MiwicHVyIjoiYmxvYl9pZCJ9fQ==--f3e4423b60500c206067a11b01975bd2187a45f6/cad.jpg" alt="CAD" loading="lazy"/></p>
<p>При отсутствии готовой диспетчеризации нам придется делать ее руками
в том месте, где требуется обобщенное поведение:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { reduce } from 'js-pairs-data'
import * as circle from './circle'
import * as square from './square'
import * as triangle from './triangle'
import { typeTag } from './type'
const getTotalArea = figures => reduce((figure, total) => {
let area
switch (typeTag(figure)) {
case 'square':
area = square.getArea(figure)
break
case 'circle':
area = circle.getArea(figure)
break
case 'triangle':
area = triangle.getArea(figure)
break
};
return area + total
}, 0, figures)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>С наличием автоматического механизма диспетчеризации (не важно реализован он в самом
языке или нами самостоятельно) код сокращается до следующего:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import * as circle from './circle'
import * as square from './square'
import { reduce, l } from 'js-pairs-data'
import { getArea } from './figures'
const getTotalArea = figures => reduce((figure, total) => getArea(figure) + total, 0, figures)
const figures = l(circle.make(2), square.make(3))
getTotalArea(figures)
// 12.57 + 9
// 21,57</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В примере выше функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getArea</code> сама по себе не занимается вычислением площади. Это вычисление
реализовано для каждой фигуры совершенно независимо. Все, что делает <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getArea</code>, это перенаправляет
запрос на расчет площади в соответствующую функцию.</p>
<p>Алгоритм диспетчеризации в примере выше следующий:</p>
<ol>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getArea</code> извлекает тип (его название) из фигуры.</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getArea</code> обращается к глобальному хранилищу (виртуальная таблица) для поиска нужной реализации
настоящей функции вычисления площади.</li>
<li>Если реализация найдена, то <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getArea</code> ее вызывает с нужными аргументами и возвращает результат наружу.</li>
</ol>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODk0MywicHVyIjoiYmxvYl9pZCJ9fQ==--a56dff968451f37b06741df5935b0c2f933e7f1c/dispatch01.png" alt="dispatch01" loading="lazy"/></p>
<p>Важное следствие этого алгоритма в том, что для работы автоматической диспетчеризации необходимо, чтобы
реальные функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getArea</code> были занесены в виртуальную таблицу, иначе до них невозможно будет достучаться.</p>
<h2 id="heading-2-1">Виртуальная таблица</h2>
<p>Выполняет две задачи, которые мы рассмотрим ниже.</p>
<h3 id="heading-3-2">Регистрация</h3>
<p>Первая задача — это регистрация функций тех типов, по которым мы планируем делать диспетчеризацию:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">export const definer = type => (methodName, f) => { /* ... */ }</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Тогда модуль, реализующий наш тип, будет выглядеть так:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// circle.js
import { definer } from './generic'
import { attach, contents } from './type'
const defmethod = definer('Circle')
export const make = radius => attach('Circle', radius)
// Так как для определения круга не нужно ничего кроме радиуса, сам круг и есть радиус,
// Код снаружи об этом не знает!
export const getRadius = circle => contents(circle)
export const getArea = circle => (getRadius(circle) ** 2) * Math.PI
defmethod('getArea', getArea)
export const getPerimeter = circle => 2 * getRadius(circle) * Math.PI
defmethod('getPerimeter', getPerimeter)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODk0NSwicHVyIjoiYmxvYl9pZCJ9fQ==--25f8252f53bb8fa7566c5c62ac0d36c1ccf57a7e/dispatch02.png" alt="dispatch02" loading="lazy"/></p>
<p>Как видно из примера выше, по большей части <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Circle</code> является типичной абстракцией, за исключением
пары моментов:</p>
<ol>
<li>Внутри создается привязка к типу. Соответственно все селекторы должны сначала извлечь данные и потом
уже работать.</li>
<li>С помощью <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">definer</code> происходит регистрация нужных (радиус специфичен для круга, по нему диспетчеризация
не нужна) функций в нашей виртуальной таблице.</li>
</ol>
<p>Наш модуль <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">generic</code> ничего не знает про <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Circle</code>, да и вообще ничего не знает про тех, кто его использует.
В общем случае, для регистрации функции ему нужно знать три значения: имя типа, имя функции и само тело
функции, или, другими словами, мы имеем такой интерфейс: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">register('TypeName', 'funcName', funcBody)</code>. А код
регистрации выглядел бы так:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">register('Circle', 'getArea', getArea)
register('Circle', 'getPerimeter', getPerimeter)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Обратите внимание на то, что мы находимся внутри модуля <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Circle</code> и нам приходится в каждом вызове <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">register</code>
передавать его название. Это единственная причина, по которой существует функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">defmethod</code>. То есть мы
сначала специфицируем имя типа для которого будем заполнять функции, а потом делаем это без повторений.</p>
<p>С точки зрения теории мы использовали так называемое <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5" rel="noopener noreferrer" target="_blank">частичное применение функции</a>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const defmethod = partial(register, 'Circle')</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Что эквивалентно:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const defmethod = (funcName, funcBody) => register('Circle', funcName, funcBody)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Ну и самое главное, а где же происходит регистрация? Куда записываются все эти данные о типах?
Ответ достаточно простой. Фактически в наш прекрасный чистый код мы вводим внешнее изменяемое
состояние и заполняем его функцией с побочными эффектами (<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">definer</code>). Если открыть модуль
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">generic</code>, то можно увидеть:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">let methods = l()</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В свою очередь, все функции, которым нужен доступ к таблице, получают его посредством замыкания.
Причем только <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">definer</code> изменяет ее, а все остальные - читают.</p>
<p>Получается, что <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">methods</code> наполняется в тот момент, когда загружаются типы (выполняется <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">import</code>), использующие модуль
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">generic</code> для регистрации своих функций. Например:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// Первый встреченный импорт модуля `circle` приведет к тому, что внутри него выполнятся все определения.
import * as circle from './circle'</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h3 id="heading-3-3">Поиск</h3>
<p>Вторая задача это, собственно, поиск этих функций:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// извлечение типа объекта происходит внутри с помощью typeTag
export const getMethod = (obj, funcName) => { /* ... */ }</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Для поиска подходящей функции достаточно знать два параметра: имя типа и имя функции. Если
функция найдена, то <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getMethod</code> возвращает ее вызывающему коду, который, в свою очередь,
уже делает вызов найденной функции.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// figures.js
import { getMethod } from './generic'
import { contents } from './type'
export const getArea = (figure) => {
const realGetArea = getMethod(figure, 'getArea')
// В случае с кругом эквивалентно:
// circle.getArea(figure)
return realGetArea(figure)
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/js-sicp?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">СИКП на JS</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Навык фундаментального программирования</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png" alt="СИКП на JS" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/ddp/lessons/dispatch/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 9</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/ddp/lessons/dispatch/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-CdBlNCiQ.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>