<!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:20:45 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="dP6ZF1q_M4yNMCo4LBE7Ag8c4E-M9uhKiZq0FOyyj4ObL1IgqMGe7DtzDqAgHst1zxXN5YTBFug0ei5AvrVo7Q";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/js-compound-data/lessons/points/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/js-compound-data/lessons/points/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="ZWZLyf3V2xlkYRlpEmqC3dbMqF74KcnlvSB0l6QbAwSKt4D-D6t2edIiPfEeZXKqFsWF9PAeN0cAwO7D9hzkag" />
<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:20:44.981Z","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":"YeHRm-EV0fg0XpXkljR2s7hLQkSmZEZLw3Mxtoy2aoeOMBqsE2t8mIIdsXyaO4bEeEJv7q5TuOl-k6vi3rGN6Q","topics":[{"id":6634,"title":"Пересмотрел 2 раза видео, в сумме где-то час потратил на освоение темы, вроде бы все как надо усвоилось.\nНаписал функции, но всеравно есть ошибка на 18 строке, судя по-всему что-то не так с функцией симетрии.\n\nПрилагаю код и текст ошибки, пожалуйста дайте ответ в чем проблема. \n\n\n\n// END\n\n// BEGIN (write your solution here)\nexport const symmetricalPoint = point => {\n\tmakePoint(-getX(point), -getY(point));\n};\n// END\n\n```\n\nТекст ошибки:\n\n```\nmake: Entering directory `/usr/src/app'\nbabel-node --presets=es2015 test.js\n/usr/local/lib/node_modules/hexlet-points/node_modules/hexlet-pairs/dist/index.js:66\n throw new Error('Argument must be pair, but it was \\'' + String(pair) + '\\'');\n ^\n\nError: Argument must be pair, but it was 'undefined'\n at Object.toString (/usr/local/lib/node_modules/hexlet-points/node_modules/hexlet-pairs/dist/index.js:66:11)\n at toString (/usr/local/lib/node_modules/hexlet-points/dist/index.js:24:16)\n at Object.<anonymous> (test.js:18:14)\n at Module._compile (module.js:413:34)\n at loader (/usr/local/lib/node_modules/babel-register/lib/node.js:126:5)\n at Object.require.extensions.(anonymous function) [as .js] (/usr/local/lib/node_modules/babel-register/lib/node.js:136:7)\n at Module.load (module.js:357:32)\n at Function.Module._load (module.js:314:12)\n at Function.Module.runMain (module.js:447:10)\n at /usr/local/lib/node_modules/babel-cli/lib/_babel-node.js:161:27\nmake: *** [test] Error 1\nmake: Leaving directory `/usr/src/app'\n```","plain_title":"Пересмотрел 2 раза видео, в сумме где-то час потратил на освоение темы, вроде бы все как надо усвоилось. Написал функции, но всеравно есть ошибка на 18 строке, судя по-всему что-то не так с функцией симетрии. Прилагаю код и текст ошибки, пожалуйста дайте ответ в чем проблема. // END // BEGIN (write your solution here) export const symmetricalPoint = point => { makePoint(-getX(point), -getY(point)); }; // END Текст ошибки: make: Entering directory `/usr/src/app' babel-node --presets=es2015 test.js /usr/local/lib/nodemodules/hexlet-points/nodemodules/hexlet-pairs/dist/index.js:66 throw new Error('Argument must be pair, but it was \\'' + String(pair) + '\\''); ^ Error: Argument must be pair, but it was 'undefined' at Object.toString (/usr/local/lib/nodemodules/hexlet-points/nodemodules/hexlet-pairs/dist/index.js:66:11) at toString (/usr/local/lib/nodemodules/hexlet-points/dist/index.js:24:16) at Object. (test.js:18:14) at Module.compile (module.js:413:34) at loader (/usr/local/lib/nodemodules/babel-register/lib/node.js:126:5) at Object.require.extensions.(anonymous function) as .js (/usr/local/lib/node_modules/babel-register/lib/node.js:136:7) at Module.load (module.js:357:32) at Function.Module.load (module.js:314:12) at Function.Module.runMain (module.js:447:10) at /usr/local/lib/nodemodules/babel-cli/lib/babel-node.js:161:27 make: *** [test] Error 1 make: Leaving directory /usr/src/app' `` ","creator":{"public_name":"Viacheslav","id":121053,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":12164,"body":"1. Не используйте `let` у вас же нет изменяемого состояния\n2. Удалите пожаулйста код, дайте другим возможность пройти самостоятельно. Для оценки кода есть код ревью.\n\nВ остальном все ок","topic_id":6634},{"creator":{"public_name":"Viacheslav","id":121053,"is_tutor":false},"id":12171,"body":"Код удалил, про const вместо let спасибо.\nПро ручное генерирование ```undefined``` не понял","topic_id":6634},{"creator":{"public_name":"Viacheslav","id":121053,"is_tutor":false},"id":12158,"body":"я так понимаю с функцией симетрии надо поставить return и заработает\n\nи в функции distance я тоже налажал судя по всему)","topic_id":6634},{"creator":{"public_name":"Viacheslav","id":121053,"is_tutor":false},"id":12159,"body":"```\n```\nпереписал функцию=прошел задание\n\nно всеравно уважаемый ментор, хотелось бы какой-то комментарий по коду","topic_id":6634},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":12166,"body":"А и еще, нельзя руками генерировать `undefined`, об этом было в основах программирования.","topic_id":6634},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":12172,"body":"Было тут: https://ru.hexlet.io/courses/programming-basics/lessons/null/theory_unit","topic_id":6634}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":3887,"title":"что я не так сделал?\n\n```\nimport { makePoint, getX, getY } from 'hexlet-points';\n\n// BEGIN (write your solution here)\nconst point = makePoint(x, y);\nconst quadrant = (point) => {\n if (getX(point) > 0 && getY(point) > 0) { return 1; }\n else if (getX(point) < 0 && getY(point) > 0) { return 2; }\n else if (getX(point) < 0 && getY(point) < 0) { return 3; }\n else if (getX(point) > 0 && getY(point) < 0) { return 4; }\n else { return undefined; }\n};\n// END\n\n// BEGIN (write your solution here)\nconst symmetricalPoint = (point) => {\n makePoint(-getX(point), -getY(point));\n};\n// END\n\n// BEGIN (write your solution here)\nconst distance = (point1, point2) => {\n const x1 = getX(point1);\n const y1 = getY(point1);\n const x2 = getX(point2);\n const y2 = getY(point2);\n return sqrt((x2−x1) * (x2−x1) + (y2−y1) * (y2−y1));\n};\n```","plain_title":"что я не так сделал? import { makePoint, getX, getY } from 'hexlet-points'; // BEGIN (write your solution here) const point = makePoint(x, y); const quadrant = (point) => { if (getX(point) > 0 && getY(point) > 0) { return 1; } else if (getX(point) < 0 && getY(point) > 0) { return 2; } else if (getX(point) < 0 && getY(point) < 0) { return 3; } else if (getX(point) > 0 && getY(point) < 0) { return 4; } else { return undefined; } }; // END // BEGIN (write your solution here) const symmetricalPoint = (point) => { makePoint(-getX(point), -getY(point)); }; // END // BEGIN (write your solution here) const distance = (point1, point2) => { const x1 = getX(point1); const y1 = getY(point1); const x2 = getX(point2); const y2 = getY(point2); return sqrt((x2−x1) * (x2−x1) + (y2−y1) * (y2−y1)); }; ","creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"comments":[{"creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"id":6502,"body":"Александр, исправил все три замечания. Но вот теперь другое:\n```\nmake: Entering directory `/usr/src/app'\nbabel-node --presets=es2015 test.js\n\n/usr/local/lib/node_modules/chai/lib/chai/assertion.js:107\n throw new AssertionError(msg, {\n ^\nAssertionError: expected undefined to equal '(-10, -10)'\n at Object.<anonymous> (test.js:18:8)\n at Module._compile (module.js:413:34)\n at loader (/usr/local/lib/node_modules/babel-register/lib/node.js:126:5)\n at Object.require.extensions.(anonymous function) [as .js] (/usr/local/lib/node_modules/babel-register/lib/node.js:136:7)\n at Module.load (module.js:357:32)\n at Function.Module._load (module.js:314:12)\n at Function.Module.runMain (module.js:447:10)\n at /usr/local/lib/node_modules/babel-cli/lib/_babel-node.js:161:27\n at Object.<anonymous> (/usr/local/lib/node_modules/babel-cli/lib/_babel-node.js:162:7)\n at Module._compile (module.js:413:34)\nmake: *** [test] Error 1\nmake: Leaving directory `/usr/src/app'\nRun failed! Check and fix errors above!\n```","topic_id":3887},{"creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"id":6496,"body":"`make: Entering directory `/usr/src/app'\nbabel-node --presets=es2015 test.js\n/usr/local/lib/node_modules/babel-core/lib/transformation/file/index.js:556\n throw err;\n ^\n\nSyntaxError: /usr/src/app/solution.js: Unexpected character '−' (26:17)\n const x2 = getX(point2);\n const y2 = getY(point2);\n return sqrt((x2−x1) * (x2−x1) + (y2−y1) * (y2−y1));\n};\n// END\n\n at Parser.pp.raise (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:1378:13)\n at Parser.getTokenFromCode (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:5250:10)\n at Parser.readToken (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:4887:19)\n at Parser.nextToken (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:4877:19)\n at Parser.next (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:4802:10)\n at Parser.pp.parseIdentifier (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:1234:8)\n at Parser.pp.parseExprAtom (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:645:21)\n at Parser.pp.parseExprSubscripts (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:504:19)\n at Parser.pp.parseMaybeUnary (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:484:19)\n at Parser.pp.parseExprOps (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:415:19)\nmake: *** [test] Error 1\nmake: Leaving directory `/usr/src/app'\nRun failed! Check and fix errors above!`","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6505,"body":"Замечательно! Тесты прошли?","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6507,"body":"you are welcome:)","topic_id":3887},{"creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"id":6506,"body":"Да. Спасибо огромное Александр!","topic_id":3887},{"creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"id":6500,"body":"заменил, но тест похоже тоже самое выдает\n\n```\nmake: Entering directory `/usr/src/app'\nbabel-node --presets=es2015 test.js\n/usr/local/lib/node_modules/babel-core/lib/transformation/file/index.js:556\n throw err;\n ^\n\nSyntaxError: /usr/src/app/solution.js: Unexpected character '−' (26:22)\n const x2 = getX(point2);\n const y2 = getY(point2);\n return Math.sqrt((x2−x1) * (x2−x1) + (y2−y1) * (y2−y1));\n};\n// END\n\n at Parser.pp.raise (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:1378:13)\n at Parser.getTokenFromCode (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:5250:10)\n at Parser.readToken (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:4887:19)\n at Parser.nextToken (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:4877:19)\n at Parser.next (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:4802:10)\n at Parser.pp.parseIdentifier (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:1234:8)\n at Parser.pp.parseExprAtom (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:645:21)\n at Parser.pp.parseExprSubscripts (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:504:19)\n at Parser.pp.parseMaybeUnary (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:484:19)\n at Parser.pp.parseExprOps (/usr/local/lib/node_modules/babel-core/node_modules/babylon/index.js:415:19)\nmake: *** [test] Error 1\nmake: Leaving directory `/usr/src/app'\nRun failed! Check and fix errors above!\n```","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6501,"body":"Ну, тут есть несколько моментов. Во-первых, вы не экспортируете ваши функции, то есть даже если они написаны правильно, тесты их банально не увидят. Второе, что это `const point = makePoint(x, y);`? Зачем это? И последнее, по поводу ошибки тестов, у вас неверный символ стоит, как тесты и говорят. Сотрите и то что у вас на месте минуса и поставьте туда собственно минусы. На маке это клавиша справа он нуля. И все должно стать хорошо:)","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6503,"body":"Это уже шаг вперед. Теперь давайте учиться читать тесты. Что мы здесь видим?\n`expected undefined to equal '(-10, -10)'`\nСудя по этой строке, тест ожидает `(-10, -10)` но получает `undefined`. Загляните в файл теста и посмотрите, тест какой функции ожидает `(-10, -10)`. Более того, еще одна строка вывода может подсказать вам, куда смотреть: \n`at Object.<anonymous> (test.js:18:8)`. Попробуйте найти источник проблемы и напишите сюда, в какой функции по-вашему ошибка?","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6499,"body":"Встроенная `Math.sqrt`. Замените, и если решение не пройдет проверку, скопируйте сюда вывод тестов.","topic_id":3887},{"creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"id":6498,"body":"ну я думаю, что она встроенная(квадратный корень). Разве нет? мне что её самому писать?","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6495,"body":"Какой вывод тестов?","topic_id":3887},{"creator":{"public_name":"Alexander Suleymanov","id":92508,"is_tutor":false},"id":6504,"body":"ссылается на эту строчку в тестах:\n`assert.equal(toString(symmetricalPoint(makePoint(10, 10))), toString(makePoint(-10, -10)));`\n\nошибка в функции:\n```\nexport const symmetricalPoint = (point) => {\n makePoint(-getX(point), -getY(point));\n};\n\n```\nи увидел, что нет ```\nreturn\n```!\n","topic_id":3887},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6497,"body":"А что у вас за функция `sqrt`? Откуда она?","topic_id":3887}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":25548,"title":"Здравствуйте. Скажите, пожалуйста, почему не проходит проверку на null? В чем может быть проблема? \nСпасибо. \n\n```\nexport const getQuadrant = (point) => {\n\n if (getX(point) === 0 && getY(point) === 0) {\n return null;\n }\n if (getX(point) > 0 && getY(point) > 0) {\n return 1;\n }\n if (getX(point) < 0 && getY(point) > 0) {\n return 2;\n }\n if (getX(point) < 0 && getY(point) < 0) {\n return 3;\n }\n if (getX(point) > 0 && getY(point) < 0) {\n return 4;\n }\n}\n\n\n\n```","plain_title":"Здравствуйте. Скажите, пожалуйста, почему не проходит проверку на null? В чем может быть проблема? Спасибо. export const getQuadrant = (point) => { if (getX(point) === 0 && getY(point) === 0) { return null; } if (getX(point) > 0 && getY(point) > 0) { return 1; } if (getX(point) < 0 && getY(point) > 0) { return 2; } if (getX(point) < 0 && getY(point) < 0) { return 3; } if (getX(point) > 0 && getY(point) < 0) { return 4; } } ","creator":{"public_name":"Roman Serikov","id":142522,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikita Mikhaylov","id":186965,"is_tutor":true},"id":54651,"body":"Если точка не принадлежит ни одному квадранту (т.е., если она лежит **хотя бы на одной из осей** координат), то функция должна возвращать null","topic_id":25548}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":5531,"title":"Для чего вводится понятие \"квадранта\"?","plain_title":"Для чего вводится понятие \"квадранта\"? ","creator":{"public_name":"Andrew Titov","id":9330,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":9943,"body":"В практическом задании строится [библиотека](https://ru.wikipedia.org/wiki/%D0%91%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA%D0%B0_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)) по работе с графическими примитивами, в данном случае - с точками. Квадрант имеет прямое отношение к точке, т.к. характеризует её расположение на прямоугольной системе координат. Именно поэтому создание функции вычисления квадранта точки включено в задание.","topic_id":5531},{"creator":{"public_name":"Andrew Titov","id":9330,"is_tutor":false},"id":9941,"body":"Там нет ответа на то, зачем вводится понятие \"квадранта\" - присутствует только определение.\n\nИнтернетом я пользоваться умею и поиском тоже.\n\nЯ имел ввиду данную абстракцию в рамках курса и задачи.","topic_id":5531},{"creator":{"public_name":"Василий Колесников","id":81,"is_tutor":false},"id":9926,"body":"[Квадрант](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D1%8F%D0%BC%D0%BE%D1%83%D0%B3%D0%BE%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D0%BA%D0%BE%D0%BE%D1%80%D0%B4%D0%B8%D0%BD%D0%B0%D1%82#.D0.9A.D0.B2.D0.B0.D0.B4.D1.80.D0.B0.D0.BD.D1.82_.D0.BF.D0.BB.D0.BE.D1.81.D0.BA.D0.BE.D1.81.D1.82.D0.B8)","topic_id":5531}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":8771,"title":"В тестах такого пункта нет, но как будет работать функция symmetricalPoint, если одна из координат 0?","plain_title":"В тестах такого пункта нет, но как будет работать функция symmetricalPoint, если одна из координат 0? ","creator":{"public_name":"","id":134765,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":17408,"body":"0 ничем не отличается от любого другого числа","topic_id":8771},{"creator":{"public_name":"","id":299,"is_tutor":false},"id":17400,"body":"Так же?","topic_id":8771}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":3528,"title":"Объясните пожалуйста как разобраться в консоли. зачастую когда решаю задачу и нажимаю - проверить, выскакивает отчет в котором нифига не понятно что не так в задаче. Проходится менять все наугад. Вот пример отчета, как понять что не так в задаче?\nmake: Entering directory `/usr/src/app'\nbabel-node --presets=es2015 test.js\n/usr/local/lib/node_modules/hexlet-points/node_modules/hexlet-pairs/dist/index.js:23\n return pair('cdr');\n ^\n\nTypeError: pair is not a function\n at Object.cdr (/usr/local/lib/node_modules/hexlet-points/node_modules/hexlet-pairs/dist/index.js:23:10)\n at getY (/usr/local/lib/node_modules/hexlet-points/dist/index.js:21:16)\n at quadrant (solution.js:5:29)\n at Object. (test.js:11:14)\n at Module._compile (module.js:413:34)\n at loader (/usr/local/lib/node_modules/babel-register/lib/node.js:126:5)\n at Object.require.extensions.(anonymous function) [as .js] (/usr/local/lib/node_modules/babel-register/lib/node.js:136:7)\n at Module.load (module.js:357:32)\n at Function.Module._load (module.js:314:12)\n at Function.Module.runMain (module.js:447:10)\nmake: *** [test] Error 1\nmake: Leaving directory `/usr/src/app'","plain_title":"Объясните пожалуйста как разобраться в консоли. зачастую когда решаю задачу и нажимаю - проверить, выскакивает отчет в котором нифига не понятно что не так в задаче. Проходится менять все наугад. Вот пример отчета, как понять что не так в задаче? make: Entering directory `/usr/src/app' babel-node --presets=es2015 test.js /usr/local/lib/nodemodules/hexlet-points/nodemodules/hexlet-pairs/dist/index.js:23 return pair('cdr'); ^ TypeError: pair is not a function at Object.cdr (/usr/local/lib/nodemodules/hexlet-points/nodemodules/hexlet-pairs/dist/index.js:23:10) at getY (/usr/local/lib/nodemodules/hexlet-points/dist/index.js:21:16) at quadrant (solution.js:5:29) at Object. (test.js:11:14) at Module.compile (module.js:413:34) at loader (/usr/local/lib/nodemodules/babel-register/lib/node.js:126:5) at Object.require.extensions.(anonymous function) as .js (/usr/local/lib/node_modules/babel-register/lib/node.js:136:7) at Module.load (module.js:357:32) at Function.Module.load (module.js:314:12) at Function.Module.runMain (module.js:447:10) make: *** [test] Error 1 make: Leaving directory `/usr/src/app' ","creator":{"public_name":"Дмитрий Берилло","id":93590,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":5822,"body":"Это типичный трейс, который выдает любой язык на ошибку. Его нужно учиться читать и объяснение того как это делать было в этом уроке https://ru.hexlet.io/courses/programming-basics/lessons/errors/theory_unit\n\nСудя по тексту вы передали в cdr нечто не являющееся парой и сделали это тут `at quadrant (solution.js:5:29)`.","topic_id":3528}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":4840,"title":"Написал, запускаю на проверку, получаю:\n\nmake: Entering directory `/usr/src/app'\nbabel-node --presets=es2015 test.js\n/usr/local/lib/node_modules/babel-cli/lib/babel-node.js:50\n if (v8Flags.indexOf(arg) >= 0 || arg.indexOf(\"--trace\") === 0) {\n ^\n\nTypeError: Cannot read property 'indexOf' of undefined\n at /usr/local/lib/node_modules/babel-cli/lib/babel-node.js:50:20\n at Array.forEach (native)\n at /usr/local/lib/node_modules/babel-cli/lib/babel-node.js:26:13\n at /usr/local/lib/node_modules/babel-cli/node_modules/v8flags/index.js:115:16\n at /usr/local/lib/node_modules/babel-cli/node_modules/v8flags/index.js:70:14\n at exithandler (child_process.js:209:5)\n at ChildProcess.errorhandler (child_process.js:221:5)\n at emitOne (events.js:90:13)\n at ChildProcess.emit (events.js:182:7)\n at Process.ChildProcess._handle.onexit (internal/child_process.js:202:12)\nmake: *** [test] Error 1\nmake: Leaving directory `/usr/src/app'\nRun failed! Check and fix errors above!\n\nУ меня в общем-то винда... Не в этом ли проблема?","plain_title":"Написал, запускаю на проверку, получаю: make: Entering directory `/usr/src/app' babel-node --presets=es2015 test.js /usr/local/lib/node_modules/babel-cli/lib/babel-node.js:50 if (v8Flags.indexOf(arg) >= 0 || arg.indexOf(\"--trace\") === 0) { ^ TypeError: Cannot read property 'indexOf' of undefined at /usr/local/lib/nodemodules/babel-cli/lib/babel-node.js:50:20 at Array.forEach (native) at /usr/local/lib/nodemodules/babel-cli/lib/babel-node.js:26:13 at /usr/local/lib/nodemodules/babel-cli/nodemodules/v8flags/index.js:115:16 at /usr/local/lib/nodemodules/babel-cli/nodemodules/v8flags/index.js:70:14 at exithandler (childprocess.js:209:5) at ChildProcess.errorhandler (childprocess.js:221:5) at emitOne (events.js:90:13) at ChildProcess.emit (events.js:182:7) at Process.ChildProcess.handle.onexit (internal/childprocess.js:202:12) make: *** [test] Error 1 make: Leaving directory `/usr/src/app' Run failed! Check and fix errors above! У меня в общем-то винда... Не в этом ли проблема? ","creator":{"public_name":"Alex Wiseland","id":117079,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр-C Вагин","id":6409,"is_tutor":false},"id":8408,"body":"здравствуйте! попробуйте сбросить прогресс в упражнении, у нас были определенные проблемы с созданием контейнеров. ","topic_id":4840}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":28337,"title":"`// removed`\n\nПомогите, не проходят , тесты пишут\n \n FAIL __tests__/points.test.js\n ● Test suite failed to run\n\n SyntaxError: /usr/src/app/points.js: Unexpected token, expected \",\" (20:124)\n\nЧто не так?\n","plain_title":"// removed Помогите, не проходят , тесты пишут FAIL __tests__/points.test.js ● Test suite failed to run SyntaxError: /usr/src/app/points.js: Unexpected token, expected \",\" (20:124) Что не так? ","creator":{"public_name":"Вера Ковалева","id":211806,"is_tutor":false},"comments":[{"creator":{"public_name":"Вера Ковалева","id":211806,"is_tutor":false},"id":61010,"body":"убрала const point))) и добавила экспорты))) Решено)\n","topic_id":28337}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":35943,"title":"Почему у меня не определяется функция sqrt: https://ru.hexlet.io/code_reviews/188063\nЧто не так с моей функцией?","plain_title":"Почему у меня не определяется функция sqrt: https://ru.hexlet.io/code_reviews/188063 Что не так с моей функцией? ","creator":{"public_name":"Алексей Ковальковский","id":123367,"is_tutor":false},"comments":[{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":78514,"body":"**Алексей Ковальковский**, Здравствуйте.\nВ задании сказано:\n> Реализуйте и экспортируйте следующие функции для работы с точками:\n\nВы может и реализовали функции, но не экспортировали.","topic_id":35943},{"creator":{"public_name":"Алексей Ковальковский","id":123367,"is_tutor":false},"id":78515,"body":"**Dmitriy Bataev**, Первой функции добавил export default, а другим просто export. Не помогло. \nТа же ошибка: https://ru.hexlet.io/code_reviews/188063","topic_id":35943},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":78625,"body":"**Алексей**, приветствую!\n\nУ вас получилось разобраться и справиться с упражнением?","topic_id":35943},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":78526,"body":"Ваши функции не вовзвращают значения.\n\n> Первой функции добавил export default\n\nЗачем? У вас в задании сказано, что нужно экспортировать функции `getQuadrant` `getSymmetricalPoint` и `calculateDistance`.","topic_id":35943},{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":78527,"body":"Чтобы решить задачу, вам нужно четко выполнять описанное в задании и изучать тесты. И исходя из этих требований смотреть почему не работает то или иное решение.","topic_id":35943}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":3261,"title":"Привет, вроде неправильно показаны квадранты на плоскости урок 2, зад 1","plain_title":"Привет, вроде неправильно показаны квадранты на плоскости урок 2, зад 1 ","creator":{"public_name":"","id":97417,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр Кочемасов","id":112,"is_tutor":false},"id":42667,"body":"Возведение в степень 2","topic_id":3261},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":5311,"body":"Спасибо! Поправил.","topic_id":3261},{"creator":{"public_name":"","id":186370,"is_tutor":false},"id":42666,"body":"Подскажите, что значит эта формула: **d = sqrt((x2−x1)^2+(y2−y1)^2)** ??????\nвообще, что значит **^2** ????\n","topic_id":3261},{"creator":{"public_name":"","id":3961,"is_tutor":false},"id":5562,"body":"Несущественно - делаем согласно установке :)","topic_id":3261},{"creator":{"public_name":"Александр Кочемасов","id":112,"is_tutor":false},"id":5306,"body":"http://prntscr.com/azwm2m","topic_id":3261}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":443,"slug":"js_compound_data_points_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## points.js\n\nРеализуйте и экспортируйте следующие функции для работы с точками:\n\n* `getQuadrant()` — функция, которая вычисляет квадрант, в котором находится точка.\nНиже приведена схема, показывающая номера квадрантов на плоскости.\n\n ```\n +\n 2 | 1\n |\n +----------------+\n |\n 3 | 4\n +\n ```\n\n ```javascript\n const point = makePoint(1, 5)\n getQuadrant(point) // 1\n getQuadrant(makePoint(3, -3)) // 4\n ```\n\n Если точка не принадлежит ни одному квадранту (т.е., если она лежит хотя бы на одной из осей координат), то функция должна возвращать `null`:\n\n ```javascript\n const point = makePoint(0, 7)\n getQuadrant(point) // null\n getQuadrant(makePoint(2, 0)) // null\n ```\n\n* `getSymmetricalPoint()` — функция, возвращающая новую точку, симметричную относительно начала координат.\nТакая симметричность означает, что меняются знаки у `x` и `y`.\n\n ```javascript\n getSymmetricalPoint(makePoint(1, 5)) // makePoint(-1, -5)\n ```\n\n* `calculateDistance()` — функция, вычисляющая расстояние между точками по формуле: `d = sqrt((x2−x1)^2+(y2−y1)^2)`\n\n ```javascript\n calculateDistance(makePoint(-2, -3), makePoint(-4, 4)) // ≈ 7.28\n ```\n","prepared_readme":"## points.js\n\nРеализуйте и экспортируйте следующие функции для работы с точками:\n\n* `getQuadrant()` — функция, которая вычисляет квадрант, в котором находится точка.\nНиже приведена схема, показывающая номера квадрантов на плоскости.\n\n ```\n +\n 2 | 1\n |\n +----------------+\n |\n 3 | 4\n +\n ```\n\n ```javascript\n const point = makePoint(1, 5)\n getQuadrant(point) // 1\n getQuadrant(makePoint(3, -3)) // 4\n ```\n\n Если точка не принадлежит ни одному квадранту (т.е., если она лежит хотя бы на одной из осей координат), то функция должна возвращать `null`:\n\n ```javascript\n const point = makePoint(0, 7)\n getQuadrant(point) // null\n getQuadrant(makePoint(2, 0)) // null\n ```\n\n* `getSymmetricalPoint()` — функция, возвращающая новую точку, симметричную относительно начала координат.\nТакая симметричность означает, что меняются знаки у `x` и `y`.\n\n ```javascript\n getSymmetricalPoint(makePoint(1, 5)) // makePoint(-1, -5)\n ```\n\n* `calculateDistance()` — функция, вычисляющая расстояние между точками по формуле: `d = sqrt((x2−x1)^2+(y2−y1)^2)`\n\n ```javascript\n calculateDistance(makePoint(-2, -3), makePoint(-4, 4)) // ≈ 7.28\n ```\n","has_solution":true,"entity_name":"Точки"},"units":[{"id":1320,"name":"theory","url":"/courses/js-compound-data/lessons/points/theory_unit"},{"id":1322,"name":"quiz","url":"/courses/js-compound-data/lessons/points/quiz_unit"},{"id":1321,"name":"exercise","url":"/courses/js-compound-data/lessons/points/exercise_unit"}],"links":[{"id":424914,"name":"Система координат","url":"https://ru.wikipedia.org/wiki/Система_координат"}],"ordered_units":[{"id":1320,"name":"theory","url":"/courses/js-compound-data/lessons/points/theory_unit"},{"id":1322,"name":"quiz","url":"/courses/js-compound-data/lessons/points/quiz_unit"},{"id":1321,"name":"exercise","url":"/courses/js-compound-data/lessons/points/exercise_unit"}],"id":675,"slug":"points","state":"approved","name":"Точки","course_order":20,"goal":"Начинаем проектирование примитивной графической библиотеки с создания специальных данных, которые будут моделировать точки на плоскости","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"165147033","theory":"## Песочница\n\n```javascript\nimport { makePoint, getX, getY, toString, quadrant } from '@hexlet/points'\n\nconst point = makePoint(1, -1)\nconsole.log(getX(point)) // => 1\nconsole.log(getY(point)) // => -1\n\n// don't do it\nconsole.log(point) // => { [Function] pair: true }\n\nconsole.log(toString(point)) // => (1, -1)\nconsole.log(quadrant(point)) // => 4\n```\n\n```javascript\nconst point = makePoint(4, 5)\n```\n\nЕсли вас мучает вопрос, что находится внутри константы `point`, скажу сразу — мы ответим на него в следующих уроках. Для работы с точками\nне нужно знать их устройство. Когда мы решаем геометрические\nзадачи, то легко оперируем понятием отрезка, круга и других фигур. С помощью формул мы можем находить\nплощадь, рассчитывать расстояние между точками, проводить касательные и делать многое другое.\n\nКаждое из перечисленных выше понятий абстрактно, не имеет прямого воплощения в реальной жизни и\nможет применяться как к описанию детских игрушек (круглый мяч), так и египетских пирамид. Причем\nв каждом конкретном случае нас будут интересовать разные детали. В одних случаях важны только\nразмеры, в других — расположение относительно некоей системы координат, в третьих — цвет\nфигуры.\n\nВ некоторых задачах при работе с точками могут отсутствовать координаты, но имеются цвет или скорость\nмерцания. Тогда набор функций для работы с подобными точками будет кардинально отличаться от нашего набора.\n\nТочки, которые мы рассматриваем в этом курсе, интересны для нас только как место на координатной\nплоскости, задаваемое парой чисел — координат. Важно, что любая пара чисел однозначно определяет\nточку на этой плоскости, и существование двух разных точек с одинаковыми координатами невозможно.\n\nТочки — это всего лишь один из примеров того, как мы пытаемся выразить в коде\nнашу предметную область, в данном случае — геометрию. Подобная абстракция\nиспользуется в исходном коде графических редакторов. В финансовых приложениях\nиспользуется понятие \"деньги\". Деньги можно выразить просто числом, но\nэтого наверняка будет недостаточно, потому что помимо численного значения, для выражения денежной суммы необходимо как минимум указать еще и валюту.\n\nМожно сказать, что в коде выше создан свой собственный, пользовательский тип данных.\nВ отличие от примитивных типов данных, таких как числа и строки, наш новый тип является _составным_ —\nотсюда и понятие **составные данные**. Внутри себя он хранит набор значений,\nно мы оперируем ими как единым целым.\n\nРеальные прикладные приложения, по большей части, оперируют именно такими данными. Например,\nв Хекслете это уроки, курсы, практики, пользователи, наставники, топики в сообществе, счета и сотни других\nпонятий. Все они гораздо более сложные, чем те же точки. В одном пользователе могут находиться десятки\nпараметров.\n\nНевозможно заранее определить типы данных для представления\nвсего сущего на Земле. Все, что требуется от языка — предоставить возможность определять свои\nсобственные типы и операции над ними. И об этом мы поговорим в следующем уроке.\n\n---\n\n## Конспект урока\n\nВ этом уроке мы начнем проектирование библиотек для работы с графическими примитивами (точками, отрезками), а также фигурами (кругами, прямоугольниками). Естественно, проще всего начать с основы — библиотеки для работы с точками. Для этого давайте вспомним координатную плоскость и то, как определяются на ней точки.\n\n\n\nВ этом примере мы видим плоскость, на которой расположены две оси (ось абсцисс X и ось ординат Y) и точки. При этом обычно точка описывается двумя параметрами: первым указывается координата x (абсцисса), вторым — координата y (ордината). Например, точка D описывается как (2, 4), точка C — (1, 0) и так далее. Координаты могут быть отрицательными. Оси делят плоскость на четыре части, которые называются квадрантами. На рисунке точка D лежит в первом квадранте, точки A, G, E — во втором, в третьем и в четвертом квадрантах соответственно.\n\nКогда мы говорим о точках, мы на самом деле имеем в виду так называемые __абстрактные данные__. Это означает, что мы не делаем никаких предположений (кроме самых необходимых) для того, чтобы рассуждать в терминах этих данных (в нашем случае — точек). И действительно, вспомним, например, любой раздел школьной математики: когда мы оперируем какими-то понятиями, мы вообще не думаем о том, что они должны быть как-то представлены, мы мыслим в них на абстрактном уровне. Это позволяет нам строить законы, делать выводы и предположения о том, как будут вести себя данные, как они друг с другом комбинируются. При этом нам не нужна никакая реализация, нам не нужен ни язык программирования, ни компьютер. Более того, нам вообще необязательно графически изображать эти данные — мы можем представлять это в голове и при этом делать выводы, имеющие практическое значение. При этом конкретное представление абстрактных данных (так называемые __конкретные данные__) определяется условиями прикладного использования, например, особенностями разных языков программирования. Зачастую библиотеки, которые снаружи выглядят одинаково и оперируют одними и теми же понятиями, внутри устроены совершенно по-разному. Естественно, при манипуляциях с такими библиотеками необходимо оставаться в рамках понятийного аппарата тех абстрактных данных, с которыми мы работаем. \"Клеем\" между абстрактными данными и конкретными данными выступает так называемый интерфейс. Интерфейс — это набор функций, обычно разделяемых на конструкторы и селекторы. Конструкторы позволяют из набора данных строить составной объект, а с помощью селекторов из составного объекта извлекают его части. В нашем случае, если говорить о точках, селекторы позволяют извлекать координаты x и y. Вот как выглядит интерфейс нашей библиотеки:\n\n```javascript\nconst x = 5\nconst y = -7\n\n// Конструктор\nconst point = makePoint(x, y)\n\n// Селекторы\ngetX(point)\ngetY(point)\n\ntoString(point) // (5, -7)\n```\n\nВ коде выше видно, что мы определили функцию `makePoint`, которая принимает на вход два числа (`x` и `y`) и возвращает точку с такими координатами. Кроме этого, есть два основных селектора `getX` и `getY`, которые возвращают соответствующие координаты из точки.\nПри проектировании библиотеки мы используем стратегию \"мечтать не вредно\", потому что еще не знаем, как она будет устроена внутри. Это, по большому счету, не имеет значения, потому что мы пользуемся абстрактными данными и оперируем терминами нашей предметной области, а не конкретной реализации. Поэтому мы можем сосредоточиться на сути, отложив принятие решения о деталях.\n\nКроме основных селекторов, нами определена функция `toString`, которая возвращает текстовое представление точки и может быть использована для отладки.\n\nДавайте посмотрим, какие манипуляции можно производить над точками, имея базовый интерфейс:\n\n```javascript\nconst symmetricalXPoint = point =>\n makePoint(getX(point), -getY(point))\n\nconst point = makePoint(10, 10)\n\nsymmetricalXPoint(point) // (10, -10)\n```\n\nВ примере выше определена функция `symmetricalXPoint`, которая возвращает точку, симметричную заданной относительно оси X. Например, для точки c координатами (10, 10) функция вернет точку с координатами (10, -10). Функция устроена следующим образом: с помощью селекторов мы извлекаем координаты, инвертируем координату Y и создаем новую точку с помощью конструктора `makePoint`.\nБольшинство операций с точками состоят в том, чтобы извлечь координаты, произвести с ними необходимые изменения и создать новые точки. С помощью этого нехитрого алгоритма можно реализовать массу других функций, например:\n\n```javascript\nconst point = makePoint(3, 4)\nconst point2 = makePoint(0, 0)\n\nquadrant(point) // 1\ndistance(point, point2) // 5\n```\n\nФункция `quadrant` определяет квадрант, в котором расположена точка, а функция `distance` вычисляет расстояние между заданными точками. При этом мы оперируем понятием \"точки\", а обращение к составным частям этих точек происходит внутри и скрыто от наших глаз.\n\nПодведем итог:\n\nАбстрактными данными у нас является точка, которая характеризуется двумя значениями (координатами) `x` и `y`.\n\nКонкретные данные: на текущий момент мы не знаем, как реализованы точки (что не мешает нам пользоваться библиотекой в рамках нашей предметной области).\n\nИнтерфейс:\n\n```javascript\n// Конструктор\nmakePoint(<x>, <y>);\n\n// Селекторы\ngetX(<point>);\ngetY(<point>);\n```\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1318,"name":"theory","url":"/courses/js-compound-data/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":1318,"name":"theory","url":"/courses/js-compound-data/lessons/intro/theory_unit"}],"id":674,"slug":"intro","state":"approved","name":"Введение","course_order":10,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"165146990","theory":"<figure>\n <blockquote class=\"blockquote\">\n <p>Абстракция — один из главных способов борьбы со сложностью реального мира.</p>\n </blockquote>\n <figcaption class=\"blockquote-footer\">\n Стив Макконнелл - <cite title=\"Source Title\">Совершенный код</cite>\n </figcaption>\n</figure>\n\nДо сих пор мы работали только с примитивными типами данных, такими как строки и числа. В этом курсе произойдет переход на новый уровень, и большая часть работы сосредоточится вокруг **составных данных**.\n\nВы могли подумать, что на этот раз мы будем осваивать массивы. И если бы мы не были Хекслетом, то все было бы именно так. Но у нас немного другие планы на ближайшие курсы. Есть вещи важнее массивов, и с них мы и начнем. К массивам же обратимся позже.\n\nНачиная с этого курса, мы начнем погружаться в **парадигму декларативного программирования**, без которой в JavaScript никуда. К сожалению, массивы при стандартном использовании плохо сочетаются с этими концепциями, а также позволяют срезать углы там, где не надо. Это сводит на нет весь эффект от обучения. Поэтому основой для составных данных станет так называемая **пара** — структура данных, некогда популярная во многих языках программирования. В отличие от массивов, ее нельзя изменять. В любой ситуации потребуется создать новую пару на основе предыдущей, и обойти этот механизм невозможно. Подобное ограничение оставляет только один способ работы — функциональный.\n\n*Как вы увидите позже, точно так же можно и зачастую нужно работать с массивами. К тому моменту, когда мы начнем их использовать, вы уже будете готовы к такому способу работы.*\n\nОдна из самых важных тем в программировании — **абстракция**. Чем больше кодовая база, тем больше абстракций используется, либо создается в ней. Значительная часть времени разработчика тратится на моделирование предметной области и реализации ее в коде, а также в ее дальнейшей поддержке и развитии. Как правило, этому вопросу совсем не уделяют времени, но именно от умения моделировать зависит качество вашего кода, насколько просто будет работать с ним, понимать и модифицировать его.\n\nПредставьте, что вам необходимо автоматизировать работу отдела продаж (создать CRM). С чего вы начнете? А начать стоит с [онтологии](https://ru.wikipedia.org/wiki/%D0%9E%D0%BD%D1%82%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%8F_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)).\n\nВ следующих курсах вы увидите, как, благодаря некоторым особым свойствам, пара может стать основой для создания более сложных структур данных, таких как списки, множества и даже деревья. Другими словами, мы заочно познакомимся с разными структурами данных и поработаем с ними.\n\nНе забывайте, что эти курсы направлены на развитие ваших навыков кодирования, и перед тем, как мы познакомимся с платформами (фреймворками, библиотеками), нам нужно разобраться с самим JavaScript.\n\n---\n\n## Конспект урока\n\nПо названию этого курса можно подумать, что мы будем говорить о таких типах данных в JavaScript, как массив или объект. Это совсем не так. В этом курсе мы используем JavaScript как язык, на котором пишем код, но сам курс относится в целом к программированию. Мы изучим такое понятие, как абстракция данных. Это общая концепция никак не связана конкретно с JavaScript, поскольку это крайне важный механизм абсолютно во всех языках программирования. Более того, в отличие от курса [Основы программирования](https://ru.hexlet.io/courses/programming-basics) мы вообще не введем ни одной новой синтаксической конструкции. В этом и есть наше отличие. Мы учим вас мыслить, писать код и программировать, что сильно отличается от того, что существует сейчас на рынке.\n\nИтак, давайте начнем.\n\n## Что такое составные данные?\n\n**Составные данные** используются в следующих случаях. Когда мы пишем программы,\nто чаще всего пытаемся моделировать достаточно сложные процессы и явления,\nпротекающие часто в реальной жизни. Для этого мы можем использовать составные\nвычислительные объекты, которые включают в себя несколько различных частей.\nЭто позволяет лучше моделировать все явления реальной среды, того, с чем мы\nвзаимодействуем. И язык программирования должен предоставлять возможность создавать механизмы для создания так называемых составных данных, т.е. из разных кусков собирать отдельный вычислительный объект.\n\n## Зачем это нужно?\n\nПри работе со сложными вычислительными объектами благодаря составным данным,\nкак я уже говорил, мы можем поднимать так называемый понятийный уровень. Т.е. работать на более высоком уровне абстракции и создавать нашу программу, и производить вычисления в терминах наших вычислительных объектов. Давайте рассмотрим это на конкретном примере, который известен всем со школы — это точки на плоскости.\n\nУ нас есть координатная плоскость, и мы можем строить на ней точки. При этом любая точка обладает двумя характеристиками — это ее координаты `x` и `y`, т.е. абсцисса и ордината. В данном примере я использую то, что уже известно — простые числовые значения, и описываю две точки. Одна из них `x1/y1`, вторая `x2/y2`. После этого, вычисляю точку посередине.\n\n```javascript\nconst x1 = 3\nconst y1 = 5\n\nconst x2 = -2\nconst y2 = 10\n\n// Точка посередине\nconst x3 = middleX(x1, x2)\nconst y3 = middleY(y1, y2)\n```\n\nМы считаем, что мы строим отрезок между двумя этими точками и находим точку, которая лежит посередине. То, как это работает, не имеет значения. Главное, что вы можете себе это представить. Мы оперируем понятием *точка*. При этом понимаем, что она состоит из двух простых типов, т.е. двух чисел. По сути появляется такая вещь, как пара. И, в общем-то, нам удобно оперировать такими терминами.\n\nНо при написании кода, если у нас не существует понятия составные данные, то, как видите, нам приходится производить вычисления независимо. Т.е. сначала вычислить `х3`, после этого нам надо вычислить `y3`. Это происходит потому, что мы не оперируем понятием точка. У нас нет такого составного вычислительного объекта. И все, что мы можем в наших функциях, это принимать простые параметры и возвращать точно такие же простые параметры. Получается, что одно действие технически разбивается на два. Согласитесь, это крайне неудобно. Особенно, когда ваша программа становится достаточно большой и начинает оперировать большим количеством сложных объектов.\n\nТочки, кстати говоря — это еще достаточно простые объекты, которые включают в себя буквально 2 параметра. Представьте, что будет, когда мы начнем использовать более сложные объекты: хотя бы про фигуры, например, квадрат. Квадрат включает в себя 4 точки — это уже 8 параметров, потому что в каждой точке 2 параметра. Работать на таком уровне абстракции, используя только примитивные типы и примитивные данные, мы просто бы не смогли. Наше мышление очень сильно бы этому сопротивлялось. Код получался бы очень громоздкий и неинтуитивный.\n\nВ идеале нам бы хотелось работать так, как показано в этой строчке:\n\n```javascript\n// Compound Data\nconst middlePoint = middle(point1, point2)\n```\n\nМы отдаем в функцию две точки (`point1` и `point2`), и нам возвращается точка посередине. Как она устроена и что там внутри — это отдельный вопрос. Главное, что мы оперируем этим понятием, как единым целым. Возможность строить составные объекты данных позволяет нам использовать технику, которая называется абстракция данных.\n\n**Абстракция данных** — это метод отделения частей программы, которые имеют дело\nс представлением объектов данных, от тех частей, где эти объекты используются.\n\nПри использовании точки в предыдущем примере это обозначает, что нас не очень волнует, как внутри устроена точка. Мы можем просто оперировать только этим понятием. Соответственно, определять то, как непосредственно внутри она выглядит, мы можем совершенно в другом месте. Это делает код модульным и дает огромные возможности по его дальнейшей модификации и поддержке. Например, мы можем изменить представление данных на более эффективное, более удобное в данный момент и не переписывать всю программу. Нужно будет переписать только ту часть, где именно происходит определение того, как данные структурированы внутри.\n\nАбстракция данных приводит нас к такому понятию, как **барьеры абстракции**, когда мы можем строить многоуровневые слои, позволяющие изолировать разные части и разные уровни системы друг от друга. Об этом отдельный будет урок, где мы подробно поговорим, как работает абстракция данных, и как строятся барьеры абстракции.\n\nКроме этого в любом языке программирования нужен некий клей, который позволит из простых данных строить более сложные. И как мы увидим в дальнейшем, для этого даже не нужны специальные операции. Строить составные объекты можно используя только возможности функций, что еще больше стирает разницу между функциями и данными. Это можно было уже заметить, потому что функции определяются точно так же, как данные. Функции являются полноправными данными, так называемыми объектами первого рода. Этому будет посвящен целый отдельный урок.\n\n## Графические примитивы\n\nНа протяжении всего курса мы будем строить графические примитивы и небольшую библиотеку для работы с примитивами на плоскости. Начнем с точек, поработаем с кругами, научимся делать различные отрезки и фигуры.\n\n## Рациональные числа\n\nКроме этого попробуем создать простую библиотеку для работы с рациональными числами. Это числа, у которых есть числитель и знаменатель. Определение, конечно, не строго математическое, но вы на интуитивном уровне все с ними знакомы. Рациональное число — достаточно простой, но составной объект, который включает в себя пару чисел.\n\n## Преимущества Hexlet\n\n- Осмысление\n- СИКП\n- Не используем существующие структуры данных\n- Функции высшего порядка\n- Неизменяемость (Функциональный стиль)\n\nПо опыту предыдущих курсов очень часто у людей возникают вопросы: \"А почему именно так?\", \"Как мне это поможет в практике?\" и \"Я хочу быстрее начать писать продакшн-код во фреймворках\". Да, мы не просто учим синтаксису. Мы хотим изменить ваше мышление, хотим сделать его правильным, чтобы у вас развилось критическое мышление, чтобы вы понимали причинно-следственные связи и, в первую очередь, мыслили не синтаксисом языка, а смыслом того, что вы делаете с точки зрения построения абстракций. Это позволит писать вам модульные программы, которые легко читать, поддерживать, развивать, и они будут выполнять то, что нам нужно. Это наша основная идея, то, что хочет дать и несет Хекслет. Возможно, вы уже заметили это по каким-то вещам, которые мы рассказываем или делаем.\n\nНаш курс во многом построены на так называемом СИКПе — курсе, который велся и ведется до сих пор в большом количестве университетов мира. Расшифровывается, как структура и интерпретация компьютерных программ. Курс был придуман в Массачусетском технологическом институте (MIT). Это университет номер один в мире IT-технологий, который выпускал и выпускает лучших специалистов в этой области. СИКП — достаточно большой, сложный, но крайне важный курс. По нему выпущена книга, которая уже десятки лет считается книгой номер один среди обучающей литературы по программированию. В любом случае, всем ее рекомендуем. При этом здесь мы ее активно используем, потому что она дала нам много материала и пищи для размышлений. Книга была выпущена в 1985 году, а сам курс велся еще раньше (с начала 80-х). Но, как вы увидите в дальнейшем, несмотря на то, что меняются технологии, меняются фреймворки, меняется все вокруг (JavaScript меняется особенно сильно), базовые вещи, так же как во многом и математика, в общем-то не меняется и не поменяется. Не гонитесь за новым модным, изучайте то, на чем все это базируется.\n\nКурс не использует существующие структуры данных. Я сказал об этом в самом начале. Здесь не будет ничего про массивы, объекты или какие-то другие возможные способы комбинирования данных. Это отвлечет нас от основной идеи. От понимания сути вопроса. Наши функции в этом курсе приобретут совершенно новый оттенок и заиграют новыми красками. Мы познакомимся с функциями высшего порядка и увидим, что функции — это точно такие же данные, которые можно передавать в другие функции, возвращать из функций и делать с ними совершенные чудеса.\n\nВ курсе мы будем работать с неизменямыми данными, использовать функциональный стиль программирования. Этот достаточно важный аспект позволяет нам сосредоточиться только на самом вопросе, который разбирается в курсе, и оставить вопрос изменения состояния и связанных с этим проблем в стороне. В Основах программирования мы уже немного говорили о том, какие проблемы привносят переменные и возможность изменять состояние. В дальнейшем будет отдельный большой курс, посвященный работе с состояниями и всеми особенностями, которые привносят возможность использовать переменные.\n"},"id":114,"slug":"js-compound-data","challenges_count":3,"name":"JS: Составные данные","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите идею составных данных. Вы узнаете больше о концепциях создания сложных типов данных из простых и о парадигме декларативного программирования. В итоге научитесь создавать абстракции и изолировать разные части программы. Знания из этого курса помогают программистам моделировать необходимую предметную область, писать более читаемый и модульный код.","kind":"additional","updated_at":"2026-01-20T11:50:53.368Z","language":"javascript","duration_cache":29880,"skills":["Создавать код, который легко читать и понимать что он делает","Научиться создавать удобные абстракции и скрывать внутреннюю реализацию данных","Определять границу между слоями приложения так, чтобы поддерживать высокий уровень модульности (независимости разных частей) кода"],"keywords":["моделирование данных","барьеры абстракции","замыкание"],"lessons_count":7,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODIzNCwicHVyIjoiYmxvYl9pZCJ9fQ==--d7865c47fdac3be18bfc41cd41e2b544aa491efb/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/js-compound-data/lessons/points/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"><h2 id="heading-2-1">Песочница</h2>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>javascript</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-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-CodeHighlightTabs-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-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">import { makePoint, getX, getY, toString, quadrant } from '@hexlet/points'
const point = makePoint(1, -1)
console.log(getX(point)) // => 1
console.log(getY(point)) // => -1
// don't do it
console.log(point) // => { [Function] pair: true }
console.log(toString(point)) // => (1, -1)
console.log(quadrant(point)) // => 4</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></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">point</code>, скажу сразу — мы ответим на него в следующих уроках. Для работы с точками
не нужно знать их устройство. Когда мы решаем геометрические
задачи, то легко оперируем понятием отрезка, круга и других фигур. С помощью формул мы можем находить
площадь, рассчитывать расстояние между точками, проводить касательные и делать многое другое.</p>
<p>Каждое из перечисленных выше понятий абстрактно, не имеет прямого воплощения в реальной жизни и
может применяться как к описанию детских игрушек (круглый мяч), так и египетских пирамид. Причем
в каждом конкретном случае нас будут интересовать разные детали. В одних случаях важны только
размеры, в других — расположение относительно некоей системы координат, в третьих — цвет
фигуры.</p>
<p>В некоторых задачах при работе с точками могут отсутствовать координаты, но имеются цвет или скорость
мерцания. Тогда набор функций для работы с подобными точками будет кардинально отличаться от нашего набора.</p>
<p>Точки, которые мы рассматриваем в этом курсе, интересны для нас только как место на координатной
плоскости, задаваемое парой чисел — координат. Важно, что любая пара чисел однозначно определяет
точку на этой плоскости, и существование двух разных точек с одинаковыми координатами невозможно.</p>
<p>Точки — это всего лишь один из примеров того, как мы пытаемся выразить в коде
нашу предметную область, в данном случае — геометрию. Подобная абстракция
используется в исходном коде графических редакторов. В финансовых приложениях
используется понятие "деньги". Деньги можно выразить просто числом, но
этого наверняка будет недостаточно, потому что помимо численного значения, для выражения денежной суммы необходимо как минимум указать еще и валюту.</p>
<p>Можно сказать, что в коде выше создан свой собственный, пользовательский тип данных.
В отличие от примитивных типов данных, таких как числа и строки, наш новый тип является <em>составным</em> —
отсюда и понятие <strong>составные данные</strong>. Внутри себя он хранит набор значений,
но мы оперируем ими как единым целым.</p>
<p>Реальные прикладные приложения, по большей части, оперируют именно такими данными. Например,
в Хекслете это уроки, курсы, практики, пользователи, наставники, топики в сообществе, счета и сотни других
понятий. Все они гораздо более сложные, чем те же точки. В одном пользователе могут находиться десятки
параметров.</p>
<p>Невозможно заранее определить типы данных для представления
всего сущего на Земле. Все, что требуется от языка — предоставить возможность определять свои
собственные типы и операции над ними. И об этом мы поговорим в следующем уроке.</p>
<hr/>
<h2 id="heading-2-2">Конспект урока</h2>
<p>В этом уроке мы начнем проектирование библиотек для работы с графическими примитивами (точками, отрезками), а также фигурами (кругами, прямоугольниками). Естественно, проще всего начать с основы — библиотеки для работы с точками. Для этого давайте вспомним координатную плоскость и то, как определяются на ней точки.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6ODIzNiwicHVyIjoiYmxvYl9pZCJ9fQ==--def9244aa78a0c80a0345118442068562f4c4f1c/plane.png" alt="Прямоугольная система координат на плоскости" loading="lazy"/></p>
<p>В этом примере мы видим плоскость, на которой расположены две оси (ось абсцисс X и ось ординат Y) и точки. При этом обычно точка описывается двумя параметрами: первым указывается координата x (абсцисса), вторым — координата y (ордината). Например, точка D описывается как (2, 4), точка C — (1, 0) и так далее. Координаты могут быть отрицательными. Оси делят плоскость на четыре части, которые называются квадрантами. На рисунке точка D лежит в первом квадранте, точки A, G, E — во втором, в третьем и в четвертом квадрантах соответственно.</p>
<p>Когда мы говорим о точках, мы на самом деле имеем в виду так называемые <strong>абстрактные данные</strong>. Это означает, что мы не делаем никаких предположений (кроме самых необходимых) для того, чтобы рассуждать в терминах этих данных (в нашем случае — точек). И действительно, вспомним, например, любой раздел школьной математики: когда мы оперируем какими-то понятиями, мы вообще не думаем о том, что они должны быть как-то представлены, мы мыслим в них на абстрактном уровне. Это позволяет нам строить законы, делать выводы и предположения о том, как будут вести себя данные, как они друг с другом комбинируются. При этом нам не нужна никакая реализация, нам не нужен ни язык программирования, ни компьютер. Более того, нам вообще необязательно графически изображать эти данные — мы можем представлять это в голове и при этом делать выводы, имеющие практическое значение. При этом конкретное представление абстрактных данных (так называемые <strong>конкретные данные</strong>) определяется условиями прикладного использования, например, особенностями разных языков программирования. Зачастую библиотеки, которые снаружи выглядят одинаково и оперируют одними и теми же понятиями, внутри устроены совершенно по-разному. Естественно, при манипуляциях с такими библиотеками необходимо оставаться в рамках понятийного аппарата тех абстрактных данных, с которыми мы работаем. "Клеем" между абстрактными данными и конкретными данными выступает так называемый интерфейс. Интерфейс — это набор функций, обычно разделяемых на конструкторы и селекторы. Конструкторы позволяют из набора данных строить составной объект, а с помощью селекторов из составного объекта извлекают его части. В нашем случае, если говорить о точках, селекторы позволяют извлекать координаты x и y. Вот как выглядит интерфейс нашей библиотеки:</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 x = 5
const y = -7
// Конструктор
const point = makePoint(x, y)
// Селекторы
getX(point)
getY(point)
toString(point) // (5, -7)</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">makePoint</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">x</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">y</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">getX</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">getY</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">toString</code>, которая возвращает текстовое представление точки и может быть использована для отладки.</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">const symmetricalXPoint = point =>
makePoint(getX(point), -getY(point))
const point = makePoint(10, 10)
symmetricalXPoint(point) // (10, -10)</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">symmetricalXPoint</code>, которая возвращает точку, симметричную заданной относительно оси X. Например, для точки c координатами (10, 10) функция вернет точку с координатами (10, -10). Функция устроена следующим образом: с помощью селекторов мы извлекаем координаты, инвертируем координату Y и создаем новую точку с помощью конструктора <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">makePoint</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">const point = makePoint(3, 4)
const point2 = makePoint(0, 0)
quadrant(point) // 1
distance(point, point2) // 5</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">quadrant</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">distance</code> вычисляет расстояние между заданными точками. При этом мы оперируем понятием "точки", а обращение к составным частям этих точек происходит внутри и скрыто от наших глаз.</p>
<p>Подведем итог:</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">x</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">y</code>.</p>
<p>Конкретные данные: на текущий момент мы не знаем, как реализованы точки (что не мешает нам пользоваться библиотекой в рамках нашей предметной области).</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">// Конструктор
makePoint(<x>, <y>);
// Селекторы
getX(<point>);
getY(<point>);</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/js-compound-data/lessons/points/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 / 7</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/js-compound-data/lessons/points/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>