Отображение списков — пожалуй, самая популярная операция, которая происходит в программировании. В языках она обычно представлена функцией map.
Например, в функциональных языках map — это самая часто используемая функция, которая встречается буквально через строку. Но и в таких языках, как JavaScript, Python, Ruby, даже в последнее время PHP и Java, не говоря уже о более новых языках, программисты крайне часто используют данный метод. Обычно map целиком и полностью заменяет необходимость использовать циклы, что является неоспоримым преимуществом.
Давайте познакомимся с отображением списков на примере конкретной задачи.
В данном примере мы формируем html-структуру, которая содержит в себе 2 тега blockquote (это цитаты). Ниже можно увидеть, как они добавляются в HTML. При этом мы сразу создаём этот HTML с помощью make. Далее с помощью функции b2p, которая принимает на вход HTML и возвращает новый HTML, заменяем теги blockquote на p.
Ниже распечатан processedHtml и демонстрируется результат замены:
Данная операция может происходить в реальной жизни. Давайте посмотрим, как устроена внутри функция b2p:
Здесь происходит классический рекурсивный процесс, нужно только увидеть ключевые точки. Мы получаем голову от списка, производим какие-то преобразования и в самом конце делаем рекурсивный вызов функции b2p в которую передаём хвост.
Данный рекурсивный процесс, постепенно углубляясь во внутрь, сформирует вложенные cons и у нас получится список в котором blockquote заменены на p.
В примере выше можно увидеть одну проблему. Она связана с тем, что нам важно знать о том, что из себя представляют элементы, чтобы их обходить. Это означает, что нам пришлось бы переписывать все функции для обработки HTML именно из-за того, что нам нужно знать, что внутри у нас список. Если бы вышла новая версия этой библиотеки и там использовались бы не функции head/tail, то весь код пришлось бы переписывать. Этот отрицательный момент связан с тем, что функций может быть очень много и это не обязательно замена одного тега на другой (хотя и количество таких вариантов может быть велико). В реальной жизни у элементов существует ещё и большое количество атрибутов. Возможно мы захотим извлечь их значения. То есть не обязательно получить на выходе HTML. На выходе мы можем получить список чего-то, например, ширины элементов и найти самый широкий из них для того, чтобы произвести какие-то манипуляции. Количество задач с использованием этого подхода бесконечно, можно проходиться по всем элементам и формировать новый список или структуру на их основе, в которой мы, например, преобразовали какие-то элементы.
Отображение последовательностей
Map (отображение) — универсальная абстракция. В каждом языке есть перечислимые типы данных: например, массивы или списки, и для них почти наверняка есть встроенная функция map. Она работает всегда одинаково. Принимает на вход коллекцию и функцию-трансформер, которая берет элемент и возвращает его преобразование (конкретное действие зависит от конкретной ситуации). Различается только способ вызова и иногда порядок аргументов. То же самое касается и любой абстракции, построенной поверх коллекций. Все, что может быть перечислено, может быть отображено. Неизменным в этих отображениях всегда остается количество элементов. Отображенная коллекция элементов всегда такого же размера, как и исходная.
Давайте посмотрим на реализацию той же задачи, в которой мы использовали функцию b2p. Только теперь мы это сделаем через map и увидим, как поменяется наш код:
Функция map импортирована из библиотеки @hexlet/html-tags. Она принимает на вход функцию-обработчик и коллекцию, т.е. наш HTML. При этом не важно, какая у него структура. Сейчас мы с этим разберёмся. Для простоты и чтобы не писать много кода, сформируем HTML одной строкой append(append(make(), bq1), bq2), сделав внутренние вызовы функций.
Теперь давайте посмотрим, что из себя представляет функция, которая передаётся в map первым элементом:
В этом и кроется ключ. Как минимум, сразу понятно, что map — это функция высшего порядка. Структура самой функции такова, что она принимает на вход один параметр, который является элементом и обрабатывается в данный момент, а тело — это всё, что вы хотите сделать с этим элементом, абсолютно любой код. Главное, что в конце из этой функции вы должны вернуть нечто новое, что попадёт в результирующий список, который получится на выходе из map.
После этого мы получаем processedHtml, значение для которого возвращает map. Теперь, если распечатаем processedHtml с помощью toString, то увидим, что произошла замена:
Блок blockquote был заменён на p, также как это было в предыдущем примере.
Преимущества
- Универсальный код
- Декларативный код
- Абстрагирование от структуры
Давайте посмотрим, какие преимущества даёт нам использование map. Во-первых, мы получили универсальный код. Что это означает? Теперь решена проблема, заключавшаяся в том, что нам надо написать 500 000 одинаковых функций, которые делают немного разные преобразования, а потом ещё и рефакторить (переписывать) их, если поменялась внутренняя структура нашего HTML, т.е. мы как-то по-другому его реализовали. map, в данном случае, единая функция, которая специфицируется правильным поведением в зависимости от разных ситуаций, теперь можно не писать дополнительные функции, а просто каждый раз делать ту обработку, которая вам нужна.
Во-вторых, это декларативный код, взгляните на пример. Хотя там используются ещё не изученные нами вещи, он достаточно прост, чтобы понять концепцию.
[1, 2, 3] — это так называемый массив, но в данном случае мы будем говорить список, потому что можно воспринимать его как список. На первой строке показан принцип отображения. Был список [1, 2, 3], а стал [10, 20, 30]. Каждый элемент мы умножили на 10.
Давайте посмотрим на два возможных решения. Сразу оговорюсь, это реальный код, именно так пишут на JS. Это не пары и не то, что мы сейчас проходим и изучаем, потому что мы учимся, а то, как происходит по-настоящему.
Первое — это функциональный стиль с использованием отображения списков. К списку применяется функция map, которой внутри передаётся лямбда (анонимная функция) — x => 10 * x. Она принимает элемент и умножает его на 10. Причём возврат мы здесь не пишем, это сокращённый вид функции, которая в конечном итоге делает возврат этого значения. Думаю, очевидно, что здесь происходит, это очень лаконичный и выразительный код.
Но если мы посмотрим на пример, написанный в императивном стиле, обычный способ решения этой задачи, который реализуется через циклы, то видно, что кода стало больше.
Здесь присутствует изменение состояния и код не отражает суть операции, т.е. это просто последовательность шагов: берём это, кладём сюда, делаем то, делаем это.
В чём выражается декларативность? Код можно воспринимать, как определение нового списка. Список [10, 20, 30] в терминах нашего отображения — это исходный список, в котором каждый элемент умножен на 10. По сути, данное определение — это спецификация. Имплементацией этого отображения является запись [1, 2, 3].map(x => 10 * x);. Здесь нет изменяемого состояния, что является обязательным для декларативного кода. Как вы помните, это избавляет нас от большого количества потенциальных ошибок.
И третий немаловажный момент — это абстрагирование от структуры. Напомню проблему. Когда мы работаем императивно или пишем сами функции обхода нашей структуры, то мы обязаны знать, как эта структура устроена внутри. Если такого кода очень много, то при любом изменении самой структуры вам придётся переписывать почти весь код. Но в данном случае мы переходим на новый уровень абстракции. То есть map строит барьер абстракции, удаляя нас от деталей того, как реализовано то, с чем мы работаем. Поэтому за map может скрываться всё, что угодно. Деревья, множества, какие-то сложные вещи, которые не так просто обходить, но можем об этом уже даже не задумываться.
Ниже еще пара примеров работы с использованием map. Взгляните как элегантно можно извлечь квадратный корень из элементов списка:
Теперь давайте посмотрим, как реализуется map:
Если мы вспомним первую функции b2p она была длиннее и сложнее устроена. Здесь мы видим почти то же самое, кроме одного аспекта. В том месте, где мы получаем новый элемент newElement, мы не самостоятельно как-то его обрабатываем, а используем для этого функцию func, которая передаётся первым параметром в map. Мы просто применяем функцию к элементу и после этого мы передаём эту функцию в следующий map, который рекурсивно вызывается для хвоста списка. Таким образом он постепенно обходит все его элементы, формируя рекурсивный процесс, и в конечном итоге получается отображённый список.
<!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 22:26:56 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="IfPMUGSlT7EJnsXMRdYNXcuIKZAwcuG2F5oqtT_XKnXOIgdnltvi0b_d4VRJ2f0qC4EEOjhFHxSqerDhbdDNGw";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: Последовательности: Рассматриваем функцию map и способ отображения списков">
<link rel="canonical" href="https://ru.hexlet.io/courses/sequences/lessons/map/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Отображение списков">
<meta property="og:title" content="JS: Последовательности">
<meta property="og:description" content="Отображение списков / JS: Последовательности: Рассматриваем функцию map и способ отображения списков">
<meta property="og:url" content="https://ru.hexlet.io/courses/sequences/lessons/map/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="nV7n8BPxMr9fRFci_N49inEVKNu5mg4x0B8IxokUpRpyjyzH4Y-f3-kHc7rw0c39sRwFcbGt8JNt_5KS2xNCdA" />
<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-26T22:26:56.010Z","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":"QDipDtxEFnWOLo39Z-vnFjpyelb6D7_Cjo3pGQpKpcKv6WI5Ljq7FThtqWVr5Bdh-ntX_PI4QWAzbXNNWE1CrA","topics":[{"id":7897,"title":"Решила вторую задачу рекурсивно без применения map, а с использованием cons, надеюсь, это вариант. Или предполагалась как раз то, что надо было использовать map?","plain_title":"Решила вторую задачу рекурсивно без применения map, а с использованием cons, надеюсь, это вариант. Или предполагалась как раз то, что надо было использовать map? ","creator":{"public_name":"Ольга Мелех","id":134418,"is_tutor":false},"comments":[{"creator":{"public_name":"Gennadi Egorov","id":127021,"is_tutor":false},"id":20714,"body":"столкнулся с такой же проблемой, мне кажется в задании нужно явно указать, что в `mirror` надо реализовать через `map`.\n\nа то уже интуитивно везеде хочется рекурсию запилить после пару курсов на хекслете)","topic_id":7897},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":15071,"body":"> Или предполагалась как раз то, что надо было использовать map?\n\nв данном случае \"использовать map\" обозначает просто сделать единственный вызов только что написанной функции `map` с нужными параметрами. Всё. А так получается примерно следующая картина: вы старательно делали топор для колки дров, а после чего начали рубить дрова \"голыми\" руками, проигнорировав ранее созданный инструмент.","topic_id":7897},{"creator":{"public_name":"Ольга Мелех","id":134418,"is_tutor":false},"id":15091,"body":")) да, я поняла уже. Спасибо )","topic_id":7897}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":27236,"title":"Добрый день!\nПодскажите, почему ошибка выскакивает _Argument must be pair, but it was 'quote'_\nв функции map\n```\nexport const map = (fn, elem) => {\n\n const iter = (list) => {\n if (isEmpty(list)) {\n return l();\n };\n return cons(node(getName(head(list)), fn(getValue(head(list)))), iter(tail(list)));\n };\n \n return iter(elem);\n \n};\n```","plain_title":"Добрый день! Подскажите, почему ошибка выскакивает Argument must be pair, but it was 'quote' в функции map ``` export const map = (fn, elem) => { const iter = (list) => { if (isEmpty(list)) { return l(); }; return cons(node(getName(head(list)), fn(getValue(head(list)))), iter(tail(list))); }; return iter(elem); }; ``` ","creator":{"public_name":"","id":111381,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":58385,"body":"Здравствуйте! Кроме текста сообщения обращайте внимание на последовательность вызовов функций (backtrace). В нём сообщается, какие модули были задействованы в работе программы, и где интерпретатор столкнулся с ошибкой.\n\nПодробнее о [backtrace](https://help.hexlet.io/article/7-how-to-debug-code).","topic_id":27236}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":20340,"title":"ГОСПОДИ ДА!!!! Никогда ещё я так долго не сидела над каким либо заданием - часов 6 в сумме. А потом замтила одну очень нужную функцию в документе `hexlet-pairs-data.md`. Это вам подсказка по итеративному процессу, братья, если функция работает, но с _дефектом_.\n\nПока я ее не заметила, обходилась костылем. \n```\nВторой итеративной функцией, вместо `reverse`. Прогоняем дефектный(результат выходил в обратном порядке) список через такую же, как использовали, только без применения функции-трансформера - и вуаля, все работает.\nТак можно, или это и есть темная сторона силы?\n```\n\nТеперь конкретный вопрос по фунции `mirror`. Спрячу под спойлер, в вопросе есть подсказка. \n```\nНужно ли использовать частичное применение, если лямбда, которую мы передавали \"на лету\" как аргумент в `map` будет намного сложнее/длинее?\n```","plain_title":"ГОСПОДИ ДА!!!! Никогда ещё я так долго не сидела над каким либо заданием - часов 6 в сумме. А потом замтила одну очень нужную функцию в документе hexlet-pairs-data.md. Это вам подсказка по итеративному процессу, братья, если функция работает, но с дефектом. Пока я ее не заметила, обходилась костылем. Второй итеративной функцией, вместо `reverse`. Прогоняем дефектный(результат выходил в обратном порядке) список через такую же, как использовали, только без применения функции-трансформера - и вуаля, все работает. Так можно, или это и есть темная сторона силы? Теперь конкретный вопрос по фунции mirror. Спрячу под спойлер, в вопросе есть подсказка. Нужно ли использовать частичное применение, если лямбда, которую мы передавали \"на лету\" как аргумент в `map` будет намного сложнее/длинее? ","creator":{"public_name":"Natalia A.","id":189144,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":43065,"body":"Частичное применение удобная техника, которую можно использовать в тех ситуациях где она подходит).\n\n> часов 6 в сумме\n\nНекоторые сидят сутками и даже неделями)","topic_id":20340},{"creator":{"public_name":"Natalia A.","id":189144,"is_tutor":false},"id":43077,"body":"> Некоторые сидят сутками и даже неделями)\n\nCпасибо, буду вспоминать ваши слова в моменты, когда руки опускаются.\n> Частичное применение удобная техника, которую можно использовать в тех ситуациях где она подходит).\n\nВидимо понять где именно она подходит и есть самое трудное :)\n","topic_id":20340}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":19293,"title":"Подскажите, в чем может быть ошибка, вроде тест mapAsB2p проходит, а map нет: \n```\nexport const map = (func, elements) => {\n function iter(newElement, elements) {\n if (isEmpty(elements)) return l();\n return cons(func(head(elements)), iter(func, tail(elements)));\n }\n return cons(func(head(elements)), iter(func, tail(elements))) \n};\n\n\n FAIL __tests__/html-tags.test.js\n dom\n ✓ #b2p (4ms)\n ✓ #mapAsB2p (1ms)\n ✕ #map (27ms)\n ✕ #mirror (3ms)\n\n ● dom › #map\n\n Argument must be pair, but it was 'null'\n\n 10 | return cons(func(head(elements)), iter(func, tail(elements)));\n 11 | }\n > 12 | return cons(func(head(elements)), iter(func, tail(elements))) \n | ^\n 13 | };\n 14 | \n 15 | export const mirror = (dom) => {\n\n at checkPair (../../lib/node_modules/hexlet-pairs-data/node_modules/hexlet-pairs/src/index.js:19:11)\n at Object.car (../../lib/node_modules/hexlet-pairs-data/node_modules/hexlet-pairs/src/index.js:52:3)\n at car (../../lib/node_modules/hexlet-pairs-data/src/index.js:69:16)\n at map (html-tags.js:12:20)\n at Object.it (__tests__/html-tags.test.js:43:20)\n```","plain_title":"Подскажите, в чем может быть ошибка, вроде тест mapAsB2p проходит, а map нет: ``` export const map = (func, elements) => { function iter(newElement, elements) { if (isEmpty(elements)) return l(); return cons(func(head(elements)), iter(func, tail(elements))); } return cons(func(head(elements)), iter(func, tail(elements))) }; FAIL tests/html-tags.test.js dom ✓ #b2p (4ms) ✓ #mapAsB2p (1ms) ✕ #map (27ms) ✕ #mirror (3ms) ● dom › #map Argument must be pair, but it was 'null' 10 | return cons(func(head(elements)), iter(func, tail(elements))); 11 | } > 12 | return cons(func(head(elements)), iter(func, tail(elements))) | ^ 13 | }; 14 | 15 | export const mirror = (dom) => { at checkPair (../../lib/node_modules/hexlet-pairs-data/node_modules/hexlet-pairs/src/index.js:19:11) at Object.car (../../lib/node_modules/hexlet-pairs-data/node_modules/hexlet-pairs/src/index.js:52:3) at car (../../lib/node_modules/hexlet-pairs-data/src/index.js:69:16) at map (html-tags.js:12:20) at Object.it (__tests__/html-tags.test.js:43:20) ","creator":{"public_name":"Александр Курсанов","id":163864,"is_tutor":false},"comments":[{"creator":{"public_name":"Илья Токарев","id":76797,"is_tutor":false},"id":40836,"body":"1. У вас рекурсивный процесс.\n2. Обратите внимание на эту строчку\n```\nat Object.it (__tests__/html-tags.test.js:43:20)\n```\n","topic_id":19293}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":35696,"title":"Добрый день!\nМое ревью: https://ru.hexlet.io/code_reviews/185513\n\nДайте наводку, что не так с моей функцией map?","plain_title":"Добрый день! Мое ревью: https://ru.hexlet.io/code_reviews/185513 Дайте наводку, что не так с моей функцией map? ","creator":{"public_name":"Elizaveta Kazakova","id":240534,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Chertkov","id":130487,"is_tutor":false},"id":78080,"body":"Здравствуйте, Елизавета!\n\nНачните с того, что перечитайте условие задачи и вывод тестов. Разберитесь с ошибкой `Argument must be list, but it was ''`.\n\nЕсли будет непонятно, пишите прямо сюда, как вы понимаете эту ошибку, будем думать дальше.\n\n","topic_id":35696}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":43863,"title":"Часа 3 вникал в задание, [решил](https://ru.hexlet.io/code_reviews/278384) минут за 5, но, похоже, так и не понял его до конца.","plain_title":"Часа 3 вникал в задание, решил (https://ru.hexlet.io/code_reviews/278384) минут за 5, но, похоже, так и не понял его до конца. ","creator":{"public_name":"Александр Кремнёв","id":261661,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр Кремнёв","id":261661,"is_tutor":false},"id":95465,"body":"**Сергей Мелодин**, следующая тема и комментарии к ней добавили понимания и расставили все на свои места. ","topic_id":43863},{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":95420,"body":"**Александр Кремнёв**, приветствую.\n\nНе страшно, если что-то осталось непонятным, всё приходит с опытом. Попробуйте двигаться дальше, а затем вернуть и пройти упражнение ещё раз. Также, можно пошагово разобрать решение учителя, чтобы лучше понять происходящее ;)","topic_id":43863}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":8480,"title":"Не могу разобраться с функцией map \n```\n// removed\n```\nОшибка `Argument must be list, but it was 'undefined'`. Что не так?","plain_title":"Не могу разобраться с функцией map // removed Ошибка Argument must be list, but it was 'undefined'. Что не так? ","creator":{"public_name":"Alex Weinberg","id":126829,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":16639,"body":"> Ошибка `Argument must be list, but it was 'undefined'`. Что не так?\n\nЛокализацию ошибки (конкретную строчку) можете посмотреть в выводе `OUTPUT`: некая функция ожидает в качестве аргумента значение типа \"Список\", но по факту получает `undefined`, о чём и ругается ;) Осталось отследить почему так происходит","topic_id":8480},{"creator":{"public_name":"Alex Weinberg","id":126829,"is_tutor":false},"id":16653,"body":"Прошу прощения, разобрался.\nФеерически элементарные ошибки -- наше всё.","topic_id":8480},{"creator":{"public_name":"Alex Weinberg","id":126829,"is_tutor":false},"id":16652,"body":"В том и дело, что вывод ошибки не указывает на мой код, только на код тестов: \n`Object.it (__tests__/html-tags.test.js:29:41)` \nчто соответствует \n`expect(htmlToString(processedDom)).toBe(result);` \nа `result`, соответственно, \n`const result = '<p>quote</p>';` \nТо есть лог говорит что вывод функции не соответствует ожидаемому, что, в общем-то, было понятно и так. \nХотя само по себе странно, то что тест пишет что аргумент должен быть списком, в то время, как результат, равенство с которым проверяется -- строка.","topic_id":8480},{"creator":{"public_name":"","id":299,"is_tutor":false},"id":16614,"body":"А что у вас map возвращает?","topic_id":8480}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":30354,"title":"В конце текстовой версии теории после заголовка \"Пример\" - пусто. Подозреваю, что по задумке там что-то должно быть)","plain_title":"В конце текстовой версии теории после заголовка \"Пример\" - пусто. Подозреваю, что по задумке там что-то должно быть) ","creator":{"public_name":"Евгения Осьмакова","id":16065,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":65765,"body":"> В конце текстовой версии теории после заголовка \"Пример\" - пусто. Подозреваю, что по задумке там что-то должно быть)\n\nУгу, там пример кода в онлайн-редакторе repl.id, только что проверил — он отображается. Попробуйте открыть урок в другом браузере","topic_id":30354}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":29336,"title":" const iter = (items, acc) => {\nКак расшифровывается acc?)","plain_title":" const iter = (items, acc) => { Как расшифровывается acc?) ","creator":{"public_name":"Данило Карпенко","id":228556,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":63475,"body":"> Как расшифровывается acc?)\n\naccumulator -> аккумулятор — параметр, в котором происходит накопление итогового результата выполнения функции.","topic_id":29336}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}},{"id":14444,"title":"два вечера решал, пытался выдавить нормальное решение, в итоге как обычно после своего решения, заглянув в решение учителя, увидел однострочную ф-цию mirror, против десятка строк у меня","plain_title":"два вечера решал, пытался выдавить нормальное решение, в итоге как обычно после своего решения, заглянув в решение учителя, увидел однострочную ф-цию mirror, против десятка строк у меня ","creator":{"public_name":"Сергей Шевцов","id":162787,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":30385,"body":"Зато на всю жизнь запомните -)","topic_id":14444}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Отображение списков","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":458,"slug":"js_sequences_map_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"## html-tags.js\n\nРеализуйте и экспортируйте функцию `map()` для библиотеки *html-tags*. Реализация должна быть построена с использованием итеративного процесса (без циклов, на основе рекурсии). Эта функция подобна той, что описывалась в теории для списков, только текущая реализация работает с HTML-списком. Параметры и их порядок у функций аналогичный. Первый — функция-трансформер, второй — коллекция (в нашем случае список HTML-тегов).\n\n```javascript\nimport { make, append, node, getValue, is } from '@hexlet/html-tags';\n\nconst dom1 = make();\nconst dom2 = append(dom1, node('h1', 'scheme'));\nconst dom3 = append(dom2, node('p', 'is a lisp'));\n\n// Отображение в результате которого в html-списке заменяются теги h1 на теги h2\nconst processedDom = map((element) => {\n if (is('h1', element)) {\n return node('h2', getValue(element));\n }\n return element;\n}, dom3);\n```\n\nРеализуйте и экспортируйте функцию `mirror()`, которая переворачивает содержимое тегов, так чтобы читать его нужно было\nсправа налево, а не слева направо.\n\n```javascript\nimport { make, append, node, getValue, is, toString as htmlToString } from '@hexlet/html-tags';\n\nconst dom1 = make();\nconst dom2 = append(dom1, node('h1', 'scheme'));\nconst dom3 = append(dom2, node('p', 'is a lisp'));\n\n// <h1>emehcs</h1>\n// <p>psil a si</p>\nhtmlToString(mirror(dom3));\n```\n\nЭкспортируйте все созданные функции.\n\n### Примечание\n\nФункцию `b2p()` можно использовать для наглядного сопоставления частного варианта операции отображения с обобщённой реализацией операции отображения (собственно, `map()`).\n\n### Подсказки\n\n* Функция `reverseStr()` (псевдоним функции `reverse()` из модуля по работе со строками *strings.js*) делает переворот строки.\n* При необходимости вы можете самостоятельно импортировать функцию `toString()` из библиотеки `@hexlet/pairs-data` и использовать её для отладки решений. Эта функция возвращает строковое представление списка\n* При необходимости вы можете самостоятельно импортировать функцию `toString()` из библиотеки `@hexlet/html-tags` и использовать её для отладки решений. Эта функция возвращает строковое представление html-списка\n* Для разрешения противоречий в случае импорта нескольких функций с одинаковыми именами используйте псевдонимы (aliases)\n","prepared_readme":"## html-tags.js\n\nРеализуйте и экспортируйте функцию `map()` для библиотеки *html-tags*. Реализация должна быть построена с использованием итеративного процесса (без циклов, на основе рекурсии). Эта функция подобна той, что описывалась в теории для списков, только текущая реализация работает с HTML-списком. Параметры и их порядок у функций аналогичный. Первый — функция-трансформер, второй — коллекция (в нашем случае список HTML-тегов).\n\n```javascript\nimport { make, append, node, getValue, is } from '@hexlet/html-tags';\n\nconst dom1 = make();\nconst dom2 = append(dom1, node('h1', 'scheme'));\nconst dom3 = append(dom2, node('p', 'is a lisp'));\n\n// Отображение в результате которого в html-списке заменяются теги h1 на теги h2\nconst processedDom = map((element) => {\n if (is('h1', element)) {\n return node('h2', getValue(element));\n }\n return element;\n}, dom3);\n```\n\nРеализуйте и экспортируйте функцию `mirror()`, которая переворачивает содержимое тегов, так чтобы читать его нужно было\nсправа налево, а не слева направо.\n\n```javascript\nimport { make, append, node, getValue, is, toString as htmlToString } from '@hexlet/html-tags';\n\nconst dom1 = make();\nconst dom2 = append(dom1, node('h1', 'scheme'));\nconst dom3 = append(dom2, node('p', 'is a lisp'));\n\n// <h1>emehcs</h1>\n// <p>psil a si</p>\nhtmlToString(mirror(dom3));\n```\n\nЭкспортируйте все созданные функции.\n\n### Примечание\n\nФункцию `b2p()` можно использовать для наглядного сопоставления частного варианта операции отображения с обобщённой реализацией операции отображения (собственно, `map()`).\n\n### Подсказки\n\n* Функция `reverseStr()` (псевдоним функции `reverse()` из модуля по работе со строками *strings.js*) делает переворот строки.\n* При необходимости вы можете самостоятельно импортировать функцию `toString()` из библиотеки `@hexlet/pairs-data` и использовать её для отладки решений. Эта функция возвращает строковое представление списка\n* При необходимости вы можете самостоятельно импортировать функцию `toString()` из библиотеки `@hexlet/html-tags` и использовать её для отладки решений. Эта функция возвращает строковое представление html-списка\n* Для разрешения противоречий в случае импорта нескольких функций с одинаковыми именами используйте псевдонимы (aliases)\n","has_solution":true,"entity_name":"Отображение списков"},"units":[{"id":1357,"name":"theory","url":"/courses/sequences/lessons/map/theory_unit"},{"id":1359,"name":"quiz","url":"/courses/sequences/lessons/map/quiz_unit"},{"id":1358,"name":"exercise","url":"/courses/sequences/lessons/map/exercise_unit"}],"links":[],"ordered_units":[{"id":1357,"name":"theory","url":"/courses/sequences/lessons/map/theory_unit"},{"id":1359,"name":"quiz","url":"/courses/sequences/lessons/map/quiz_unit"},{"id":1358,"name":"exercise","url":"/courses/sequences/lessons/map/exercise_unit"}],"id":689,"slug":"map","state":"approved","name":"Отображение списков","course_order":400,"goal":"Рассматриваем функцию map и способ отображения списков","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"170597334","theory":"Отображение списков — пожалуй, самая популярная операция, которая происходит в программировании. В языках она обычно представлена функцией `map`.\n\nНапример, в функциональных языках `map` — это самая часто используемая функция, которая встречается буквально через строку. Но и в таких языках, как JavaScript, Python, Ruby, даже в последнее время PHP и Java, не говоря уже о более новых языках, программисты крайне часто используют данный метод. Обычно `map` целиком и полностью заменяет необходимость использовать циклы, что является неоспоримым преимуществом.\n\nДавайте познакомимся с отображением списков на примере конкретной задачи.\n\n```javascript\nimport {\n make, append, toString, node,\n} from '@hexlet/html-tags'\n\nconst bq1 = node('blockquote', 'quote')\nconst bq2 = node('blockquote', 'another quote')\nconst html1 = append(make(), bq1)\nconst html2 = append(html1, bq2)\nconst processedHtml = b2p(html2)\n```\n\nВ данном примере мы формируем html-структуру, которая содержит в себе 2 тега `blockquote` (это цитаты). Ниже можно увидеть, как они добавляются в HTML. При этом мы сразу создаём этот HTML с помощью `make`. Далее с помощью функции `b2p`, которая принимает на вход HTML и возвращает новый HTML, заменяем теги `blockquote` на `p`.\n\nНиже распечатан `processedHtml` и демонстрируется результат замены:\n\n```javascript\ntoString(processedHtml)\n// <p>quote</p>\n// <p>another quote</p>\n```\n\nДанная операция может происходить в реальной жизни. Давайте посмотрим, как устроена внутри функция `b2p`:\n\n```javascript\nexport const b2p = (elements) => {\n // Если список пуст, то возвращаем пустой список\n if (isEmpty(elements)) {\n return l()\n }\n\n // Получаем голову списка\n const element = head(elements)\n let newElement\n // Если это тег blockquote, то создаём\n // новую ноду, которая формирует параграф\n // на основе значения из element\n if (is('blockquote', element)) {\n newElement = node('p', value(element))\n }\n else {\n newElement = element\n }\n\n // Создаём новый список. Первым элементом становится\n // обработанная нода, а вторым рекурсивный вызов функции b2p()\n // в которую мы передаём хвост списка\n return cons(newElement, b2p(tail(elements)))\n}\n```\n\nЗдесь происходит классический рекурсивный процесс, нужно только увидеть ключевые точки. Мы получаем голову от списка, производим какие-то преобразования и в самом конце делаем рекурсивный вызов функции `b2p` в которую передаём хвост.\n\nДанный рекурсивный процесс, постепенно углубляясь во внутрь, сформирует вложенные `cons` и у нас получится список в котором `blockquote` заменены на `p`.\n\nВ примере выше можно увидеть одну проблему. Она связана с тем, что нам важно знать о том, что из себя представляют элементы, чтобы их обходить. Это означает, что нам пришлось бы переписывать все функции для обработки HTML именно из-за того, что нам нужно знать, что внутри у нас список. Если бы вышла новая версия этой библиотеки и там использовались бы не функции `head/tail`, то весь код пришлось бы переписывать. Этот отрицательный момент связан с тем, что функций может быть очень много и это не обязательно замена одного тега на другой (хотя и количество таких вариантов может быть велико). В реальной жизни у элементов существует ещё и большое количество атрибутов. Возможно мы захотим извлечь их значения. То есть не обязательно получить на выходе HTML. На выходе мы можем получить список чего-то, например, ширины элементов и найти самый широкий из них для того, чтобы произвести какие-то манипуляции. Количество задач с использованием этого подхода бесконечно, можно проходиться по всем элементам и формировать новый список или структуру на их основе, в которой мы, например, преобразовали какие-то элементы.\n\n## Отображение последовательностей\n\n\n\n`Map` (отображение) — универсальная абстракция. В каждом языке есть перечислимые типы данных: например, массивы или списки, и для них почти наверняка есть встроенная функция `map`. Она работает всегда одинаково. Принимает на вход коллекцию и функцию-трансформер, которая берет элемент и возвращает его преобразование (конкретное действие зависит от конкретной ситуации). Различается только способ вызова и иногда порядок аргументов. То же самое касается и любой абстракции, построенной поверх коллекций. Все, что может быть перечислено, может быть отображено. Неизменным в этих отображениях всегда остается количество элементов. Отображенная коллекция элементов всегда такого же размера, как и исходная.\n\nДавайте посмотрим на реализацию той же задачи, в которой мы использовали функцию `b2p`. Только теперь мы это сделаем через `map` и увидим, как поменяется наш код:\n\n```javascript\nimport {\n node, append, make, map,\n} from '@hexlet/html-tags'\n\nconst bq1 = node('blockquote', 'quote')\nconst bq2 = node('blockquote', 'another quote')\nconst processedHtml = map((element) => {\n if (is('blockquote', element)) {\n return node('p', value(element))\n }\n return element\n}, append(append(make(), bq1), bq2))\n\n// <p>quote</p>\n// <p>another quote</p>\ntoString(processedHtml)\n```\n\nФункция `map` импортирована из библиотеки `@hexlet/html-tags`. Она принимает на вход функцию-обработчик и коллекцию, т.е. наш HTML. При этом не важно, какая у него структура. Сейчас мы с этим разберёмся. Для простоты и чтобы не писать много кода, сформируем HTML одной строкой `append(append(make(), bq1), bq2)`, сделав внутренние вызовы функций.\n\nТеперь давайте посмотрим, что из себя представляет функция, которая передаётся в `map` первым элементом:\n\n```javascript\n(element) => {\n if (is('blockquote', element)) {\n return node('p', value(element))\n }\n return element\n}\n```\n\nВ этом и кроется ключ. Как минимум, сразу понятно, что `map` — это функция высшего порядка. Структура самой функции такова, что она принимает на вход один параметр, который является элементом и обрабатывается в данный момент, а тело — это всё, что вы хотите сделать с этим элементом, абсолютно любой код. Главное, что в конце из этой функции вы должны вернуть нечто новое, что попадёт в результирующий список, который получится на выходе из `map`.\n\nПосле этого мы получаем `processedHtml`, значение для которого возвращает `map`. Теперь, если распечатаем `processedHtml` с помощью `toString`, то увидим, что произошла замена:\n\n```javascript\n// <p>quote</p>\n// <p>another quote</p>\ntoString(processedHtml)\n```\n\nБлок `blockquote` был заменён на `p`, также как это было в предыдущем примере.\n\n## Преимущества\n\n- Универсальный код\n- Декларативный код\n- Абстрагирование от структуры\n\nДавайте посмотрим, какие преимущества даёт нам использование `map`. Во-первых, мы получили универсальный код. Что это означает? Теперь решена проблема, заключавшаяся в том, что нам надо написать 500 000 одинаковых функций, которые делают немного разные преобразования, а потом ещё и рефакторить (переписывать) их, если поменялась внутренняя структура нашего HTML, т.е. мы как-то по-другому его реализовали. `map`, в данном случае, единая функция, которая специфицируется правильным поведением в зависимости от разных ситуаций, теперь можно не писать дополнительные функции, а просто каждый раз делать ту обработку, которая вам нужна.\n\nВо-вторых, это декларативный код, взгляните на пример. Хотя там используются ещё не изученные нами вещи, он достаточно прост, чтобы понять концепцию.\n\n```javascript\n// [1, 2, 3] => [10, 20, 30]\n\n// functional way\n[1, 2, 3].map(x => 10 * x)\n```\n\n`[1, 2, 3]` — это так называемый массив, но в данном случае мы будем говорить список, потому что можно воспринимать его как список. На первой строке показан принцип отображения. Был список `[1, 2, 3]`, а стал `[10, 20, 30]`. Каждый элемент мы умножили на 10.\n\nДавайте посмотрим на два возможных решения. Сразу оговорюсь, это реальный код, именно так пишут на JS. Это не пары и не то, что мы сейчас проходим и изучаем, потому что мы учимся, а то, как происходит по-настоящему.\n\nПервое — это функциональный стиль с использованием отображения списков. К списку применяется функция `map`, которой внутри передаётся лямбда (анонимная функция) — `x => 10 * x`. Она принимает элемент и умножает его на 10. Причём возврат мы здесь не пишем, это сокращённый вид функции, которая в конечном итоге делает возврат этого значения. Думаю, очевидно, что здесь происходит, это очень лаконичный и выразительный код.\n\nНо если мы посмотрим на пример, написанный в императивном стиле, обычный способ решения этой задачи, который реализуется через циклы, то видно, что кода стало больше.\n\n```javascript\n// imperative way\nconst result = []\nfor (let i of [1, 2, 3]) {\n result.push(i * 10)\n}\n```\n\nЗдесь присутствует изменение состояния и код не отражает суть операции, т.е. это просто последовательность шагов: берём это, кладём сюда, делаем то, делаем это.\n\nВ чём выражается декларативность? Код можно воспринимать, как определение нового списка. Список `[10, 20, 30]` в терминах нашего отображения — это исходный список, в котором каждый элемент умножен на 10. По сути, данное определение — это спецификация. Имплементацией этого отображения является запись `[1, 2, 3].map(x => 10 * x);`. Здесь нет изменяемого состояния, что является обязательным для декларативного кода. Как вы помните, это избавляет нас от большого количества потенциальных ошибок.\n\nИ третий немаловажный момент — это абстрагирование от структуры. Напомню проблему. Когда мы работаем императивно или пишем сами функции обхода нашей структуры, то мы обязаны знать, как эта структура устроена внутри. Если такого кода очень много, то при любом изменении самой структуры вам придётся переписывать почти весь код. Но в данном случае мы переходим на новый уровень абстракции. То есть `map` строит барьер абстракции, удаляя нас от деталей того, как реализовано то, с чем мы работаем. Поэтому за `map` может скрываться всё, что угодно. Деревья, множества, какие-то сложные вещи, которые не так просто обходить, но можем об этом уже даже не задумываться.\n\nНиже еще пара примеров работы с использованием `map`. Взгляните как элегантно можно извлечь квадратный корень из элементов списка:\n\n```javascript\nimport { l, map, toString } from '@hexlet/pairs-data'\n\nconst list = l(4, 16, 64)\nconst list2 = map(Math.sqrt, list)\nconsole.log(toString(list2)) // => (2, 4, 8)\n\nconst list3 = map(item => item + 5, list)\nconsole.log(toString(list3)) // => (9, 21, 69)\n```\n\nТеперь давайте посмотрим, как реализуется `map`:\n\n```javascript\nexport const map = (func, elements) => {\n if (isEmpty(elements)) {\n return l()\n }\n\n const newElement = func(head(elements))\n return cons(newElement, map(func, tail(elements)))\n}\n```\n\nЕсли мы вспомним первую функции `b2p` она была длиннее и сложнее устроена. Здесь мы видим почти то же самое, кроме одного аспекта. В том месте, где мы получаем новый элемент `newElement`, мы не самостоятельно как-то его обрабатываем, а используем для этого функцию `func`, которая передаётся первым параметром в `map`. Мы просто применяем функцию к элементу и после этого мы передаём эту функцию в следующий `map`, который рекурсивно вызывается для хвоста списка. Таким образом он постепенно обходит все его элементы, формируя рекурсивный процесс, и в конечном итоге получается отображённый список.\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/map/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"><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">map</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">map</code> — это самая часто используемая функция, которая встречается буквально через строку. Но и в таких языках, как JavaScript, Python, Ruby, даже в последнее время PHP и Java, не говоря уже о более новых языках, программисты крайне часто используют данный метод. Обычно <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">map</code> целиком и полностью заменяет необходимость использовать циклы, что является неоспоримым преимуществом.</p>
<p>Давайте познакомимся с отображением списков на примере конкретной задачи.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import {
make, append, toString, node,
} from '@hexlet/html-tags'
const bq1 = node('blockquote', 'quote')
const bq2 = node('blockquote', 'another quote')
const html1 = append(make(), bq1)
const html2 = append(html1, bq2)
const processedHtml = b2p(html2)</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>В данном примере мы формируем html-структуру, которая содержит в себе 2 тега <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">blockquote</code> (это цитаты). Ниже можно увидеть, как они добавляются в HTML. При этом мы сразу создаём этот HTML с помощью <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">make</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">b2p</code>, которая принимает на вход HTML и возвращает новый HTML, заменяем теги <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">blockquote</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">p</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">processedHtml</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">toString(processedHtml)
// <p>quote</p>
// <p>another quote</p></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">b2p</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">export const b2p = (elements) => {
// Если список пуст, то возвращаем пустой список
if (isEmpty(elements)) {
return l()
}
// Получаем голову списка
const element = head(elements)
let newElement
// Если это тег blockquote, то создаём
// новую ноду, которая формирует параграф
// на основе значения из element
if (is('blockquote', element)) {
newElement = node('p', value(element))
}
else {
newElement = element
}
// Создаём новый список. Первым элементом становится
// обработанная нода, а вторым рекурсивный вызов функции b2p()
// в которую мы передаём хвост списка
return cons(newElement, b2p(tail(elements)))
}</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">b2p</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">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">blockquote</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">p</code>.</p>
<p>В примере выше можно увидеть одну проблему. Она связана с тем, что нам важно знать о том, что из себя представляют элементы, чтобы их обходить. Это означает, что нам пришлось бы переписывать все функции для обработки HTML именно из-за того, что нам нужно знать, что внутри у нас список. Если бы вышла новая версия этой библиотеки и там использовались бы не функции <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/tail</code>, то весь код пришлось бы переписывать. Этот отрицательный момент связан с тем, что функций может быть очень много и это не обязательно замена одного тега на другой (хотя и количество таких вариантов может быть велико). В реальной жизни у элементов существует ещё и большое количество атрибутов. Возможно мы захотим извлечь их значения. То есть не обязательно получить на выходе HTML. На выходе мы можем получить список чего-то, например, ширины элементов и найти самый широкий из них для того, чтобы произвести какие-то манипуляции. Количество задач с использованием этого подхода бесконечно, можно проходиться по всем элементам и формировать новый список или структуру на их основе, в которой мы, например, преобразовали какие-то элементы.</p>
<h2 id="heading-2-1">Отображение последовательностей</h2>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE4MCwicHVyIjoiYmxvYl9pZCJ9fQ==--03f59b3d8057542e28dca337b008bc555f7eeaeb/map.png" alt="Отображение" loading="lazy"/></p>
<p><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Map</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">map</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">b2p</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">map</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 {
node, append, make, map,
} from '@hexlet/html-tags'
const bq1 = node('blockquote', 'quote')
const bq2 = node('blockquote', 'another quote')
const processedHtml = map((element) => {
if (is('blockquote', element)) {
return node('p', value(element))
}
return element
}, append(append(make(), bq1), bq2))
// <p>quote</p>
// <p>another quote</p>
toString(processedHtml)</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">map</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">@hexlet/html-tags</code>. Она принимает на вход функцию-обработчик и коллекцию, т.е. наш HTML. При этом не важно, какая у него структура. Сейчас мы с этим разберёмся. Для простоты и чтобы не писать много кода, сформируем HTML одной строкой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">append(append(make(), bq1), bq2)</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">map</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">(element) => {
if (is('blockquote', element)) {
return node('p', value(element))
}
return element
}</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">map</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">map</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">processedHtml</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">map</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">processedHtml</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">toString</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">// <p>quote</p>
// <p>another quote</p>
toString(processedHtml)</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">blockquote</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">p</code>, также как это было в предыдущем примере.</p>
<h2 id="heading-2-2">Преимущества</h2>
<ul>
<li>Универсальный код</li>
<li>Декларативный код</li>
<li>Абстрагирование от структуры</li>
</ul>
<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">map</code>. Во-первых, мы получили универсальный код. Что это означает? Теперь решена проблема, заключавшаяся в том, что нам надо написать 500 000 одинаковых функций, которые делают немного разные преобразования, а потом ещё и рефакторить (переписывать) их, если поменялась внутренняя структура нашего HTML, т.е. мы как-то по-другому его реализовали. <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">map</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">// [1, 2, 3] => [10, 20, 30]
// functional way
[1, 2, 3].map(x => 10 * x)</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">[1, 2, 3]</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">[1, 2, 3]</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">[10, 20, 30]</code>. Каждый элемент мы умножили на 10.</p>
<p>Давайте посмотрим на два возможных решения. Сразу оговорюсь, это реальный код, именно так пишут на JS. Это не пары и не то, что мы сейчас проходим и изучаем, потому что мы учимся, а то, как происходит по-настоящему.</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">map</code>, которой внутри передаётся лямбда (анонимная функция) — <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">x => 10 * x</code>. Она принимает элемент и умножает его на 10. Причём возврат мы здесь не пишем, это сокращённый вид функции, которая в конечном итоге делает возврат этого значения. Думаю, очевидно, что здесь происходит, это очень лаконичный и выразительный код.</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">// imperative way
const result = []
for (let i of [1, 2, 3]) {
result.push(i * 10)
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Здесь присутствует изменение состояния и код не отражает суть операции, т.е. это просто последовательность шагов: берём это, кладём сюда, делаем то, делаем это.</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">[10, 20, 30]</code> в терминах нашего отображения — это исходный список, в котором каждый элемент умножен на 10. По сути, данное определение — это спецификация. Имплементацией этого отображения является запись <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">[1, 2, 3].map(x => 10 * x);</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">map</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">map</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">map</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 { l, map, toString } from '@hexlet/pairs-data'
const list = l(4, 16, 64)
const list2 = map(Math.sqrt, list)
console.log(toString(list2)) // => (2, 4, 8)
const list3 = map(item => item + 5, list)
console.log(toString(list3)) // => (9, 21, 69)</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">map</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">export const map = (func, elements) => {
if (isEmpty(elements)) {
return l()
}
const newElement = func(head(elements))
return cons(newElement, map(func, tail(elements)))
}</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">b2p</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">newElement</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">func</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">map</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">map</code>, который рекурсивно вызывается для хвоста списка. Таким образом он постепенно обходит все его элементы, формируя рекурсивный процесс, и в конечном итоге получается отображённый список.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/js-sicp?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">СИКП на JS</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Навык фундаментального программирования</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png" alt="СИКП на JS" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/sequences/lessons/map/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/map/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>