<!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 18:35:04 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="l4gCnK2HhG4nMUasf1VQ60f1k-nwlC9Y7MVKqG8kbs54WcmrX_kpDpFyYjRzWqCch_y-Q_ij0fpRJdD8PSOJoA";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: Последовательности: Вспоминаем пары (pairs) и рассматриваем представление списков с помощью пар">
<link rel="canonical" href="https://ru.hexlet.io/courses/sequences/lessons/list/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Представление последовательностей">
<meta property="og:title" content="JS: Последовательности">
<meta property="og:description" content="Представление последовательностей / JS: Последовательности: Вспоминаем пары (pairs) и рассматриваем представление списков с помощью пар">
<meta property="og:url" content="https://ru.hexlet.io/courses/sequences/lessons/list/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="Ib8Q-ZEHl6P7FUQmhAuWegp5tu_nmoA3O6GqfEJOGkXObtvOY3k6w01WYL6IBGYNynCbRe-tfpWGQTAoEEn9Kw" />
<script src="/vite/assets/inertia-DfXos102.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-BrRXra1y.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-cb8xch9l.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-26T18:35:04.618Z","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":"LV5qdxFA98lgIZvbEbYCaq0xPGGallI4gNiIQQympR3Cj6FA4z5aqdZiv0MdufIdbTgRy5KhrJo9OBIVXqFCcw","topics":[{"id":19210,"title":"Помогите, пожалуйста, разобрать решение учителя функции reverse. Не могу понять внутреннюю функцию.","plain_title":"Помогите, пожалуйста, разобрать решение учителя функции reverse. Не могу понять внутреннюю функцию. ","creator":{"public_name":"Денис Тимошенко","id":186656,"is_tutor":false},"comments":[{"creator":{"public_name":"Денис Тимошенко","id":186656,"is_tutor":false},"id":40671,"body":"Добрый день!\nСпасибо! Прошел день и стало все понятно :) Просто внутренняя функция вызывается с двумя параметрами и этот вызов возвращается внешней функцией.","topic_id":19210},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40705,"body":"Гуд! :)","topic_id":19210},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40581,"body":"Добрый день!\n\n> Не могу понять внутреннюю функцию.\n\nПомогло бы, если бы вы обозначили стартовую точку непонимания. Какая конкретно часть этой функции или алгоритма кажется непонятной? Вообще, это стандартная рекурсивная функция с итеративным процессом: в первом параметре передаём обрабатываемый список, во втором параметре - аккумулятор, в котором накапливается результат, то есть формируется итоговый список (перевёрнутый).","topic_id":19210}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":9098,"title":"Помогите разобраться. Для объединения строк использовал код:\n```\n// removed\n```\nНа выходе получаю: \"((3, 4, 5, 8), 3, 2, 9)\".\nПочему copy(x) выдает (3, 4, 5, 8), а copy(y) - 3, 2, 9?\nОткуда берутся скобки?\nИ почему нельзя объединить последовательности через cons(x,y)? В этом случае также появляются лишние скобки.","plain_title":"Помогите разобраться. Для объединения строк использовал код: // removed На выходе получаю: \"((3, 4, 5, 8), 3, 2, 9)\". Почему copy(x) выдает (3, 4, 5, 8), а copy(y) - 3, 2, 9? Откуда берутся скобки? И почему нельзя объединить последовательности через cons(x,y)? В этом случае также появляются лишние скобки. ","creator":{"public_name":"Дмитрий Рытиков","id":143344,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":18370,"body":"Добрый день!\n\n> Для объединения строк использовал код\n\nПравильнее так: \"для соединения двух списков\"\n\n> cons(copy(x),copy(y))\n\nДавайте разберём, что происходит в данной операции:\n1. `copy(x)` - создаётся копия списка `x`\n1. `copy(y)` - создаётся копия списка `y`\n\n---> cons(списокX, списокY);\n\nДалее, смотрим в документацию по работе со списками (файл `hexlet-pairs-data.md`), что делает функция `cons`. Она принимает два параметра: первый это произвольный элемент, второй параметр это список, к которому надо добавить первый параметр.\n\nТаким образом выражение `cons(списокX, списокY)` добавляет в начало списка `списокY` элемент `списокX`. В результатах мы видим новый список (это его текстовое представление): `((3, 4, 5, 8), 3, 2, 9)`, в котором первым элементом является `списокX`, а остальными элементами являются элементы `списокY`.\n\n> И почему нельзя объединить последовательности через cons(x,y)?\n\nПотому что функция `cons` делает совершенно другую операцию - она добавляет элемент к списку. Это не тоже самое, что соединение двух списков. Соединение двух списков необходимо реализовать вам.","topic_id":9098}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":25852,"title":"не актуально)","plain_title":"не актуально) ","creator":{"public_name":"Ренат Сафаралиев","id":196200,"is_tutor":false},"comments":[{"creator":{"public_name":"Ренат Сафаралиев","id":196200,"is_tutor":false},"id":55346,"body":"Когда мы возвращаем `iter(tail(newList), cons(head(newList), acc))` не особо понятно, как это тут все работает. Конкретно про конструктор cons.\nЕсли изначально я передал список (1, 2, 3, 4) то \n```\niter((2, 3, 4), cons(1, l()))\n```\nследующий шаг \n```\niter((3, 4), cons(2, cons(1, l()))\n```\nnext step\n```\niter( (4), cons(3, cons(2, cons(1, l()))\n```\n\nв итоге когда список пустой мы возвращаем новый список\n```\ncons(4, cons(3, cons(2, cons(1, l())))\n```\nТакие шаги?","topic_id":25852}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":21046,"title":"Добрый день! Как мне кажется - мое решение реализации функции has правильное. Но не работает.\nПричем как-то очень странно.\nПроверял на всех тестах...\nПервый тест проходит.\nНа втором пишет что функция вернула undefined, хотя так же выводится и console.log('true');. Из чего логично что и return true сработать должен. На остальных тестах та же ошибка.(т.е. только первый тест проходит)\nПомогите понять почему так происходит\n\n`// removed`","plain_title":"Добрый день! Как мне кажется - мое решение реализации функции has правильное. Но не работает. Причем как-то очень странно. Проверял на всех тестах... Первый тест проходит. На втором пишет что функция вернула undefined, хотя так же выводится и console.log('true');. Из чего логично что и return true сработать должен. На остальных тестах та же ошибка.(т.е. только первый тест проходит) Помогите понять почему так происходит // removed ","creator":{"public_name":"Igor Zubarev","id":191492,"is_tutor":false},"comments":[{"creator":{"public_name":"Igor Zubarev","id":191492,"is_tutor":false},"id":44620,"body":"Я правильно понял, что если мои проверки не сработали и мы рекурсивно вызываем функцию снова, то результат предыдущего вызова undefined - в этом проблема?","topic_id":21046},{"creator":{"public_name":"Igor Zubarev","id":191492,"is_tutor":false},"id":44621,"body":"Понял чего не хватало. Спасибо за наводку)","topic_id":21046},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":44613,"body":"Здравствуйте!\n\nВы делаете рекурсивный вызов функции `has`, а что происходит далее с результатом этого вызова?","topic_id":21046}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":7551,"title":"Выдает ошибку TypeError: pair is not a function. Немогу понять в чем проблема\n\n```\n// removed\n```","plain_title":"Выдает ошибку TypeError: pair is not a function. Немогу понять в чем проблема // removed ","creator":{"public_name":"Dima Holiak","id":119352,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":14166,"body":"Отлично)","topic_id":7551},{"creator":{"public_name":"Dima Holiak","id":119352,"is_tutor":false},"id":14149,"body":"Все решил проблему )","topic_id":7551}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":6919,"title":"Привет! Последняя мысль в видео меня заинтересовала. Как понять, когда следует вводить отдельную функцию для чего-то, вместо того, что казалось бы можно написать в одну строчку кода. Достаточно того критерия, что ты собираешься использовать подобный кусок кода больше одного раза или есть ещё какие-то критерии? Вопрос немного общий и абстрактный получился, надеюсь меня поймут :)","plain_title":"Привет! Последняя мысль в видео меня заинтересовала. Как понять, когда следует вводить отдельную функцию для чего-то, вместо того, что казалось бы можно написать в одну строчку кода. Достаточно того критерия, что ты собираешься использовать подобный кусок кода больше одного раза или есть ещё какие-то критерии? Вопрос немного общий и абстрактный получился, надеюсь меня поймут :) ","creator":{"public_name":"Artyom Resh","id":121175,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":12753,"body":"Добрый день!\n\n> Последняя мысль в видео меня заинтересовала. Как понять, когда следует вводить отдельную функцию для чего-то, вместо того, что казалось бы можно написать в одну строчку кода.\n\nПоследняя мысль преподавателя касалась соблюдения уровня абстракции и опиралась на конкретный пример - можете расписать, как Вы её поняли?\n\n> Достаточно того критерия, что ты собираешься использовать подобный кусок кода больше одного раза\n\nДа, как правило, достаточно.\n\n\n> или есть ещё какие-то критерии? Вопрос немного общий и абстрактный получился, надеюсь меня поймут :)\n\nПричин выделения кода в отдельную функцию достаточно много. Вообще, всё это входит в более широкую тему написания хорошего (чистого) кода. Например, см. [книгу \"Совершенный код\"](https://map.hexlet.io/pages/books)","topic_id":6919}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":8304,"title":"Помогите разобраться. На первой же функции тупик. Мы через вызов функции **has()** передаем два аргумента, один из которых функция списка **l(a, b, c, d)**, а второй - **число**.\nДля того, чтобы проверить, есть ли второй аргумент в списке функции **l(a, b, c, d)** я создал две переменные, которым присвоил значения: первой переменной **head(numbers)**, второй - **tail(numbers)**. Оба значения преобразовал в строки при помощи функции **toString()**. _/* возможно, напрасно я это сделал */_ \n\nПроблемы сравнить число с результатом **head(numbers)** нет, потому что **head(numbers)** возвращает единственный символ. Возникли проблемы с **tail(numbers)**. Вопрос: после преобразования при помощи **toString()** мы получаем строковое значение **\"a, b, c, d\"**? Из видео-уроке явствует, что мы таким образом _\"распечатываем\"_. В общем, при проверке кода именно с итерацией значения **tail(number).toString()** _/*который при итерации я преобразовываю в числа */_ происходит беда.\n\nМой код: \n```\nexport const has = (numbers, num) => {\n let a = head(numbers).toString();\n let b = tail(numbers).toString();\n if ( num === +a) return true;\n for (let i = 0; i < b.length; i++) {\n if (num === +b[i]) return true;\n }\n return false;\n}\n```\n\nОшибка: \n\n```\n1) Data #has:\n\n AssertionError: false == true\n + expected - actual\n\n -false\n +true\n \n at Context.<anonymous> (test.js:9:12)\n\n```\nЧисло 3 проверку проходит, а 8 не проходит\n \n\n ","plain_title":"Помогите разобраться. На первой же функции тупик. Мы через вызов функции has() передаем два аргумента, один из которых функция списка l(a, b, c, d), а второй - число. Для того, чтобы проверить, есть ли второй аргумент в списке функции l(a, b, c, d) я создал две переменные, которым присвоил значения: первой переменной head(numbers), второй - tail(numbers). Оба значения преобразовал в строки при помощи функции toString(). /* возможно, напрасно я это сделал */ Проблемы сравнить число с результатом head(numbers) нет, потому что head(numbers) возвращает единственный символ. Возникли проблемы с tail(numbers). Вопрос: после преобразования при помощи toString() мы получаем строковое значение \"a, b, c, d\"? Из видео-уроке явствует, что мы таким образом \"распечатываем\". В общем, при проверке кода именно с итерацией значения tail(number).toString() /*который при итерации я преобразовываю в числа */ происходит беда. Мой код: export const has = (numbers, num) => { let a = head(numbers).toString(); let b = tail(numbers).toString(); if ( num === +a) return true; for (let i = 0; i < b.length; i++) { if (num === +b[i]) return true; } return false; } Ошибка: 1) Data #has: AssertionError: false == true + expected - actual -false +true at Context.<anonymous> (test.js:9:12) Число 3 проверку проходит, а 8 не проходит ","creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"comments":[{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16156,"body":"Прежде всего я не понял, какой тип данных мы получаем в результате применения tail(numbers), число или массив? ","topic_id":8304},{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16214,"body":"Мне кажется, что я приблизительно понял принцип рекурсии функции has. \nВ самом начале мы делаем сравнение первого числа списка numbers с числом передаваемого аргумента num \n\n```\nhead(numbers) == num \n```\n\nЕсли числа равны, то возвращаем true. Если нет, то вызываем функцию has с параметрами (tail(numbers), num).\n\nТаким образом, при второй итерации сравнение первого числа списка с num будет выглядеть так:\n\n```\nhead(tail(numbers) == num\n```\nи так далее, пока не закончатся элементы списка.\n\nЕсли они закончились, но сравнение все равно не вернуло true, то по идее должно вернуться false.\n\nХотелось бы понять, в правильном ли направлении идут мои, не побоюсь этого слова, рефлексии? \n\nСам код: \n\n```\nconst has = (numbers, num) => {\n if (head(numbers) === num) return true;\n else return has(tail(numbers), num);\n return false;\n}\n```\nОшибка при проверке:\n\n```\n1) Data #has:\n TypeError: pair is not a function\n at Object.car (/usr/local/lib/node_modules/hexlet-pairs-data/node_modules/hexlet-pairs/dist/index.js:35:10)\n at head (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:48:16)\n at has (solution.js:5:7)\n at has (solution.js:6:15)\n at has (solution.js:6:15)\n at has (solution.js:6:15)\n at has (solution.js:6:15)\n at Context.<anonymous> (test.js:10:16)\n```\nЛиния 10 в файле test.js \n\n```\nassert.ok(!has(numbers, 0));\n```\n\n\n\n \n\n","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16198,"body":"> Это я таким способом хотел преодолеть 10-ую строку в test.js\n\nНе стоит подгонять код под тесты, вы же хотите научиться программировать, а не зачет в университете сдать.","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16160,"body":"Дело в том что в этом курсе не было ни слова про массив, даже название урока \"представление последовательностей\". Посмотрите пожалуйста текстовую часть теории, которую мы добавили буквально сегодня. Возможно она прояснит детали.\n\nА так же на 9:00 минуте и дальше я говорю что такое head и tail, с другой стороны в коде (в комментариях) показано что такое `tail`. Давайте разбрем что из этого было не понятно?","topic_id":8304},{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16176,"body":"Я понял, что такое head(list) и tail(list). Еще эту [статью ](http://blog.jeremyfairbank.com/javascript/functional-javascript-lists-1/) почитал. \nМое непонимание заключается в следующем: если мы проводим итерацию при помощи функции (не используем циклы), то\n\nа) Мы в только данном случае не используем циклы или мы в принципе их не можем использовать в подобных задачах. Я пробовал, в случае с итерацией tail(list) циклы возвращают через console.log только последнее число. В этой связи у меня и возник вопрос о типе данных, выдаваемых tail(list), чтобы можно было посёрфить в инете подходящий метод.\n\nб) Мне непонятно, каким образом при помощи рекурсии получить доступ к каждому элементу списка для сравнения его с переданным аргументом. Если head(list) дает значение первого элемента (и с этим все понятно), то tail(list) всех последующих, а их может быть много. ","topic_id":8304},{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16192,"body":"Пардон \n```\nconst has = (numbers, num) => {\n if (num == 0) {\n return false\n }\n if (head(numbers) == num) {\n return true;\n }\n if (tail(numbers) == num) {\n return true;\n }\n else {\n return has(tail(numbers));\n }\n return false;\n}\n```\n","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16161,"body":"Кстати, вы ведь прошли тесты? Там ведь было много вопросов с использованием tail.","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16268,"body":"Попробуйте сбросить прогресс и выполнить упражнение заново. Мы сильно обновили наши библиотеки и теперь вместо `cons is not a function.` должны быть нормальные сообщения об ошибках.","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16179,"body":"Так же как и в цикле, с помощью перебора. Вызываете функцию рекурсивно передавая туда хвост и так далее пока не дойдете до конца списка.","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16197,"body":"head же, а дальше вы рекурсивно проверяете новый список, полученный с помощью tail","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16193,"body":"`tail(numbers) == num` вы сравниваете список и число, они никогда не будут равны.\n\n`has` принимает два параметра, а внутри вы вызываете его с одним.\n\n```\nif (num == 0) {\n return false\n }\n\n```\n\nТо есть если число равно нулю, то его заведомо нет в списке?","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16191,"body":"Я не знаю как вам, но мне очень тяжело проследить цепочку вызовов, из-за того что вы опускаете фигурные скобки у if. Так не принято делать, потому что очень усложняется анализ и легко ошибиться.","topic_id":8304},{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16236,"body":"Первые два задания выполнил. Теперь застрял на функции append.\n\n```\nconst append = (numbers, numbers2) => {\n if (isEmpty(numbers)) return numbers2;\n return append(tail(numbers), cons(tail(numbers), numbers2));\n}\n```\nВыдает совершенно не то, что нужно. \n```\n1) Data #append:\n\n AssertionError: '((), (8), (5, 8), (4, 5, 8), 3, 2, 9)' == '(3, 4, 5, 8, 3, 2, 9)'\n + expected - actual\n\n -((), (8), (5, 8), (4, 5, 8), 3, 2, 9)\n +(3, 4, 5, 8, 3, 2, 9)\n \n at Context.<anonymous> (test.js:22:12)\n```\nПо идее первый аргумент должен был отсчитывать до нуля, а второй объединить tail(numbers) и numbers2.\n\nПытался манипулировать head и tail, но ругается, что cons is not a function.\n\n ","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16140,"body":"1. В этом курсе не подразумеваются мутации совсем. Никаких let и циклов, только рекурсия с неизменяемым состоянием.\n1. `.toString();` вы используете вещи, которые не только не разбирались, но и не имеют отношения к текущему заданию.\n\nОпишите алогоритм, который вы хотите построить, своими словами пожалуйста.","topic_id":8304},{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16195,"body":"> tail(numbers) == num вы сравниваете список и число, они никогда не будут равны.\n\nТак вот это самый для меня непонятный момент. Как мне выделить один элемент из списка, чтобы сравнить его с числом?\n\n> То есть если число равно нулю, то его заведомо нет в списке?\n\nЭто я таким способом хотел преодолеть 10-ую строку в test.js\n```\nassert.ok(!has(numbers, 0));\n```\n\n\n\n\n\n","topic_id":8304},{"creator":{"public_name":"Davud Kakhrimanov","id":139303,"is_tutor":false},"id":16190,"body":"Попробовал такой код (практически наугад) \n\n```\nconst has = (numbers, num) => {\n if (num == 0) return false;\n if (head(numbers) == num) return true;\n if (tail(numbers) == num) return true;\n else return has(tail(numbers));\n return false;\n}\n\nНа 11 строчке : \n```\nassert.ok(!has(numbers, 7));\n``` \nвыдает ошибку\n\n``````\n1) Data #has:\n\n AssertionError: false == true\n + expected - actual\n\n -false\n +true\n \n at Context.<anonymous> (test.js:11:12)\n```\n\nПолучается, что чтение кода не доходит до последней строчки **return false**. По какой причине это может быть?\n","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16209,"body":"Попробуйте написать рекурсивное вычисление суммы двух чисел, работающее по следующему алгоритму:\n\nот первого числа отщипываются единички и прибавляются к аккумулятору, который в начале равен второму числу. Вы получите абсолютно аналогичное решение тому что есть в этом задании.","topic_id":8304},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":16234,"body":"Идея правильная да.\n\nА так у вас не хватает проверки на пустоту списка. Что будет если вы берете `tail(l())` от пустого списка?","topic_id":8304}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":32565,"title":"Expected: \"(5, 4, 3)\"\nReceived: \"(5, (4, (3, ())))\"\n\n`// removed`\n\nСовершенно не понимаю, что делаю не так(","plain_title":"Expected: \"(5, 4, 3)\" Received: \"(5, (4, (3, ())))\" export const reverse = (list) => { const iter = (lst, acc) => { if (isEmpty(lst)) { return acc; } return iter(tail(lst), l(head(lst), acc)); }; return iter(list, l()); }; Совершенно не понимаю, что делаю не так( ","creator":{"public_name":"Тимур Анвартдинов","id":231828,"is_tutor":false},"comments":[{"creator":{"public_name":"Тимур Анвартдинов","id":231828,"is_tutor":false},"id":70741,"body":"Спасибо. Решил кривым методом через строчки. Но сейчас понимаю, как нужно сделать правильно","topic_id":32565},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":70736,"body":"Здравствуйте, **Тимур**.\n\nТесты показывают, что ожидается список значений `(5, 4, 3)`, а получен список вложенных списков `(5, (4, (3, ())))`. Обратите внимание на эту часть кода: `l(head(lst), acc)`.\nПодумайте какой функцией заменить `l`. Посмотрите список импортируемых функций в модуле.","topic_id":32565}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":11087,"title":"Сразу извиняюсь за 2 топика подряд, просто вопрос касается двух разных функций и не хочется смешивать.\n\nПо поводу функции **concat**:\n\n1. Самая простая и очевидная реализация (для меня по крайней мере) функции **copy** это:\n```\nconst copy = (list) => list;\n```\nтест она проходит, но как это может помочь мне с решением **concat** пока не понял.\n\n2. сначала логика подсказывала мне (и по комментам я понял, что не один такой) перевернуть первый список и добавлять голову этого перевёрнутого списка ко второму, так как мы явно знаем, как добавлять один элемент к списку и ложиться он будет в начало списка. НО, опять же по комментам, стало понятно, что есть более простое решение рекурсией без добавления реверсивной функции.\n\n3. пока что, решение заключается в том, чтобы рекурсивно откусывать голову от второго списка и строить новый список следующим образом:\n```\n// remove\n```\nно output выдаёт наличие лишних скобок:\n```\nExpected value to be (using ===):\n \"(3, 4, 5, 8, 3, 2, 9)\"\n Received:\n \"((((3, 4, 5, 8), 3), 2), 9)\"\n```\n\nреализация через **cons** выдаёт тоже самое (ну другого результата и ожидать было сложно в силу равносильности конструкций):\n```\n// remove\n```\nвопрос, я думаю не в ту сторону (откусывать голову у второго списка и пытаться запихнуть его вконец) или пишу неправильно (мысль правильная, но реализую криво)?\n","plain_title":"Сразу извиняюсь за 2 топика подряд, просто вопрос касается двух разных функций и не хочется смешивать. По поводу функции concat: Самая простая и очевидная реализация (для меня по крайней мере) функции copy это: const copy = (list) => list; тест она проходит, но как это может помочь мне с решением concat пока не понял. сначала логика подсказывала мне (и по комментам я понял, что не один такой) перевернуть первый список и добавлять голову этого перевёрнутого списка ко второму, так как мы явно знаем, как добавлять один элемент к списку и ложиться он будет в начало списка. НО, опять же по комментам, стало понятно, что есть более простое решение рекурсией без добавления реверсивной функции. пока что, решение заключается в том, чтобы рекурсивно откусывать голову от второго списка и строить новый список следующим образом: // remove но output выдаёт наличие лишних скобок: Expected value to be (using ===): \"(3, 4, 5, 8, 3, 2, 9)\" Received: \"((((3, 4, 5, 8), 3), 2), 9)\" реализация через cons выдаёт тоже самое (ну другого результата и ожидать было сложно в силу равносильности конструкций): // remove вопрос, я думаю не в ту сторону (откусывать голову у второго списка и пытаться запихнуть его вконец) или пишу неправильно (мысль правильная, но реализую криво)? ","creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"comments":[{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23109,"body":"> явно проблема с шагом рекурсии\n\nА какая проблема с шагом? Вы помните как выглядит рекурсивная функция для расчёта факториала? Понимаете как она работает? Если есть вопросы, то пишите.","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23142,"body":"код остался неизменным со второго сообщения в топике:\n\n```\n// remove\n```\n\nя пока что в ступоре.","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23140,"body":"А вы покажите мне свои решения. Я покажу вижу только рассуждения. Будем резать на живую раз дела не идут :)","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23194,"body":"```\nif (isEmpty(list1)) {\n return list2;\n }\n if (isEmpty(list2)) {\n return list1;\n }\n```\n\nОтлично. Только второй список у нас никогда не будет пустым, мы ведь к нему добавляем первый. Можно не исправлять, главное чтоб вы это понимали.","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23128,"body":"Ну так что, Сергей, решение найдено? Мыслите по большей части в правильном направлении.","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23210,"body":"тогда какая практика лучше: не писать 3 лишние строки кода или не делать лишние вычисления (не заходить в рекурсию, а сразу выдать результат)?","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23173,"body":"в общем это вынос мозга. получилось вот так:\n\nhttps://ru.hexlet.io/code_reviews/26911\n\nconcat тоже сделал, но засунуть рекурсивный вызов туда, куда его в итоге оказалось нужно засунуть, в голову не придёт, пока не увидишь что-то подобное. мне по крайней мере. потом, когда анализируешь, действительно очень похоже на ту же функцию факториала.","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23138,"body":"по большей? )) а в каком месте логика у меня хромает, не подскажите?","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23101,"body":"Дмитрий, да неужели? )) а как же принцип бритвы оккама - брать самый очевидный ответ? )) \n\nа если серьёзно, я понимаю, что список надо собирать заново внутри функции ))","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23066,"body":"UPD\nНа основе коммента к одному из топиков Александра О. (Александр, как всегда, жжёт)\n\n> Второй вариант: более правильный в контексте нашей практики, когда можно рекурсивно сконструировать итоговый список (на основе обхода элементов первого списка делаем новый (как это делала бы функция copy, упомянутая в описании, и в конце первого списка добавляем к нему сразу весь второй список).\n\nполучилась следующая картина:\n```\n// remove\n```\n\nна что output выдаёт:\n\n```\nArgument must be list, but it was '7'\n```\nчто нам говорит о том, что аргумент должен быть списком, а **head** нам возвращает значение, но не список. \n\nТогда, логически, надо использовать **tail** для последовательного уменьшения первого списка (так как **tail** возвращает список, а не значение), и когда мы дойдём до списка из одного аргумента, надо \"сложить\" **head** этого списка (из одного аргумента) с вторым списком и, по-идее, через рекурсию, будет добавляться каждый раз **head** растущего списка. \n\nТаким образом исходя из вышенаписаного, по моей логике, рекурсия должна выглядеть так:\n```\n// remove\n```\nно такой вариант выдаёт первый список в обратном порядке:\n```\nExpected value to be (using ===):\n \"(7, 4, 5, 8, 3, 2, 9)\"\n Received:\n \"(8, 5, 4, 7, 3, 2, 9)\"\n```\n\nИтого, я хожу по кругу пол дня :)","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23211,"body":"Это зависит от ситуации. Если в коде ресурсоёмкие вычисления, то лучше поставить Guard Expression, если нет (как в нашем случае), то упростить. Поэтому я вам и сказал, что можно это не исправлять, решение остаётся за вами.","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23107,"body":"пока нет, хотелось бы прочитать комментарии Кирилла Мокевнина и/или Александра О.\n\nявно проблема с шагом рекурсии, так как терминальный случай это при любом раскладе добраться до пустого списка (одного из) и вернуть второй.","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23105,"body":"Так что, получилось?","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23110,"body":"давайте попробую описать, как выполняется рекурсивная функция факториала, а Вы меня поправите, если я что-то неправильно опишу.\n\nВыглядеть она будет следующим образом:\n\n```\nconst fact = (n) => {\n if (n === 2) {\n return n;\n }\n return n * fact(n - 1);\n};\n\nfact(4);\n```\nтеперь что она делает:\n\n1. проверили на равенство (4 === 2);\n2. так как равенство ложно перешли к возврату функции и уменьшили n до 3;\n3. проверили на равенство (3 === 2);\n4. так как равенство ложно перешли к возврату функции и уменьшили n до 2;\n5. проверили на равенство (2 === 2);\nтут начинаются отложенные вычесления\n6. вернули 2 и умножили на 3, получилось 6;\n7. вернули 6 и умножили на 4, получилось 24;\n8. вернули 24.\n\nТо есть, мы дошли до терминального условия (в задаче **concat** это будет пустой список) и из-за отложенности вычислений начали возвращать значения как бы с конца. \n\nВ задаче **concat** по логике мы должны начать присоединять голову первого списка начиная с момента, когда он примет вид списка с одним аргументом (l(n) или cons(n, null) если хотите) и за счет отложенных вычислений присоединять голову списка к второму списку (второй список остаётся целым) пока он (первый список) растёт обратно на каждой итерации.","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23099,"body":"> const copy = (list) => list;\n\nО нет, так дело не пойдёт. Вам надо сделать `copy` из каждого элемента списка. Для этого придётся использовать импортированные функции: `isEmpty` (для проверки на пустоту списка), `cons` (для создания нового списка), `head` (для обращения к первому элементу списка) и рекурсивно это применять к оставшимся элементам при помощи `tail`. Весь алгоритм я вам уже рассказал, осталось сделать :)","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23218,"body":"ок, спасибо в любом случае.","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23209,"body":"> А если на входе второй список пустой\n\nА какая разница? Мы же ко второму прибавляем элементы первого, а не наоборот. Поэтому второй и не проверяется.\n\n> Если проверка на пустой второй список есть, он сразу вернёт первый список и не будет заходить в рекурсию\n\nВсё так, поэтому ваша функция работает и проходит тесты. Это уже гуд. Просто можно сократить кол-во лишнего кода.","topic_id":11087},{"creator":{"public_name":"Дмитрий Храпонов","id":145876,"is_tutor":false},"id":23143,"body":"Вам надо написать для начала `copy`. Попробуйте это сделать с учётом всего того о чём мы говорили в этом топике. Всё что получится в итоге пишите сюда, будет анализировать.","topic_id":11087},{"creator":{"public_name":"Sergey Ponomarev","id":150864,"is_tutor":false},"id":23198,"body":"вот как раз этот момент у меня непонимание вызывал, видел что проверка на пустой второй список не идёт в решении учителя. А если на входе второй список пустой, как в тесте:\n\n```\nexpect(listToString(concat(numbers, l()))).toBe('(7, 4, 5, 8)');\n```\n\nЕсли проверка на пустой второй список есть, он сразу вернёт первый список и не будет заходить в рекурсию. Если проверки нет, рекурсия соберёт первый список заново. \n\nЯ правильно понимаю?","topic_id":11087}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":4873,"title":"здравствуйте, подскажите пожалуйста\n\nесли опустить требование задания по реализации функции _append_\nчерез рекурсивный процесс, то логично же напрашивается следующее решение\n\n```\nexport const append = (list, list2) => {\n let i = cons (list, list2);\n return toString(i);\n};\n```\nно несмотря на то что \n```\n console.log(toString(list)); //(3, 4, 5, 8)\n console.log(toString(list2)); //(3, 2, 9)\n```\n\nитог выглядит\n```\n console.log(toString(i)); //((3, 4, 5, 8), 3, 2, 9)\n```\n\nоткуда берутся скобки для (3, 4, 5, 8) и почему их нет для 3, 2, 9?","plain_title":"здравствуйте, подскажите пожалуйста если опустить требование задания по реализации функции append через рекурсивный процесс, то логично же напрашивается следующее решение export const append = (list, list2) => { let i = cons (list, list2); return toString(i); }; но несмотря на то что console.log(toString(list)); //(3, 4, 5, 8) console.log(toString(list2)); //(3, 2, 9) итог выглядит console.log(toString(i)); //((3, 4, 5, 8), 3, 2, 9) откуда берутся скобки для (3, 4, 5, 8) и почему их нет для 3, 2, 9? ","creator":{"public_name":"Евгений Радзивончик","id":115768,"is_tutor":false},"comments":[{"creator":{"public_name":"Роман Лахтадыр","id":103858,"is_tutor":false},"id":8476,"body":"списки, построенные на парах состоят из пар, в который первый элемент пары - это значение, а второй - следующая пара, являющаяся следующим элементом списка. в своем примере вы создаете пару из двух списков. так как первый элемент пары в списке - это значение, то и приводится он к строке как отдельный элемент. в итоге, вы получили список, элементами которого являются список (3, 4, 5, 8), и элементы 3, 2, 9.\n\nсоветую изучить внимательно как из пар строятся списки.","topic_id":4873},{"creator":{"public_name":"Роман Лахтадыр","id":103858,"is_tutor":false},"id":8503,"body":"на видео с 5:28 идет объяснение структуры списков. составные данные, в первую очередь, это и есть полезная нагрузка. и любая существующая структура создана для хранения данных, иначе в ней небыло бы никакого смысла.","topic_id":4873},{"creator":{"public_name":"Евгений Радзивончик","id":115768,"is_tutor":false},"id":8482,"body":"\n\n> списки, построенные на парах состоят из пар, в который первый элемент пары - это значение, а второй - следующая пара, являющаяся следующим элементом списка.\n\nиз видео к уроку (да и ранее из курса где начали изучать пару), точнее с 4:00 минуты нет никакой информации о том что на первый или второй элемент пары при конструировании наложена какие-то дополнительная \"нагрузка\" в виде значение или элемента списка\n\nранее из того же видео (1:00 и 8:20) подана суть из которой я понимаю, что первый, что второй элемент пары может быть или какой-то другой парой либо элементом\n\n> советую изучить внимательно как из пар строятся списки.\n\nНо вместе с тем что то есть в Ваших словах, возможно в уроке не все раскрыто. \nПри написании топика, в части оформления представленного мною фрагмента кода (</> Code)\n\nпочему то **list** такой, а лист list2 такой\nтем самым намекая, что \n> списки, построенные на парах состоят из пар, в который первый элемент пары - это значение, а второй - следующая пара\n\nи\nописывая функцию _**reverse**_ из задания я использовал\n ```\nnewList = cons (head(listCount), newList);\n```\nи это работало для накопления в обратном порядке\n\nдумаю, для **_append_**, ну вот же есть часть решения, поставлю в прямом\n```\n newList = cons (newList, head(listCount));\n```\nне хочет накапливать, выдает ошибку _**TypeError: pair is not a function**_","topic_id":4873},{"creator":{"public_name":"Евгений Радзивончик","id":115768,"is_tutor":false},"id":8506,"body":"> на видео с 5:28 идет объяснение структуры списков.\n\nэто один из вариантов \n\n> \"самый простой, но не единственный\"\n\nбыл бы единственным, то это все бы объяснило\n\n> составные данные, в первую очередь,\n\n\nвот насколько я понял, как раз наоборот, второй элемент это пара\n\nтут наверное надо копать глубже, как именно реализована пара","topic_id":4873}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":457,"slug":"js_sequences_list_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"Все создаваемые функции, в рамках этого задания, должны быть реализованы независимо друг от друга, то есть их нельзя использовать для реализации друг друга.\n\n## list.js\n\nРеализуйте и экспортируйте функцию `has()`, которая проверяет, является ли переданное значение элементом списка.\n\n```javascript\nconst numbers = l(3, 4, 5, 8);\nhas(numbers, 8); // true\nhas(numbers, 0); // false\n```\n\nРеализуйте и экспортируйте функцию `reverse()`, которая переворачивает список, используя итеративный процесс.\n\n```javascript\nconst numbers = l(3, 4, 5);\nreverse(numbers); // (5, 4, 3)\n```\n\nРеализуйте и экспортируйте функцию `concat()`, которая соединяет два списка, используя рекурсивный процесс (попробуйте сначала представить, как работала бы функция `copy()`, которая принимает на вход список и возвращает его копию).\n\n```javascript\nconst numbers = l(3, 4, 5, 8);\nconst numbers2 = l(3, 2, 9);\nconcat(numbers, numbers2); // (3, 4, 5, 8, 3, 2, 9)\n```\n","prepared_readme":"Все создаваемые функции, в рамках этого задания, должны быть реализованы независимо друг от друга, то есть их нельзя использовать для реализации друг друга.\n\n## list.js\n\nРеализуйте и экспортируйте функцию `has()`, которая проверяет, является ли переданное значение элементом списка.\n\n```javascript\nconst numbers = l(3, 4, 5, 8);\nhas(numbers, 8); // true\nhas(numbers, 0); // false\n```\n\nРеализуйте и экспортируйте функцию `reverse()`, которая переворачивает список, используя итеративный процесс.\n\n```javascript\nconst numbers = l(3, 4, 5);\nreverse(numbers); // (5, 4, 3)\n```\n\nРеализуйте и экспортируйте функцию `concat()`, которая соединяет два списка, используя рекурсивный процесс (попробуйте сначала представить, как работала бы функция `copy()`, которая принимает на вход список и возвращает его копию).\n\n```javascript\nconst numbers = l(3, 4, 5, 8);\nconst numbers2 = l(3, 2, 9);\nconcat(numbers, numbers2); // (3, 4, 5, 8, 3, 2, 9)\n```\n","has_solution":true,"entity_name":"Представление последовательностей"},"units":[{"id":1352,"name":"theory","url":"/courses/sequences/lessons/list/theory_unit"},{"id":1353,"name":"quiz","url":"/courses/sequences/lessons/list/quiz_unit"},{"id":1354,"name":"exercise","url":"/courses/sequences/lessons/list/exercise_unit"}],"links":[],"ordered_units":[{"id":1352,"name":"theory","url":"/courses/sequences/lessons/list/theory_unit"},{"id":1353,"name":"quiz","url":"/courses/sequences/lessons/list/quiz_unit"},{"id":1354,"name":"exercise","url":"/courses/sequences/lessons/list/exercise_unit"}],"id":687,"slug":"list","state":"approved","name":"Представление последовательностей","course_order":200,"goal":"Вспоминаем пары (pairs) и рассматриваем представление списков с помощью пар","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"170597337","theory":"## Пары\n\nНаше погружение в последовательности мы начнём с того, что проработаем уровень абстракции для работы со списками. Давайте вспомним, как работают пары. С помощью пар, а конкретно функции-конструктора `cons`, мы можем соединять, например, числа:\n\n```javascript\nimport { cons, toString } from 'hexlet-pairs'\n\ncons(5, 8)\n\nconst pair = cons(5, cons(2, 9))\n```\n\nТакже можем соединять любые объекты данных, включая те же пары. В данном примере мы соединяем 5 как число и другую пару. Если мы распечатаем, то увидим, что у пары такое представление:\n\n\n```javascript\ntoString(pair) // (5, (2, 9))\n```\n\n## Способы соединить 1, 2, 3 и 4\n\n\n\nСуществует более одного способа соединять различные числа, т.е. представлять их разными структурами данных. Например, числа 1, 2, 3, 4 можно соединить в единую иерархическую конструкцию, как на примере слева на так называемой стрелочной диаграмме. Мы видим, что в паре на самом верху левый элемент является парой, правый — тоже пара и в каждой из этих пар элементы представлены конкретными числами.\n\nПри этом может быть и совершенно другой вариант, в котором левый элемент является парой, а правый, например, является числом 4. Левый элемент в свою очередь делится на элемент, в котором есть число и элемент, являющийся парой и т.д. Мы можем это делать бесконечно много раз.\n\nПод диаграммами описано представление в виде кода с помощью вложенных `cons`. Причём слева симметричное вложение, а справа немного сдвинутое.\n\nТо, что мы получили, называется — иерархической структурой. Её создание обеспечивается возможностью, которая называется — **свойство замыкания (множества)**. Чтобы понять, что это обозначает, давайте познакомимся с понятием множества.\n\n## Что такое множество\n\n\n\nМножество — это довольно интуитивное понятие. Для математики оно является одним из ключевых. Через множество определено большое количество вещей. Определение чистых математических функций, с которыми мы работаем, полностью построено на теоретико-множественном подходе. Так вот, что такое множество? Множество — это какой-то набор. Мы можем сказать, что это совокупность объектов данных. Например, в данном случае мы видим, что это различные типы чисел. При этом множества вложены друг в друга, т.е. у множеств есть отношения друг с другом. Существуют операции над множествами, например: пересечение, объединение и т.д. Причём это не просто какая-то математическая теория. Множества очень активно используются в практике программирования. Те же самые базы данных [SQL](https://en.wikipedia.org/wiki/SQL) основаны на реляционной алгебре, которая очень тесно связанна с теорией множеств. Например, \"джойны\", которые используются в SQL следует воспринимать и анализировать именно на основе понимания теории множеств.\n\nПоскольку мы говорим о парах, мы можем себе представить некоторое множество — **множество всех пар**. Далее нам это понадобится.\n\n## Замыкание (абстрактная алгебра)\n\nЭто не то замыкание, про которое мы уже говорили, связанное с запоминанием контекста. Это замыкание из математики, из абстрактной алгебры и звучит оно примерно так:\n\n_Множество замкнуто относительно операции, если применение операции к элементам этого множества даёт результат, который также является элементом этого множества._\n\nВозможно, это слишком формальное определение, которое звучит очень абстрактно. На самом деле оно крайне простое. Всё, что оно означает: используя функцию `cons`, множество всех пар замкнуто относительно функции `cons`. Почему? Потому что когда вы с помощью функции `cons` что-либо объединяете, вы снова получаете пару. Если полученную пару объединить функцией `cons` в ещё одну пару, получится, опять же, пара. Но как мы помним, множество всех пар включает все возможные виды пар. Какую бы пару вы ни создали, она будет туда включена, таким образом функция `cons` всегда порождает пару. Если бы в какой-то момент она порождала что-то другое, то не существовало бы замкнутости. Замкнутость присутствует именно потому, что применение этой функции к абсолютно любым элементам данного множества порождает элемент этого же множества. А свойство замыкания является ключевым фактором в возможности построения иерархических структур.\n\nИерархические структуры — это такие структуры, части которых содержат в себе ещё более элементарные части, которые в свою очередь содержат ещё более элементарные части.\n\n```javascript\ncons(cons(3, 5), cons(2, 9))\n```\n\nВ примере выше `cons` соединяет не элементарные части. Она соединяет пары, которые в свою очередь состоят из частей и так далее. Это может происходить бесконечно, благодаря свойству замыкания.\n\n## Последовательности\n\n\n\nКак же всё-таки проще всего представить последовательности? Мы используем стрелочную диаграмму, которая показывает самый простой, но не единственный способ представления последовательностей. На верхнем уровне у нас находится пара, первый элемент которой объект данных, например, число, второй элемент — это ссылка на другую пару, в которой опять же первый элемент — объект данных и дальше опять ссылка на следующую пару. И так далее до самой последней вложенной пары, где первый элемент — данные, а второй элемент - специальный маркер, который обозначает, что список закончился, и дальше смотреть не нужно.\n\nКак это записывается кодом? По сути — это последовательность cons'ов, которые вложены друг в друга.\n\n```javascript\ncons(1,\n cons(2,\n cons(3,\n cons(4, null))))\n```\n\nМы приняли за аксиоматический факт, что у последнего элемента маркер окончания списка это `null`. Если мы его встречаем значит дальше можно не смотреть, список закончился.\n\n## Интерфейс\n\n```javascript\nimport {\n l, cons, head, tail, isEmpty, toString,\n} from 'hexlet-pairs-data'\n\n// cons(1, cons(2, cons(3, cons(4, null))));\nconst list = l(1, 2, 3, 4)\n\ncons(10, list) // (10, 1, 2, 3, 4)\ntoString(list) // (1, 2, 3, 4)\n\nhead(list) // 1\ntail(list) // l(2, 3, 4)\n\n// list === null\nisEmpty(l(4)) // false\nisEmpty(l()) // true\n```\n\nДавайте рассмотрим интерфейс работы со списками. Начиная с текущего момента и на протяжении всего курса, мы будем постоянно с ними работать, строить поверх них абстракции. Поэтому нам нужно научиться выполнять какие-то базовые операции со списками и построить данный слой абстракции поверх пар.\n\nЕсли бы мы действовали в лоб, то создание списка выглядело бы так:\n\n```javascript\ncons(1, cons(2, cons(3, cons(4, null))))\n```\n\nНо как видите, это довольно громоздкая запись, здесь слишком много \"шума\" и сложно увидеть реальную структуру списка, а ведь список состоит всего из четырёх чисел. Поэтому мы сделали функцию-конструктор для создания списков:\n\n```javascript\nconst list = l(1, 2, 3, 4)\n```\n\nЭта функция принимает на вход числа через запятую и возвращает список. Т.е. внутри она делает то же самое, что описывает код выше. Функция `l` работает с переменным числом аргументов (оно не задано жёстко и вы можете передавать любое количество). Внутри она имеет кое-какие особенности, с которыми мы ещё не знакомы и чтобы понять её устройство нужно продвинуться вперёд гораздо дальше. Поэтому просто используйте данную функцию, как чёрный ящик, представляя, что вызывая конструктор `l(1, ...)` вы получаете запись `cons(1, ...)`.\n\nПочему мы сделали именно так и назвали конструктор `l` одной буквой? Всё очень просто. Потому что мы часто будем строить вложенные списки и это очень просто делать, если у вас короткое название функции. Кстати, если бы оно было длинное, то вложенные функции банально привели бы к распуханию этой структуры. При этом для добавления элемента в список мы используем функцию `cons`. Но эта функция будет определена уже в списках, т.е. она не является функцией пар, так как это другой слой абстракции. Если мы посмотрим на внутреннюю структуру, то увидим следующее:\n\n```javascript\ncons(10, list) // (10, 1, 2, 3, 4)\n```\n\nЕсли распечатать список, то можно увидеть, что десятки здесь уже нет:\n\n```javascript\ntoString(list) // (1, 2, 3, 4)\n```\n\nЭто означает, что у нас присутствует неизменяемость. Добавление в список, как это было с парами, не изменяет его, а возвращает новый список. Поэтому, если вы хотите его использовать, то необходимо создать константу, например, `list2`.\n\nИ ещё несколько базовых операций, которые обязательно будут нужны для работы со списками. Они очень похожи на `car` и `cdr`. Первая из них — это `head`, так называемая голова. Данная функция берёт из списка первый элемент. Когда идёт работа со списками, существует понятийный аппарат, в котором есть и данное определение.\n\nПервый элемент списка (т.е. последний добавленный) называется \"голова\", а всё остальное — это хвост. Соответственно, `head` — это взять голову, `tail` — это взять хвост. Хвост — это список минус первый элемент. Мы говорим первый, так как он находится всегда слева, но по сути это то, что добавляется в список последним. В данном случае `tail(list)`, учитывая, что наш список был `l(1, 2, 3, 4)`, это список `l(2, 3, 4)`. Если мы будем применять к списку `tail` много раз, то постепенно будем получать список меньшего размера, пока в конце концов не получим `null`.\n\nЕщё одна функция — это `isEmpty`. Она проверяет пустой ли список, т.е. внутри она делает такую проверку:\n\n```javascript\n// list === null\nisEmpty(l(4)) // false\nisEmpty(l()) // true\n```\n\nВидно, что если мы передаём просто конструктор, вызванный без параметров, то `isEmpty` считает, что это пустой список. Если хотя бы один параметр был передан, то список уже не пустой.\n\nПочему мы не делаем такую проверку `list === null`, а вводим новую функцию? Думаю вам уже должно быть понятно. Мы ведь всё время говорим о том, что необходимо использовать абстракцию. То, что внутри используется `null` — это всего лишь особенность реализации. Сегодня `null`, завтра что-то ещё. И если вы используете те функции, которые предоставляет библиотека, вам не придётся в будущем переписывать весь код.\n\n## Повышаем уровень абстракции\n\nВ этом уроке мы реализовали [абстрактный тип данных](https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)) **Список** на основе ранее пройденных **пар**. Ключевой особенностью списка является его интерфейс, а именно функции, реализующие такие операции, как \"получить голову\" (возвращает первый элемент списка), \"получить хвост\" (возвращает новый список, полученный из исходного списка отсечением у него первого элемента), \"добавить новую голову\" (добавление элемента в начало списка). И здесь следует обратить внимание на несколько вещей.\n\nВо-первых, не стоит отождествлять между собой пары и списки. Пары в данном случае были использованы как подходящий _инструмент_ для создания списка. **Абстрактный список** (см. первый абзац) может быть реализован в **конкретных структурах данных**. В нашем случае это [односвязный список](https://ru.wikipedia.org/wiki/Связный_список#.D0.9E.D0.B4.D0.BD.D0.BE.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA_.28.D0.BE.D0.B4.D0.BD.D0.BE.D0.BD.D0.B0.D0.BF.D1.80.D0.B0.D0.B2.D0.BB.D0.B5.D0.BD.D0.BD.D1.8B.D0.B9_.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA.29). Важной характеристикой этой структуры данных является то, что каждый элемент списка, помимо определённого хранимого значения (число, строка, дата, адрес, имя и любая другая информация), содержит ссылку на другой такой же по структуре элемент. Таким образом, между элементами списка существует **связь**, и мы можем последовательно \"путешествовать\" (перемещаться) от текущего элемента к следующему, от начала списка к его **концу**.\n\nИ, как видим, пары вполне позволяют реализовать особенности списка как структуры данных. Ведь пара, помимо основного значения, может содержать ещё другую пару аналогичной структуры (значение + ссылка на другую пару) — и такая последовательность может длиться бесконечно.\n\n```javascript\ncons(1, cons(2, cons(3, cons(4, cons(5, ...\ncons('one', cons('two', cons('three', cons('four', cons('five', ...\n```\n\nОднако, надо понимать, что сделать это можно не только с помощью пар. Cписки можно представить на основе массивов, а также некоторых других типов данных. Поэтому, если мы пользуемся **списком**, то к нему нельзя напрямую применять функции по работе с парами даже если мы знаем, что данный конкретный список состоит из пар. Для этого вводятся специальные функции для работы со списками, учитывающие его внутреннюю реализацию, которая может изменяться.\n\nВо-вторых, коль скоро мы реализовали список именно на основе **пар**, то следует понимать, что не каждая пара, с которой мы имеем дело, какой бы «сложной» она ни была, является списком. У списка есть начало и конец, и мы должны иметь возможность последовательно перемещаться от одного элемента к другому. Для того, чтобы понять, что мы достигли последнего элемента списка и перемещаться дальше уже нет смысла, мы вводим специальный **маркер конца списка** — пустую пару `l()`. По предварительному соглашению в роли маркера могут также выступать другие подходящие значения, например, `null`. Как только мы встречаем очередную пару, в `cdr` которой находится пустая пара, это означает, что очередное значение списка, лежащее в `car` пары, является **последним элементом списка**.\nОтметим также, что у пустого списка (списка, в котором нет ни одного элемента) не может быть ни головы, ни хвоста. Поэтому соответствующие операции, примененные к пустому списку (взятие `head` или `tail`), приводят к возникновению ошибки. Чтобы её избежать, вводят функцию для проверки списка на «пустоту» (например, `isEmpty`), а далее берут голову или хвост у списка, предварительно убедившись, что он не является пустым.\n\nНиже приведены примеры пар, которые нельзя назвать списками. Они не реализуют требования, предъявляемые к односвязным спискам: в них отсутствует последовательная связь между элементами и/или нет маркера конца списка:\n\n```javascript\ncons(1, cons(cons(3, null), 2))\ncons(1, cons(2, cons(3, 4)))\ncons(cons(1, 2), cons(3, cons(4, null)))\n```\n\nТеперь покажем пары, которыми мы можем смело пользоваться как списками:\n\n```javascript\ncons(1, cons(2, cons(3, null)))\ncons('This', cons('is', cons('a', cons('list', null))))\n```\n\nВ уроке мы отметили, что следующие две записи, конструирующие список, являются эквивалентными:\n\n```javascript\ncons(1, cons(2, cons(3, cons(4, cons(5, null))))) // (1, 2, 3, 4, 5)\nl(1, 2, 3, 4, 5) // (1, 2, 3, 4, 5)\n```\n\nДля генерации списка мы создали функцию `l`, несмотря на то, что у нас уже есть конструктор `cons`. Это необходимо по нескольким причинам. Отметим наиболее важные из них:\n\n- Выделив код создания списка в отдельную функцию и дав ей имя `l` (сокращ. от `list`), мы ввели отдельную и понятную абстракцию, отвечающую за создание **списков** (и ничего другого!). Выше говорилось, что списки могут быть реализованы не только на парах, но и на массивах, а также других типах данных. Так вот, наша абстракция `l` позволяет отвлечься от внутреннего устройства списка. Тот, кто будет пользоваться этой функцией, не обязан знать, какими способами она конструирует список, а в случае необходимости может изменить его внутреннее устройство \"незаметно и безболезненно\" для пользователей `l`.\n- Хорошая абстракция делает код более понятным и повышает его читабельность. Теперь, видя в коде `l`, мы однозначно понимаем, что здесь происходит. Тогда как в ином случае, цепляясь глазом за множественные, разбросанные среди строк `cons`, мы каждый раз должны будем определять, что же делает данная конкретная пара: то ли создаёт список, то ли хранит временные данные, то ли печёт пирожки ?! ;)\n\nПосмотрите на «безликие» нагромождения **пар**, создающие разные сущности, которые мы проходили в прошлых курсах. Только комментарии помогают понять, что здесь происходит.\n\n```javascript\ncons(5, cons(2, cons(7, cons(11, cons(6, cons(14, null)))))) // создание списка\ncons(3, 17) // создание точки\ncons(8, 17) // создание рационального числа\ncons(cons(3, 33), cons(-2, -22)) // создание отрезка\nconst rectangle = cons(cons(-2, 23), cons(5, 11)) // создание прямоугольника\n2 * (car(cdr(rectangle)) + cdr(cdr(rectangle))) // вычисление периметра прямоугольника\n```\n\nА вот как выглядит код с введёнными абстракциями. Комментарии здесь уже не нужны:\n\n```javascript\nlist(5, 2, 7, 11, 6, 14)\nmakePoint(3, 17)\nmakeRational(8, 17)\nmakeSegment(makePoint(3, 33), makePoint(-2, -22))\nconst point = makePoint(-2, 23)\nconst rectangle = makeRectangle(point, 5, 11)\nperimeter(rectangle)\n```\n\nЕстественно, введение функции устраняет дублирование кода. Каждый раз писать при создании списка такое нагромождение кода в виде вложенных `cons`, демонстрируя при этом все внутренности создаваемой конструкции, — не лучший стиль написания кода.\n\n```javascript\nconst { l, head, tail } = require('@hexlet/pairs-data')\n\nconst list = l(5, 3, 9)\nconsole.log(head(tail(list))) // => 3\n```\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1351,"name":"theory","url":"/courses/sequences/lessons/intro/theory_unit"}],"links":[{"id":425614,"name":"Документация по функциям для работы с парами js-pairs","url":"https://github.com/hexlet-components/js-pairs/tree/main/docs"}],"ordered_units":[{"id":1351,"name":"theory","url":"/courses/sequences/lessons/intro/theory_unit"}],"id":686,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом и проектом «Генератор HTML», который будет постепенно разрабатываться в течение всего курса","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"170597336","theory":"Этот курс — логическое продолжение предыдущего курса про [Составные данные](https://ru.hexlet.io/courses/compound_data). Теперь мы будем говорить о составных данных в чуть более сложном и продвинутом их виде. Как это часто бывает на Хекслете, данный курс будет не про JavaScript и не про изучение его возможностей, а про некоторые фундаментальные вещи, которые обязан знать программист, не зависимо от того, на каком языке он пишет код. JavaScript в данном случае всего лишь способ выражения той идеи, которую мы хотим донести.\n\n## Что такое последовательности\n\n\n\nПоследовательность — это упорядоченная совокупность объектов данных.\n\nДавайте разберём это определение. Оно звучит немного страшновато, но в реальности всё очень просто. Под совокупностью подразумевается некая единая сущность, а под объектами данных подразумевается всё, что угодно. В нашем случае, в программировании, это могут быть: числа, строки, составные объекты (например, пары). В математике последовательности представлены очень широко. Этому посвящены целые её разделы, например, матанализ, который изучает различные числовые последовательности: натуральные числа или числа Фибоначчи, с которыми мы так или иначе имеем дело когда учимся программированию. В реальной жизни с ними никто не сталкивается, но их любят использовать при обучении.\n\n- Натуральные числа\n\n```\n1, 2, 3, 4, 5, 6, 7, 8, ...\n```\n\n- Числа Фибоначчи\n\n```\n0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n```\n\n## Списки\n\n- список файлов\n- список дел\n- список сотрудников\n- список сайтов\n- список списков :)\n\nВ программировании гораздо чаще, чем с числовыми последовательностями, мы будем иметь дело со списками. Списки являются практически центральной частью в любой системе. Например, в функциональных языках манипуляции со списками являются ключевым моментом при написании кода.\n\nКакие бывают списки? Если на компьютере вы открываете папку, то видите список файлов. У вас где-то записан список дел, более того, вы можете их вести в онлайновых системах. На предприятии есть список сотрудников. А если вы ищете в Яндекс, Google или другом поисковике, то видите список сайтов с пейджингом (переключатель, который позволяет вам ходить по страницам одного большого списка). И даже такой мета-список, который вы видите сейчас перед собой в данный момент — список списков. Это тоже список. Так что списки можно комбинировать, делать из них новые списки и это очень напоминает то, что мы делали в курсе Составные данные.\n\nВ этом курсе мы будем разрабатывать проект, который называется **Генератор HTML**. Это библиотека, генерирующая части HTML-кода. Если на текущий момент вы не знакомы с HTML, то не стоит переживать, потому что в будущих уроках мы будем обязательно разбирать, что это такое, как он работает, для чего нужен и что такое языки разметки вообще. Это крайне простая тема, на освоение которой не уйдет много времени.\n\n## Принцип работы\n\nПринцип работы нашего проекта показан в примере ниже:\n\n```javascript\nimport {\n make, append, addChild, toString, node,\n} from '@hexlet/html-tags'\n\nconst ul1 = node('ul')\nconst ul2 = addChild(ul1, node('li', 'hello'))\nconst ul3 = addChild(ul2, node('li', 'world'))\n\nconst html1 = make()\nconst html2 = append(html1, ul3)\n```\n\nСейчас не обязательно пытаться полностью понять данный код. Он иллюстрирует, что в конечном итоге получится библиотека, в которой есть обычные функции, позволяющие нам строить древовидную структуру. Которая с одной стороны список,а с другой может быть сложней, чем список. Например, она может быть деревом, о чём будет рассказано позже. В конечном итоге после того, как мы создали эту структуру, мы можем её распечатать и увидеть, что генерируется такое представление:\n\n```javascript\ntoString(html2)\n// <ul>\n// <li>hello</li>\n// <li>world</li>\n// </ul>\n```\n\nЭтот кусочек и есть часть HTML-кода.\n\n## Применение\n\n- программная (динамическая) генерация\n- манипуляции\n- анализ (например, проверка корректности)\n- внутреннее представление в браузере\n\n## Зачем такие библиотеки вообще нужны?\n\n1. Программная (динамическая) генерация HTML очень востребованная операция. Существует большое количество сайтов, которые используют, например, технологии [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application), когда не происходит перезагрузки страницы, а всё меняется прямо на самом сайте. HTML в таком случае действительно генерируется динамически и в итоге представление перерисовывается непосредственно на клиенте.\n\n2. Кроме этого такие библиотеки позволяют манипулировать текущим HTML, который уже загружен и используется. Собственно сайты это постоянно и делают, когда вы закрываете какое-то окно, что-то открываете или меняете на странице.\n\n3. Помимо самого использования есть и анализ, например, валидация или проверка корректности. Верификация — когда проверяется правильно ли создана структура, так как HTML обладает определёнными правилами построения и их можно нарушить. То есть когда в браузере идёт работа с HTML, можно создать HTML, который будет невалиден — не пройдет верификацию. Библиотеки позволяют анализировать и давать рекомендации о том, как лучше делать, а как лучше не делать.\n\n4. И самое интересное, что в реальности именно так внутри браузера и представлен HTML. Если мы загрузим какую-то страницу и посмотрим её исходный код, мы увидим, что там есть HTML как текст. Но текст — это всего лишь текстовое представление HTML, а в реальности он загружается в виде некоторой программной сущности. У неё есть специальное название — [DOM](https://en.wikipedia.org/wiki/Document_Object_Model). Модель построения элементов на веб-странице. Она хранится где-то внутри и ею можно манипулировать\n\n## Document Object Model\n\n```javascript\nconst impl = document.implementation\nlet doc = impl.createDocument('', '', null)\nlet peopleElem = doc.createDocument('people')\n\nlet personElem1 = doc.createDocument('personal')\npersonElem1.setAttribute('first-name', 'eric')\n\nlet addressElem = doc.createDocument('address')\naddressElem.setAttribute('street', '321 south st')\npersonElem1.appendChild(addressElem)\n\npeopleElem.apendChild(personElem1)\ndoc.appendChild(peopleElem)\n```\n\nЗдесь как раз показан пример того, как в браузере происходит манипуляция DOM, т.е. реальным представлением HTML, как он представлен в программном коде. Опять же, не нужно рассчитывать на то, что вы сейчас поймёте этот код, тем более, что он содержит некоторые новые аспекты. Важно, что та библиотека, которую мы разрабатываем, является по сути локальным представлением того, что происходит в браузере. Зная принципы её работы, умея самостоятельно её написать, вы будете легко ориентироваться в том, как это происходит в браузере. По сути мы делаем прототип настоящего DOM. Конечно, сложность очень сильно отличается, потому что DOM в браузере очень навороченная вещь. Наша библиотека гораздо проще, но главное это понять общий принцип.\n\n## Темы\n\n- списки, множества, деревья\n- отображение, фильтрация, агрегация\n- стандартные интерфейсы\n- уровневое проектирования\n\n### Какие темы мы рассмотрим в этом курсе?\n\nИх много, и они достаточно серьёзные.\n\n1. Первое — это структуры данных. Мы рассмотрим списки, познакомимся с понятием множеств и с деревьями.\n\n2. Мы научимся с ними работать посредством тройки функций: отображение (`map`), фильтрация (`filter`) и агрегация (`reduce`). Это тройка методов, которые используются для обработки различных списков, множеств, деревьев и других структур данных, которые существуют в JavaScript. Когда в будущем мы будем работать с внутренними структурами языка, вы увидите, что это основные способы манипуляции структурами в JS и, кстати, во всех функциональных языках и даже не в функциональных. Во всех достаточно продвинутых языках программирования, которые поддерживают функции высшего порядка, реализована эта тройка. А код с использованием данных методов является чаще всего каноническим, т.е. так принято и правильно писать, а не использовать, например, циклы, о чём мы позже обязательно поговорим.\n\n3. Мы познакомимся с таким подходом, как стандартные интерфейсы. Ярким примером данного подхода является конструктор для детей Lego.\n\n4. Также мы познакомимся с уровневым проектированием, которое касается не только программирования, а всей инженерной технической области. Именно оттуда оно берет начало и именно оттуда уровневому проектированию можно учиться.\n\n## Превосходство Хекслета\n\n- функциональный стиль и неизменяемость\n- СИКП\n- фокус на программировании, а не синтаксисе языка\n- двигаемся вперед не с помощью изучения новых фич языка, а путем комбинирования изученных инструментов и развития абстрактного мышления\n- нет нового синтаксиса\n\nНапоследок, несколько аспектов, почему курс сделан именно так. Почему и зачем мы освещаем эту тему на Хекслете.\n\nЭтот курс продолжает традицию предыдущего курса. Всё, что мы здесь делаем, будет неизменяемым. Мы используем функциональный стиль. Потому что введение состояния привносит множество сложностей и проблем, и на данном этапе для понимания темы это совершенно не нужно. Поэтому мы оставляем за скобками изменение. Библиотека может показаться вам немного странной, но чуть позже вы поймёте, почему она реализована именно так.\n\nПомимо этого курс продолжает традицию и также основан на СИКПе ([Структура и интерпретация компьютерных программ](https://web.mit.edu/6.001/6.037/sicp.pdf)). В нём отсутствует новый синтаксис. Мы в очередной раз подчёркиваем, что этот курс не про изучение JavaScript, он про изучение программирования. Пройдя его, вы действительно поймёте и, наконец, осознаете разницу между тем, что такое программировать и что такое знать синтаксис языка.\n\nВсё, что происходит в этом курсе — мы просто берём то, что уже изучили, начинаем это комбинировать и получаем какие-то новые возможности нашей системы, более сложное поведение. Данный подход развивает абстрактное мышление. Многие вещи, которые мы делаем в этом курсе, направлены не только на то, чтобы вы понимали, что такое программирование, но и постепенно начинали развивать свой мозг и тренировать его для того, чтобы он мог переваривать всё более и более сложные сущности и концепции, потому что изучение нового синтаксиса не привносит нового развития в части того, как вы размышляете. Оно не позволит вам автоматически строить действительно сложные программы. А вот абстрактное мышление — это как раз та вещь, которая влияет на это больше всего. Некоторые уроки и практические задания могут показаться весьма сложными, потому что в этом курсе будет много кода и он будет заставлять ваш мозг кипеть. В каком-то смысле это лакмусовая бумажка. Если вы пройдёте курс, действительно в нём разберётесь и поймёте его, то скорее всего дальнейшее обучение пройдет для вас достаточно легко и, в целом, в программировании у вас всё будет хорошо получаться.\n\nЖелаю удачи! Вперёд!\n"},"id":120,"slug":"sequences","challenges_count":10,"name":"JS: Последовательности","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы продолжите изучать составные данные на более продвинутом уровне и рассмотрите тип данных «список». Вы узнаете больше о функциях высшего порядка filter, map и reduce и иерархических структурах. В итоге вы научитесь строить сложные структуры данных на базе более простых и проектировать функции так, чтобы их можно было легко соединять друг с другом. Составные данные пригодятся, если вы решите работать над проектами, которые требуют обработку сложных структур данных. Знания из этого курса помогают программистам обрабатывать коллекции, представленные списками с помощью функций высшего порядка.","kind":"additional","updated_at":"2026-01-20T11:55:07.138Z","language":"javascript","duration_cache":36000,"skills":["Строить сложные структуры данных на базе более простых","Проектировать функции так чтобы их можно было легко соединять друг с другом","Обрабатывать коллекции представленные списками с помощью функций высшего порядка (map/filter/reduce)","Разделять код на уровни, выстраивая правильное взаимодействие между слоями"],"keywords":["функции высшего порядка","стандартные интерфейсы","уровневое проектирование"],"lessons_count":9,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE0OCwicHVyIjoiYmxvYl9pZCJ9fQ==--d07901b5af81babe7daa5cc3f1853beb9c936160/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/sequences/lessons/list/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","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>Наше погружение в последовательности мы начнём с того, что проработаем уровень абстракции для работы со списками. Давайте вспомним, как работают пары. С помощью пар, а конкретно функции-конструктора <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">import { cons, toString } from 'hexlet-pairs'
cons(5, 8)
const pair = cons(5, cons(2, 9))</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>Также можем соединять любые объекты данных, включая те же пары. В данном примере мы соединяем 5 как число и другую пару. Если мы распечатаем, то увидим, что у пары такое представление:</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">toString(pair) // (5, (2, 9))</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>
<h2 id="heading-2-2">Способы соединить 1, 2, 3 и 4</h2>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE2MiwicHVyIjoiYmxvYl9pZCJ9fQ==--954373c49d6ae402331be175cc4d94b7b337e742/cons.png" alt="Соединение пар" loading="lazy"/></p>
<p>Существует более одного способа соединять различные числа, т.е. представлять их разными структурами данных. Например, числа 1, 2, 3, 4 можно соединить в единую иерархическую конструкцию, как на примере слева на так называемой стрелочной диаграмме. Мы видим, что в паре на самом верху левый элемент является парой, правый — тоже пара и в каждой из этих пар элементы представлены конкретными числами.</p>
<p>При этом может быть и совершенно другой вариант, в котором левый элемент является парой, а правый, например, является числом 4. Левый элемент в свою очередь делится на элемент, в котором есть число и элемент, являющийся парой и т.д. Мы можем это делать бесконечно много раз.</p>
<p>Под диаграммами описано представление в виде кода с помощью вложенных <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>. Причём слева симметричное вложение, а справа немного сдвинутое.</p>
<p>То, что мы получили, называется — иерархической структурой. Её создание обеспечивается возможностью, которая называется — <strong>свойство замыкания (множества)</strong>. Чтобы понять, что это обозначает, давайте познакомимся с понятием множества.</p>
<h2 id="heading-2-3">Что такое множество</h2>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE2MywicHVyIjoiYmxvYl9pZCJ9fQ==--5a3003aa09d3c4156e3c8af0806c0dd795d7d353/numbers.png" alt="Множества чисел" loading="lazy"/></p>
<p>Множество — это довольно интуитивное понятие. Для математики оно является одним из ключевых. Через множество определено большое количество вещей. Определение чистых математических функций, с которыми мы работаем, полностью построено на теоретико-множественном подходе. Так вот, что такое множество? Множество — это какой-то набор. Мы можем сказать, что это совокупность объектов данных. Например, в данном случае мы видим, что это различные типы чисел. При этом множества вложены друг в друга, т.е. у множеств есть отношения друг с другом. Существуют операции над множествами, например: пересечение, объединение и т.д. Причём это не просто какая-то математическая теория. Множества очень активно используются в практике программирования. Те же самые базы данных <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://en.wikipedia.org/wiki/SQL" rel="noopener noreferrer" target="_blank">SQL</a> основаны на реляционной алгебре, которая очень тесно связанна с теорией множеств. Например, "джойны", которые используются в SQL следует воспринимать и анализировать именно на основе понимания теории множеств.</p>
<p>Поскольку мы говорим о парах, мы можем себе представить некоторое множество — <strong>множество всех пар</strong>. Далее нам это понадобится.</p>
<h2 id="heading-2-4">Замыкание (абстрактная алгебра)</h2>
<p>Это не то замыкание, про которое мы уже говорили, связанное с запоминанием контекста. Это замыкание из математики, из абстрактной алгебры и звучит оно примерно так:</p>
<p><em>Множество замкнуто относительно операции, если применение операции к элементам этого множества даёт результат, который также является элементом этого множества.</em></p>
<p>Возможно, это слишком формальное определение, которое звучит очень абстрактно. На самом деле оно крайне простое. Всё, что оно означает: используя функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">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">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">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">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">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">cons(cons(3, 5), cons(2, 9))</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">cons</code> соединяет не элементарные части. Она соединяет пары, которые в свою очередь состоят из частей и так далее. Это может происходить бесконечно, благодаря свойству замыкания.</p>
<h2 id="heading-2-5">Последовательности</h2>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE2NCwicHVyIjoiYmxvYl9pZCJ9fQ==--1c4ac8864be3788e9c2c2da192b8687de99a34d3/sequence.png" alt="Последовательности" loading="lazy"/></p>
<p>Как же всё-таки проще всего представить последовательности? Мы используем стрелочную диаграмму, которая показывает самый простой, но не единственный способ представления последовательностей. На верхнем уровне у нас находится пара, первый элемент которой объект данных, например, число, второй элемент — это ссылка на другую пару, в которой опять же первый элемент — объект данных и дальше опять ссылка на следующую пару. И так далее до самой последней вложенной пары, где первый элемент — данные, а второй элемент - специальный маркер, который обозначает, что список закончился, и дальше смотреть не нужно.</p>
<p>Как это записывается кодом? По сути — это последовательность cons'ов, которые вложены друг в друга.</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">cons(1,
cons(2,
cons(3,
cons(4, null))))</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">null</code>. Если мы его встречаем значит дальше можно не смотреть, список закончился.</p>
<h2 id="heading-2-6">Интерфейс</h2>
<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 {
l, cons, head, tail, isEmpty, toString,
} from 'hexlet-pairs-data'
// cons(1, cons(2, cons(3, cons(4, null))));
const list = l(1, 2, 3, 4)
cons(10, list) // (10, 1, 2, 3, 4)
toString(list) // (1, 2, 3, 4)
head(list) // 1
tail(list) // l(2, 3, 4)
// list === null
isEmpty(l(4)) // false
isEmpty(l()) // true</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>
<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">cons(1, cons(2, cons(3, cons(4, null))))</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 list = l(1, 2, 3, 4)</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">l</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">l(1, ...)</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(1, ...)</code>.</p>
<p>Почему мы сделали именно так и назвали конструктор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</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>
<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">cons(10, list) // (10, 1, 2, 3, 4)</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">toString(list) // (1, 2, 3, 4)</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">list2</code>.</p>
<p>И ещё несколько базовых операций, которые обязательно будут нужны для работы со списками. Они очень похожи на <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">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">head</code>, так называемая голова. Данная функция берёт из списка первый элемент. Когда идёт работа со списками, существует понятийный аппарат, в котором есть и данное определение.</p>
<p>Первый элемент списка (т.е. последний добавленный) называется "голова", а всё остальное — это хвост. Соответственно, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">head</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">tail</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">tail(list)</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">l(1, 2, 3, 4)</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">l(2, 3, 4)</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">tail</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">null</code>.</p>
<p>Ещё одна функция — это <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">isEmpty</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">// list === null
isEmpty(l(4)) // false
isEmpty(l()) // true</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">isEmpty</code> считает, что это пустой список. Если хотя бы один параметр был передан, то список уже не пустой.</p>
<p>Почему мы не делаем такую проверку <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">list === null</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">null</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">null</code>, завтра что-то ещё. И если вы используете те функции, которые предоставляет библиотека, вам не придётся в будущем переписывать весь код.</p>
<h2 id="heading-2-7">Повышаем уровень абстракции</h2>
<p>В этом уроке мы реализовали <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)" rel="noopener noreferrer" target="_blank">абстрактный тип данных</a> <strong>Список</strong> на основе ранее пройденных <strong>пар</strong>. Ключевой особенностью списка является его интерфейс, а именно функции, реализующие такие операции, как "получить голову" (возвращает первый элемент списка), "получить хвост" (возвращает новый список, полученный из исходного списка отсечением у него первого элемента), "добавить новую голову" (добавление элемента в начало списка). И здесь следует обратить внимание на несколько вещей.</p>
<p>Во-первых, не стоит отождествлять между собой пары и списки. Пары в данном случае были использованы как подходящий <em>инструмент</em> для создания списка. <strong>Абстрактный список</strong> (см. первый абзац) может быть реализован в <strong>конкретных структурах данных</strong>. В нашем случае это <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9_%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA#.D0.9E.D0.B4.D0.BD.D0.BE.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA_.28.D0.BE.D0.B4.D0.BD.D0.BE.D0.BD.D0.B0.D0.BF.D1.80.D0.B0.D0.B2.D0.BB.D0.B5.D0.BD.D0.BD.D1.8B.D0.B9_.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA.29" rel="noopener noreferrer" target="_blank">односвязный список</a>. Важной характеристикой этой структуры данных является то, что каждый элемент списка, помимо определённого хранимого значения (число, строка, дата, адрес, имя и любая другая информация), содержит ссылку на другой такой же по структуре элемент. Таким образом, между элементами списка существует <strong>связь</strong>, и мы можем последовательно "путешествовать" (перемещаться) от текущего элемента к следующему, от начала списка к его <strong>концу</strong>.</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">cons(1, cons(2, cons(3, cons(4, cons(5, ...
cons('one', cons('two', cons('three', cons('four', cons('five', ...</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>Однако, надо понимать, что сделать это можно не только с помощью пар. Cписки можно представить на основе массивов, а также некоторых других типов данных. Поэтому, если мы пользуемся <strong>списком</strong>, то к нему нельзя напрямую применять функции по работе с парами даже если мы знаем, что данный конкретный список состоит из пар. Для этого вводятся специальные функции для работы со списками, учитывающие его внутреннюю реализацию, которая может изменяться.</p>
<p>Во-вторых, коль скоро мы реализовали список именно на основе <strong>пар</strong>, то следует понимать, что не каждая пара, с которой мы имеем дело, какой бы «сложной» она ни была, является списком. У списка есть начало и конец, и мы должны иметь возможность последовательно перемещаться от одного элемента к другому. Для того, чтобы понять, что мы достигли последнего элемента списка и перемещаться дальше уже нет смысла, мы вводим специальный <strong>маркер конца списка</strong> — пустую пару <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l()</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">null</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> пары, является <strong>последним элементом списка</strong>.
Отметим также, что у пустого списка (списка, в котором нет ни одного элемента) не может быть ни головы, ни хвоста. Поэтому соответствующие операции, примененные к пустому списку (взятие <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">head</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">tail</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">isEmpty</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">cons(1, cons(cons(3, null), 2))
cons(1, cons(2, cons(3, 4)))
cons(cons(1, 2), cons(3, cons(4, null)))</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">cons(1, cons(2, cons(3, null)))
cons('This', cons('is', cons('a', cons('list', null))))</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">cons(1, cons(2, cons(3, cons(4, cons(5, null))))) // (1, 2, 3, 4, 5)
l(1, 2, 3, 4, 5) // (1, 2, 3, 4, 5)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Для генерации списка мы создали функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</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>
<ul>
<li>Выделив код создания списка в отдельную функцию и дав ей имя <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</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">list</code>), мы ввели отдельную и понятную абстракцию, отвечающую за создание <strong>списков</strong> (и ничего другого!). Выше говорилось, что списки могут быть реализованы не только на парах, но и на массивах, а также других типах данных. Так вот, наша абстракция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</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">l</code>.</li>
<li>Хорошая абстракция делает код более понятным и повышает его читабельность. Теперь, видя в коде <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</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>, мы каждый раз должны будем определять, что же делает данная конкретная пара: то ли создаёт список, то ли хранит временные данные, то ли печёт пирожки ?! ;)</li>
</ul>
<p>Посмотрите на «безликие» нагромождения <strong>пар</strong>, создающие разные сущности, которые мы проходили в прошлых курсах. Только комментарии помогают понять, что здесь происходит.</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">cons(5, cons(2, cons(7, cons(11, cons(6, cons(14, null)))))) // создание списка
cons(3, 17) // создание точки
cons(8, 17) // создание рационального числа
cons(cons(3, 33), cons(-2, -22)) // создание отрезка
const rectangle = cons(cons(-2, 23), cons(5, 11)) // создание прямоугольника
2 * (car(cdr(rectangle)) + cdr(cdr(rectangle))) // вычисление периметра прямоугольника</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">list(5, 2, 7, 11, 6, 14)
makePoint(3, 17)
makeRational(8, 17)
makeSegment(makePoint(3, 33), makePoint(-2, -22))
const point = makePoint(-2, 23)
const rectangle = makeRectangle(point, 5, 11)
perimeter(rectangle)</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">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 { l, head, tail } = require('@hexlet/pairs-data')
const list = l(5, 3, 9)
console.log(head(tail(list))) // => 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></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/sequences/lessons/list/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 9</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/sequences/lessons/list/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-Bukl1lYy.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-BrRXra1y.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>