<!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:17:43 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="imvTOD6uy3yhOHVPmDNbFie9Lw-rHqfboR3zkS4jaaFluhgPzNBmHBd7UdeUPKth57QCpaMpWXkc_WnFfCSOzw";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/pair/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/pair/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="rIuvdgmZMr3QZJ9b3TA_FesePXHmzNesA8yEkNaIzllDWmRB--ef3WYnu8PRP89iKxcQ2-77KQ6-LB7EhI8pNw" />
<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:17:43.524Z","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":"O25rWhpDtkNjqv5D5M32V700rA_kBKkXTXQsYSq785LUv6Bt6D0bI9Xp2tvowgYgfT2BpewzV7XwlLY1eLwU_A","topics":[{"id":5733,"title":"```import { makePoint, getX, getY, toString } from 'hexlet-points';\nimport { cons, car, cdr } from 'hexlet-pairs';\n\n// BEGIN (write your solution here)\nexport const makeSegment=(point1,point2)=>{\n const segment=cons(point1,point2);\n return segment;\n};\nexport const startSegment=(segment)=>{\n return car(segment);\n};\nexport const endSegment=(segment)=>{\n return cdr(segment);\n};\nexport const toStr=(segment)=>{\n return('['+car(segment)+','+cdr(segment)+']');\n};\nexport const midpointSegment=(segment)=>{\n const x1=getX(car(segment));\n const y1=getY(car(segment));\n const x2=getX(cdr(segment));\n const y2=getY(cdr(segment));\n const x0=(x1+x2)/2;\n const y0=(y1+y2)/2;\n const midpoint=makePoint(x0,y0);\n return midpoint;\n};\n//END```\nв итоге получил вот эту ругань\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 '[function (message) {\\n switch (message) {\\n case \\'car\\':\\n return a;\\n case \\'cdr\\':\\n return b;\\n default:\\n throw new Error(\\'Unknown message \\\\\\'\\' + message + \\'\\\\\\'\\');\\n }\\n },function (message) {\\n switch (message) {\\n case \\'car\\':\\n return a;\\n case \\'cdr\\':\\n return b;\\n default:\\n throw new Error(\\'Unknown message \\\\\\'\\' + message + \\'\\\\\\'\\');\\n }\\n }]' to equal '[(1, 2), (-4, -2)]'\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'\n```\nпытался заменить `return('['+car(segment)+','+cdr(segment)+']');` на `return toString(segment);`, но тогда он выдает круглые скобки вместо квадратных. Что не так в первом случае?","plain_title":"```import { makePoint, getX, getY, toString } from 'hexlet-points'; import { cons, car, cdr } from 'hexlet-pairs'; // BEGIN (write your solution here) export const makeSegment=(point1,point2)=>{ const segment=cons(point1,point2); return segment; }; export const startSegment=(segment)=>{ return car(segment); }; export const endSegment=(segment)=>{ return cdr(segment); }; export const toStr=(segment)=>{ return('['+car(segment)+','+cdr(segment)+']'); }; export const midpointSegment=(segment)=>{ const x1=getX(car(segment)); const y1=getY(car(segment)); const x2=getX(cdr(segment)); const y2=getY(cdr(segment)); const x0=(x1+x2)/2; const y0=(y1+y2)/2; const midpoint=makePoint(x0,y0); return midpoint; }; //END в итоге получил вот эту ругань make: Entering directory `/usr/src/app' babel-node --presets=es2015 test.js /usr/local/lib/nodemodules/chai/lib/chai/assertion.js:107 throw new AssertionError(msg, { ^ AssertionError: expected '[function (message) {\\n switch (message) {\\n case \\'car\\':\\n return a;\\n case \\'cdr\\':\\n return b;\\n default:\\n throw new Error(\\'Unknown message \\\\'\\' + message + \\'\\\\'\\');\\n }\\n },function (message) {\\n switch (message) {\\n case \\'car\\':\\n return a;\\n case \\'cdr\\':\\n return b;\\n default:\\n throw new Error(\\'Unknown message \\\\'\\' + message + \\'\\\\'\\');\\n }\\n }]' to equal '[(1, 2), (-4, -2)]' at Object. (test.js:18:8) 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 at Object. (/usr/local/lib/nodemodules/babel-cli/lib/babel-node.js:162:7) at Module._compile (module.js:413:34) make: *** [test] Error 1 make: Leaving directory /usr/src/app' ` пытался заменитьreturn('['+car(segment)+','+cdr(segment)+']');наreturn toString(segment);`, но тогда он выдает круглые скобки вместо квадратных. Что не так в первом случае? ","creator":{"public_name":"tim ten","id":110585,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":10280,"body":"> пытался заменить\n\nи правильно делали... Только, во-первых, внимательно посмотрите (в документации) как работает функция `toString`; во-вторых, здесь нужны и круглые и квадратные скобки ;)","topic_id":5733}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":3439,"title":"Предлагаю попробовать как-то поменять readme, у меня ни разу не возникла мысль использовать что-то из pairs ИЛИ как вариант сделать чтобы тесты не пропускали решений без использования pairs.","plain_title":"Предлагаю попробовать как-то поменять readme, у меня ни разу не возникла мысль использовать что-то из pairs ИЛИ как вариант сделать чтобы тесты не пропускали решений без использования pairs. ","creator":{"public_name":"Павел Ерастов","id":70260,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":5665,"body":"Спасибо! Поправил ридми. А вы реализовали напрямую через замыкание?","topic_id":3439},{"creator":{"public_name":"Павел Ерастов","id":70260,"is_tutor":false},"id":5671,"body":"Видимо у меня не обновилось readme. Вот мое решение:\n```\nimport { makePoint, getX, getY } from 'hexlet-points';\nimport * as pairs from 'hexlet-pairs';\n\n// BEGIN (write your solution here)\nexport function makeSegment(point1, point2) {\n return [point1, point2];\n}\n\nexport function startSegment(segment) {\n return segment[0];\n}\n\nexport function endSegment(segment) {\n return segment[1];\n}\n\nexport function midpointSegment(segment) {\n const point1 = startSegment(segment);\n const point2 = endSegment(segment);\n const middleX = (getX(point1) + getX(point2))/2;\n const middleY = (getY(point1) + getY(point2))/2;\n return makePoint(middleX, middleY);\n}\n\nexport function toString(segment) {\n const point1 = startSegment(segment);\n const point2 = endSegment(segment);\n return '[(' + getX(point1) + ', ' + getY(point1) + '), (' + getX(point2) + ', ' + getY(point2) + ')]';\n}\n```\nКирилл если вы еще дадите посмотреть на реализацию hexlet-pairs будет очень здорово, а то я не смог найти как там оно внутри выглядит. IDE же не подсказывает функции из нее.","topic_id":3439},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":5699,"body":"Не должно быть массивов, мы их не проходили, и смысл этого курса в том чтобы самостоятельно создавать подобные структуры.\n\nРеализация пар будет позже по курсу.","topic_id":3439}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":5456,"title":"Задание простое как топор, но отсутсвие документации по импортированным методам иногда ставит в тупик. Если бы не нашел на гитхабе js-pair, то долго бы провозился.","plain_title":"Задание простое как топор, но отсутсвие документации по импортированным методам иногда ставит в тупик. Если бы не нашел на гитхабе js-pair, то долго бы провозился. ","creator":{"public_name":"","id":116182,"is_tutor":false},"comments":[{"creator":{"public_name":"","id":116182,"is_tutor":false},"id":9760,"body":"Ого, крутота. Слона то я и не заметил. Извините :)","topic_id":5456},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":9758,"body":"Документация доступна и лежит в папке `docs`.","topic_id":5456}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":19140,"title":"Здравствуйте \nЯ вот хотел у себя локально запустить пакет hexlet-pairs \nскачал его _npm install hexlet-pairs_. \nИ написал код из урока\n```\nimport { cons, car, cdr } from 'hexlet-pairs';\n\nconst makePoint = (x, y) => cons(x, y);\n\nconst getX = (point) => car(point);\nconst getY = (cdr) => cdr(point);\n\n```\nНо при запуске интерпретатор выдает синтаксическую ошибку\n\n```\n/home/saloev/diskE/myOwnlessons/hexlet/jsCompoundData/lessons3Pairs/pairs.js:1\n(function (exports, require, module, __filename, __dirname) { import { cons, car, cdr } from 'hexlet-pairs';\n\nSyntaxError: Unexpected token {\n at new Script (vm.js:79:7)\n at createScript (vm.js:251:10)\n at Object.runInThisContext (vm.js:303:10)\n at Module._compile (internal/modules/cjs/loader.js:657:28)\n at Module._compile (/home/saloev/diskE/myOwnlessons/hexlet/jsCompoundData/lessons3Pairs/node_modules/pirates/lib/index.js:83:24)\n at Module._extensions..js (internal/modules/cjs/loader.js:700:10)\n at Object.newLoader [as .js] (/home/saloev/diskE/myOwnlessons/hexlet/jsCompoundData/lessons3Pairs/node_modules/pirates/lib/index.js:88:7)\n at Module.load (internal/modules/cjs/loader.js:599:32)\n at tryModuleLoad (internal/modules/cjs/loader.js:538:12)\n at Function.Module._load (internal/modules/cjs/loader.js:530:3)\n```\nЯ подумал может у nodejs другой синтаксис импорта модулей и поэтому скачал и установил babel добавил в .babelrc\n```\n{\n \"presets\": [\n [\"@babel/env\", {\n \"targets\": {\n \"node\": \"current\"\n }\n }]\n ]\n}\n```\nи сделал транспиляцию кода , но результат транспиляции был идентичным.\nИ соответственно запуска script-compiled.js выдает туже ошибку.\n\nВ чем может быть проблема ??\nСПАСИБО\n","plain_title":"Здравствуйте Я вот хотел у себя локально запустить пакет hexlet-pairs скачал его npm install hexlet-pairs. И написал код из урока ``` import { cons, car, cdr } from 'hexlet-pairs'; const makePoint = (x, y) => cons(x, y); const getX = (point) => car(point); const getY = (cdr) => cdr(point); Но при запуске интерпретатор выдает синтаксическую ошибку /home/saloev/diskE/myOwnlessons/hexlet/jsCompoundData/lessons3Pairs/pairs.js:1 (function (exports, require, module, _filename, _dirname) { import { cons, car, cdr } from 'hexlet-pairs'; SyntaxError: Unexpected token { at new Script (vm.js:79:7) at createScript (vm.js:251:10) at Object.runInThisContext (vm.js:303:10) at Module.compile (internal/modules/cjs/loader.js:657:28) at Module.compile (/home/saloev/diskE/myOwnlessons/hexlet/jsCompoundData/lessons3Pairs/nodemodules/pirates/lib/index.js:83:24) at Module.extensions..js (internal/modules/cjs/loader.js:700:10) at Object.newLoader as .js (/home/saloev/diskE/myOwnlessons/hexlet/jsCompoundData/lessons3Pairs/node_modules/pirates/lib/index.js:88:7) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) Я подумал может у nodejs другой синтаксис импорта модулей и поэтому скачал и установил babel добавил в .babelrc { \"presets\": [ [\"@babel/env\", { \"targets\": { \"node\": \"current\" } }] ] } ``` и сделал транспиляцию кода , но результат транспиляции был идентичным. И соответственно запуска script-compiled.js выдает туже ошибку. В чем может быть проблема ?? СПАСИБО ","creator":{"public_name":"Saloev Sa'di","id":188371,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40421,"body":"Добрый день!\n\n> Я подумал может у nodejs другой синтаксис импорта модулей и поэтому скачал и установил babel\n\nПравильно, nodejs пока не понимает такой синтаксис, нужен babel\n\n> В чем может быть проблема ??\n\nПроблема в неправильной [настройке окружения](https://ru.hexlet.io/courses/js-setup-environment). Что за код, какие команды вы выполняли - не видя этого, трудно сказать в чём конкретно проблема. Вы можете сделать репозиторий с кодом (так как мы оформляли проекты в курсе про настройку окружения) и залить его на гитхаб, тогда можно будет посмотреть на ваш код.","topic_id":19140},{"creator":{"public_name":"Saloev Sa'di","id":188371,"is_tutor":false},"id":40443,"body":" Еще раз прочитал урок по babel и исправил ошибку.\n\n_\"Babel полагается на наличие файла .babelrc в **корне проекта.**\"_\n\nА у меня .babelrc был не в корне проекта :)","topic_id":19140},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40466,"body":"Отлично!","topic_id":19140}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":3991,"title":"Здравствуйте! Для чего в toStr, в решении учителя, символы ${} ?","plain_title":"Здравствуйте! Для чего в toStr, в решении учителя, символы ${} ? ","creator":{"public_name":"A T","id":107427,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":6660,"body":"Это интерполяция, об этом говорилось тут https://ru.hexlet.io/courses/programming-basics/lessons/string/theory_unit","topic_id":3991}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":29482,"title":"Всем привет!\nПодскажите, не понимаю в какой момент при исполнении третьей функции появляется значение **undefined**?\nhttps://ru.hexlet.io/code_reviews/124391","plain_title":"Всем привет! Подскажите, не понимаю в какой момент при исполнении третьей функции появляется значение undefined? https://ru.hexlet.io/code_reviews/124391 ","creator":{"public_name":"Дмитрий Хребтов","id":231636,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":63844,"body":"Вечер добрый! Хорошо, что разобрались.","topic_id":29482},{"creator":{"public_name":"Дмитрий Хребтов","id":231636,"is_tutor":false},"id":63838,"body":"Проблему решил - вся соль заключалась в использовании тернарного оператора\n> condition ? expression : expression\n\nТак вот после **?** и **:** скрытого выполнения команды return не происходит - отсюда и undefined","topic_id":29482}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":9708,"title":"Подскажите пожалуйста, вообще в голову ничего не лезет. Функцию findPrimitiveBox нужно реализовывать через цикл или он здесь вообще ни к чему? ","plain_title":"Подскажите пожалуйста, вообще в голову ничего не лезет. Функцию findPrimitiveBox нужно реализовывать через цикл или он здесь вообще ни к чему? ","creator":{"public_name":"Евгений Бухаров","id":143484,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":19866,"body":"Добрый день, Евгений! Я обновил описание этой практики и добавил инфу про эту функцию, для работы с новой версией нажмите кнопку \"Сбросить\"","topic_id":9708},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":19854,"body":"В первом уроке этого курса говорилось что здесь используется только рекурсия и неизменяемые данные.","topic_id":9708},{"creator":{"public_name":"Евгений Бухаров","id":143484,"is_tutor":false},"id":19861,"body":"А что за функция isPair экспортируется из hexlet-pairs в solution ?? Примеров применения этой функции нет в hexlet-pairs","topic_id":9708},{"creator":{"public_name":"Kirill Grafov","id":102252,"is_tutor":false},"id":19840,"body":"Насколько мне помнится, данное задание решается через рекурсию.","topic_id":9708}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":24456,"title":"Добрый вечер.Вот мой код \n\n`// removed`\n\nВ ответ я получаю :\n\n Unknown message 'function cdr(pair) {\n checkPair(pair);\n return pair('cdr');\n }'\n\nЯ не понимаю что хочет тест.По моей логике функция reversePair вызывате конструктор который с помощью car и cdr достает из аргумента pair точки,и кладет в переменную newPair в обратном порядке.","plain_title":"Добрый вечер.Вот мой код // removed В ответ я получаю : Unknown message 'function cdr(pair) { checkPair(pair); return pair('cdr'); }' Я не понимаю что хочет тест.По моей логике функция reversePair вызывате конструктор который с помощью car и cdr достает из аргумента pair точки,и кладет в переменную newPair в обратном порядке. ","creator":{"public_name":"Артур Петрович","id":191702,"is_tutor":false},"comments":[{"creator":{"public_name":"Andrey Nechaev","id":196072,"is_tutor":false},"id":52197,"body":"Расскажите ваше видение того, что вы подставляете в качестве аргументов в функцию cons()?\n","topic_id":24456},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":52225,"body":"Вот сейчас вы предлагаете использовать селекторы по назначению.","topic_id":24456},{"creator":{"public_name":"Артур Петрович","id":191702,"is_tutor":false},"id":52210,"body":"Я представляю себе работу инструкций следующим образом - импортированные функции car и cdr получают соответствующее им значение, первое и второе соответственно.Хотя вот подумал,это ведь функции, следовало писать так - car(pair) и cdr(pair). Так верно будет?","topic_id":24456},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":52198,"body":"Артур, приветствую! Тест просит вас передать корректное сообщение. \n\nДавайте разберёмся, что у вас происходит или должно происходить в следующих выражениях (опишите работу этих конструкций):\n\n pair(cdr)\n pair(car)\n\nВсё дело заключается в том, как выстроена работа с парами: как пара создаётся, и с помощью чего из неё извлекаются значения. Документация по конструкторам и селекторам всегда лежит в файле `doc/hexlet-pairs.md` в среде выполнения практики. А сейчас воспользуйтесь примерами из текстовой части урока.","topic_id":24456}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":25625,"title":"Добрый. Снова, возможно 100-й такой же вопрос, как понять, наверняка, когда следует создать переменную и с ней работать, а когда обойтись без нее\n[link](https://ru.hexlet.io/code_reviews/97692#file-2)","plain_title":"Добрый. Снова, возможно 100-й такой же вопрос, как понять, наверняка, когда следует создать переменную и с ней работать, а когда обойтись без нее link (https://ru.hexlet.io/code_reviews/97692#file-2) ","creator":{"public_name":"Ренат Сафаралиев","id":196200,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":54851,"body":"Ренат, приветствую! Универсального рецепта не существует. Нужно держать баланс. Если пояснительная переменная облегчит чтение кода, смело её вводите. В вашей программе эти переменные будут уместны.","topic_id":25625}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}},{"id":28412,"title":"Здравствуйте, объясните пж, я сначала написал функцию - \n\n`// removed`\n\nона ведь также работает как и функция учителя? или я не вижу в упор какой то маленькой детали? Моя функция при работе падает с ошибкой \n\n Argument must be pair, but it was '1'\n\n 3 | // BEGIN (write your solution here)\n 4 | const findPrimitiveBox = pair => {\n > 5 | const newCar = car(pair);\n | ^\n\nЯ задачу решил немного другим способом, без создания доп. констант и все работало, но когда увидел решение учителя, удивился, ведь в моем исполнении эта функция не работала.\n","plain_title":"Здравствуйте, объясните пж, я сначала написал функцию - // removed она ведь также работает как и функция учителя? или я не вижу в упор какой то маленькой детали? Моя функция при работе падает с ошибкой Argument must be pair, but it was '1' 3 | // BEGIN (write your solution here) 4 | const findPrimitiveBox = pair => { > 5 | const newCar = car(pair); | ^ Я задачу решил немного другим способом, без создания доп. констант и все работало, но когда увидел решение учителя, удивился, ведь в моем исполнении эта функция не работала. ","creator":{"public_name":"Олег Николаенко","id":215504,"is_tutor":false},"comments":[{"creator":{"public_name":"Dmitriy Bataev","id":106130,"is_tutor":false},"id":61226,"body":"Тут важно узнать откуда в тестах взялся аргумент `1`, в злополучном вызове функции `findPrimitiveBox`.","topic_id":28412},{"creator":{"public_name":"Олег Николаенко","id":215504,"is_tutor":false},"id":61400,"body":"isPair() возвращает либо true, либо false","topic_id":28412},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":61302,"body":"Олег, приветствую! Подскажите, вы поняли, о чём говорит Дмитрий?","topic_id":28412},{"creator":{"public_name":"Кадыров Александр","id":209321,"is_tutor":false},"id":61399,"body":"Простите, может глупый вопрос или я где-то что-то пропустил, но зачем писать \"!\" перед isPair в коде выше?","topic_id":28412},{"creator":{"public_name":"Олег Николаенко","id":215504,"is_tutor":false},"id":61360,"body":"\n`// removed`\n\nБанальная невнимательность. Зато решил задачу немного по разному)","topic_id":28412},{"creator":{"public_name":"Олег Николаенко","id":215504,"is_tutor":false},"id":78555,"body":"**Алексей Ковальковский**, лучше продублировать вопрос из этого треда в Обсуждения.","topic_id":28412},{"creator":{"public_name":"Алексей Ковальковский","id":123367,"is_tutor":false},"id":78523,"body":"Ребята, я застрял... Помогите понять, что я делаю не так? Первую и вторую функцию я сделал, но линтер меня не пускает. Не знаю, как завершить... А третью я вообще не могу понять. Знаю, что нужна рекурсия, знаю, что найдя в одном отсеке коробку, мы должны опять запустить функцию на проверку обоих отсеков на наличие коробок, но я не знаю, как это сделать(знаю, что нужна рекурсия)\nhttps://ru.hexlet.io/code_reviews/188146","topic_id":28412},{"creator":{"public_name":"Алексей Ковальковский","id":123367,"is_tutor":false},"id":78522,"body":"Что нужно делать, если isPair true!??? Снова запускать функцию findPrimitiveBox?","topic_id":28412},{"creator":{"public_name":"Олег Николаенко","id":215504,"is_tutor":false},"id":61359,"body":"Нет, я так и не могу понять, почему у меня появляется единица. \n\nhttps://ru.hexlet.io/code_reviews/116177#file-2 - такое решение проходит тесты, а так как я написал в первый раз - нет.","topic_id":28412}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Пары","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":441,"slug":"js_compound_data_pairs_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"В этом задании мы немного потренируемся работать с парами. Без фанатизма и по шагам.\n\n## Шаг 1 - reversePair.js\n\nРеализуйте функцию `reversePair()`, которая принимает на вход\nпару и возвращает другую, в которой значения переставлены местами:\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair = cons('one', 'two')\nconsole.log(toString(reversePair(pair))) // ('two', 'one')\n```\n\n## Шаг 2 - sumOfPairs.js\n\nРеализуйте функцию `sumOfPairs()`, которая принимает на вход\nдве пары и возвращает новую пару, в элементах которой находятся суммы элементов из\nисходных пар:\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair1 = cons(4, 10)\nconst pair2 = cons(100, 0)\nconsole.log(toString(sumOfPairs(pair1, pair2))) // (104, 10)\n```\n\n## Шаг 3 - findPrimitiveBox.js\n\nОднажды вы сидели дома, когда курьер Василий принес вам коробку. С коробкой\nшла записка следующего содержания:\n\n> Коробка состоит из двух отсеков, в одном из которых письмо, а в другом лежит еще одна\n> коробка, в которой также два отсека и точно также один отсек с письмом, а в другом -\n> коробка. Коробки могут быть вложены друг в друга сколько угодно раз. Вам нужно\n> добраться до коробки, внутри которой нет вложенной коробки ни в одном из двух отсеков, и отдать ее курьеру.\n\nПодчеркну, что во всех коробках, кроме той последней, в одном отсеке письмо (любые данные, которые не являются парой), а в другом -\nвсегда коробка, но никогда не две коробки одновременно. Сами отсеки при этом могут меняться, то есть в одной коробке отсеком с письмом может быть первый, а в другой - последний.\n\nРеализуйте рекурсивную функцию `findPrimitiveBox()`, которая принимает на вход \"коробку\" (**пару**), находит внутри нее пару без вложенных пар (как описано выше) и возвращает наружу.\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair = cons(\n null,\n cons('one', 'two'),\n)\ntoString(findPrimitiveBox(pair)) // ('one', 'two')\n\nconst pair2 = cons(\n cons(null, cons(1, 5)),\n null,\n)\ntoString(findPrimitiveBox(pair2)) // (1, 5)\n```\n\n### Подсказки\n\n* Выполняйте практику строго по порядку (по шагам): от простых упражнений к более сложным.\n* Функция `isPair()` из библиотеки `@hexlet/pairs` проверяет, является ли переданный ей параметр парой. Более подробное описание функции смотрите в документации библиотеки (файл `hexlet-pairs.md` в каталоге `docs`).\n* Функцию `toString()` из библиотеки `@hexlet/pairs` можно использовать (при необходимости) для отладки решений.\n","prepared_readme":"В этом задании мы немного потренируемся работать с парами. Без фанатизма и по шагам.\n\n## Шаг 1 - reversePair.js\n\nРеализуйте функцию `reversePair()`, которая принимает на вход\nпару и возвращает другую, в которой значения переставлены местами:\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair = cons('one', 'two')\nconsole.log(toString(reversePair(pair))) // ('two', 'one')\n```\n\n## Шаг 2 - sumOfPairs.js\n\nРеализуйте функцию `sumOfPairs()`, которая принимает на вход\nдве пары и возвращает новую пару, в элементах которой находятся суммы элементов из\nисходных пар:\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair1 = cons(4, 10)\nconst pair2 = cons(100, 0)\nconsole.log(toString(sumOfPairs(pair1, pair2))) // (104, 10)\n```\n\n## Шаг 3 - findPrimitiveBox.js\n\nОднажды вы сидели дома, когда курьер Василий принес вам коробку. С коробкой\nшла записка следующего содержания:\n\n> Коробка состоит из двух отсеков, в одном из которых письмо, а в другом лежит еще одна\n> коробка, в которой также два отсека и точно также один отсек с письмом, а в другом -\n> коробка. Коробки могут быть вложены друг в друга сколько угодно раз. Вам нужно\n> добраться до коробки, внутри которой нет вложенной коробки ни в одном из двух отсеков, и отдать ее курьеру.\n\nПодчеркну, что во всех коробках, кроме той последней, в одном отсеке письмо (любые данные, которые не являются парой), а в другом -\nвсегда коробка, но никогда не две коробки одновременно. Сами отсеки при этом могут меняться, то есть в одной коробке отсеком с письмом может быть первый, а в другой - последний.\n\nРеализуйте рекурсивную функцию `findPrimitiveBox()`, которая принимает на вход \"коробку\" (**пару**), находит внутри нее пару без вложенных пар (как описано выше) и возвращает наружу.\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair = cons(\n null,\n cons('one', 'two'),\n)\ntoString(findPrimitiveBox(pair)) // ('one', 'two')\n\nconst pair2 = cons(\n cons(null, cons(1, 5)),\n null,\n)\ntoString(findPrimitiveBox(pair2)) // (1, 5)\n```\n\n### Подсказки\n\n* Выполняйте практику строго по порядку (по шагам): от простых упражнений к более сложным.\n* Функция `isPair()` из библиотеки `@hexlet/pairs` проверяет, является ли переданный ей параметр парой. Более подробное описание функции смотрите в документации библиотеки (файл `hexlet-pairs.md` в каталоге `docs`).\n* Функцию `toString()` из библиотеки `@hexlet/pairs` можно использовать (при необходимости) для отладки решений.\n","has_solution":true,"entity_name":"Пары"},"units":[{"id":1323,"name":"theory","url":"/courses/js-compound-data/lessons/pair/theory_unit"},{"id":1325,"name":"quiz","url":"/courses/js-compound-data/lessons/pair/quiz_unit"},{"id":1324,"name":"exercise","url":"/courses/js-compound-data/lessons/pair/exercise_unit"}],"links":[{"id":424923,"name":"История возникновения cons/car/cdr","url":"https://en.wikipedia.org/wiki/CAR_and_CDR"}],"ordered_units":[{"id":1323,"name":"theory","url":"/courses/js-compound-data/lessons/pair/theory_unit"},{"id":1325,"name":"quiz","url":"/courses/js-compound-data/lessons/pair/quiz_unit"},{"id":1324,"name":"exercise","url":"/courses/js-compound-data/lessons/pair/exercise_unit"}],"id":676,"slug":"pair","state":"approved","name":"Пары","course_order":30,"goal":"Разбираемся с тем, как устроены и реализованы точки в нашей графической библиотеке","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"165147034","theory":"## Пары\n\nОбратите внимание на то, что **пары неизменяемы**. Нельзя просто так взять и изменить пару. Можно только создать новую на основе предыдущей. Поначалу такой способ программирования может показаться необычным и сложным, так как надо перестроить свое мышление. Чем дальше вы будете продвигаться по курсам, тем больше он вам начнет нравиться. Вы увидите, как часто упрощается код и его отладка в отсутствие изменяемости.\n\n## Ошибки\n\nРаботая с парами, очень легко допустить ошибку, которая будет выглядеть так:\n\n> Argument must be a pair, but it was ...\n\nПарой является только то, что создано с помощью конструктора `cons`. Если по какой-то причине в селекторы произошла передача не пары, то результатом будет как раз такая ошибка. Проверить это очень легко:\n\n```javascript\nconst pair = 3\ncar(pair) // Argument must be a pair, but it was '3'\n```\n\nТо же самое, если передать селектору строку вместо пары:\n\n```javascript\nconst pair = 'hello'\ncar(pair) // Argument must be a pair, but it was 'hello'\n```\n\n---\n\n## Конспект урока\n\nМы уже написали несколько полезных функций для работы с точками и, в принципе, поняли, как работает эта абстракция. Теперь пришло время копнуть на уровень глубже и посмотреть, как же устроены наши точки.\n\n```javascript\nimport { cons, car, cdr } from 'hexlet-pairs'\n\n// Конструктор\nconst pair = cons(8, 7)\n\ncar(pair) // 8\ncdr(pair) // 7\n\nconst pair2 = cons(3, pair)\n```\n\nУстроены они достаточно просто и используют структуру данных, которая называется парой. Пар в самом языке JavaScript не существует, мы их реализовали с помощью отдельной библиотеки, и выше можно увидеть пример того, как они используются. Мы импортируем из библиотеки конструктор `cons` и селекторы `car` и `cdr`. Конструктор создает пару, а селекторы служат для извлечения из пары первого значения (с помощью `car`) и второго значения (с помощью `cdr`). Все достаточно просто и очень похоже на реализацию точек из прошлого урока.\n\nЧто интересно, элементами пары могут быть другие пары. В будущем это даст нам очень мощные возможности для того, чтобы строить более сложные структуры данных, в том числе списковые.\n\n```javascript\nimport { cons, car, cdr, toString } from '@hexlet/pairs'\n\nconst pair = cons('first', 'second')\nconsole.log(car(pair)) // => first\nconsole.log(cdr(pair)) // => second\n\n// don't do it\nconsole.log(pair) // => { [Function] pair: true }\n\nconsole.log(toString(pair)) // => (first, second)\n```\n\nДавайте посмотрим, как представлены наши точки с помощью пар:\n\n```javascript\nimport { cons, car, cdr } from 'hexlet-pairs'\n\nconst makePoint = (x, y) => cons(x, y)\n\nconst getX = point => car(point)\nconst getY = point => cdr(point)\n\nconst toString = point => String(point)\n```\n\nЗдесь все предельно просто: `makePoint` — это функция, которая принимает `x` и `y` и вызывает конструктор пары с этими аргументами. То же самое с селекторами: `getX` и `getY` принимают на вход точку и вызывают с этой точкой `car` и `cdr` соответственно.\n\nМожно заметить, что сработало бы даже такое определение:\n\n```javascript\nconst makePoint = cons\n\nconst getX = car\nconst getY = cdr\n```\n\nЗдесь все верно с синтаксической точки зрения и с точки зрения получения конечного результата. Но с таким способом определения есть некоторые проблемы: по сути, когда мы делаем такое присваивание, получается, что `makePoint` и `cons` являются одним и тем же объектом. Кто-то может сказать, что они ссылаются на одну и ту же функцию, но это уже тонкости реализации конкретного языка программирования. На практике это означает, что, запустив построенный таким образом код, вы не увидите вызова функций `makePoint`, `getX` или `getY`, потому что их фактически _не существует_. При отладке вы не найдете этих функций в трассировке стека. Вы можете захотеть увидеть все вызовы, например, `makePoint`, но вы точно не захотите отслеживать в вашей программе все вызовы `cons`, которые могут использоваться не только для точек, а вообще для любых библиотек. Поэтому мы не используем такое определение, но о нем нужно знать, чтобы понимать, как в целом все работает.\n\nТеперь, используя пары, мы можем строить новые абстракции, расширяя нашу библиотеку графических примитивов. Мы вводим понятие отрезка, для которого мы создаем конструктор и селекторы:\n\n```javascript\nconst point1 = makePoint(1, 2)\nconst point2 = makePoint(10, -2)\n\nconst segment = makeSegment(point1, point2)\n\nstartSegment(segment) // (1, 2)\nendSegment(segment) // (10, -2)\n```\n\nНам нужно сделать две точки (потому что любой отрезок представлен двумя точками). После этого мы используем конструктор `makeSegment` и передаем туда наши точки, а с помощью селекторов `startSegment` и `endSegment` мы получаем точки. Важно, что мы получаем именно точки, потому что это тоже составные данные со своими селекторами, с помощью которых можно получать примитивные значения и производить над ними какие-либо манипуляции при необходимости.\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/pair/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>
<p>Обратите внимание на то, что <strong>пары неизменяемы</strong>. Нельзя просто так взять и изменить пару. Можно только создать новую на основе предыдущей. Поначалу такой способ программирования может показаться необычным и сложным, так как надо перестроить свое мышление. Чем дальше вы будете продвигаться по курсам, тем больше он вам начнет нравиться. Вы увидите, как часто упрощается код и его отладка в отсутствие изменяемости.</p>
<h2 id="heading-2-2">Ошибки</h2>
<p>Работая с парами, очень легко допустить ошибку, которая будет выглядеть так:</p>
<blockquote>
<p>Argument must be a pair, but it was ...</p>
</blockquote>
<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">cons</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 pair = 3
car(pair) // Argument must be a pair, but it was '3'</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>То же самое, если передать селектору строку вместо пары:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const pair = 'hello'
car(pair) // Argument must be a pair, but it was 'hello'</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>
<hr/>
<h2 id="heading-2-3">Конспект урока</h2>
<p>Мы уже написали несколько полезных функций для работы с точками и, в принципе, поняли, как работает эта абстракция. Теперь пришло время копнуть на уровень глубже и посмотреть, как же устроены наши точки.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { cons, car, cdr } from 'hexlet-pairs'
// Конструктор
const pair = cons(8, 7)
car(pair) // 8
cdr(pair) // 7
const pair2 = cons(3, pair)</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>Устроены они достаточно просто и используют структуру данных, которая называется парой. Пар в самом языке JavaScript не существует, мы их реализовали с помощью отдельной библиотеки, и выше можно увидеть пример того, как они используются. Мы импортируем из библиотеки конструктор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</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">car</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">cdr</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">car</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">cdr</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">import { cons, car, cdr, toString } from '@hexlet/pairs'
const pair = cons('first', 'second')
console.log(car(pair)) // => first
console.log(cdr(pair)) // => second
// don't do it
console.log(pair) // => { [Function] pair: true }
console.log(toString(pair)) // => (first, second)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Давайте посмотрим, как представлены наши точки с помощью пар:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { cons, car, cdr } from 'hexlet-pairs'
const makePoint = (x, y) => cons(x, y)
const getX = point => car(point)
const getY = point => cdr(point)
const toString = point => String(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>
<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> принимают на вход точку и вызывают с этой точкой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">car</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">cdr</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 makePoint = cons
const getX = car
const getY = cdr</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">cons</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">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">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>, потому что их фактически <em>не существует</em>. При отладке вы не найдете этих функций в трассировке стека. Вы можете захотеть увидеть все вызовы, например, <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">cons</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 point1 = makePoint(1, 2)
const point2 = makePoint(10, -2)
const segment = makeSegment(point1, point2)
startSegment(segment) // (1, 2)
endSegment(segment) // (10, -2)</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">makeSegment</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">startSegment</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">endSegment</code> мы получаем точки. Важно, что мы получаем именно точки, потому что это тоже составные данные со своими селекторами, с помощью которых можно получать примитивные значения и производить над ними какие-либо манипуляции при необходимости.</p></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/pair/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/pair/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>