Пары
Наше погружение в последовательности мы начнём с того, что проработаем уровень абстракции для работы со списками. Давайте вспомним, как работают пары. С помощью пар, а конкретно функции-конструктора cons, мы можем соединять, например, числа:
Также можем соединять любые объекты данных, включая те же пары. В данном примере мы соединяем 5 как число и другую пару. Если мы распечатаем, то увидим, что у пары такое представление:
Способы соединить 1, 2, 3 и 4
Существует более одного способа соединять различные числа, т.е. представлять их разными структурами данных. Например, числа 1, 2, 3, 4 можно соединить в единую иерархическую конструкцию, как на примере слева на так называемой стрелочной диаграмме. Мы видим, что в паре на самом верху левый элемент является парой, правый — тоже пара и в каждой из этих пар элементы представлены конкретными числами.
При этом может быть и совершенно другой вариант, в котором левый элемент является парой, а правый, например, является числом 4. Левый элемент в свою очередь делится на элемент, в котором есть число и элемент, являющийся парой и т.д. Мы можем это делать бесконечно много раз.
Под диаграммами описано представление в виде кода с помощью вложенных cons. Причём слева симметричное вложение, а справа немного сдвинутое.
То, что мы получили, называется — иерархической структурой. Её создание обеспечивается возможностью, которая называется — свойство замыкания (множества). Чтобы понять, что это обозначает, давайте познакомимся с понятием множества.
Что такое множество
Множество — это довольно интуитивное понятие. Для математики оно является одним из ключевых. Через множество определено множество вещей. Определение чистых математических функций, с которыми мы работаем, полностью построено на теоретико-множественном подходе. Так вот, что такое множество? Множество — это какой-то набор. Мы можем сказать, что это совокупность объектов данных. Например, в данном случае мы видим, что это числа. Различные типы чисел. При этом видно, что здесь множества вложены друг в друга, т.е. у множеств есть отношения друг с другом, существуют операции над множествами, например, пересечение, объединение и т.д. Причём это не просто какая-то математическая теория. Множества очень активно используются в практике программирования. Те же самые базы данных SQL основаны на реляционной алгебре, которая очень тесно связанна с теорией множеств. И те же джойны, которые используются в SQL следует воспринимать и анализировать именно на основе понимания теории множеств.
Кроме этого, поскольку мы говорим о парах, мы можем себе вообразить некоторое множество, которое называется множество всех пар. Сейчас нам это понадобится.
Замыкание (абстрактная алгебра)
Это не то замыкание, про которое мы уже говорили, связанное с запоминанием контекста. Это замыкание из математики, из абстрактной алгебры и звучит оно примерно так:
Множество замкнуто относительно операции, если применение операции к элементам этого множества даёт результат, который так же является элементом этого множества.
Возможно, это слишком формальное определение, которое звучит очень абстрактно. На самом деле оно крайне простое. Всё, что оно означает: используя функцию cons, множество всех пар замкнуто относительно функции cons. Почему? Потому что когда вы с помощью функции cons что-либо объединяете, вы снова получаете пару. Если полученную пару снова объединить функцией cons в ещё одну пару, получится, опять же, пара. Но как мы помним, множество всех пар включает все возможные виды пар. Какую бы пару вы ни создали, она будет туда включена, т.е. функция cons всегда порождает пару. Если бы в какой-то момент она порождала что-то другое, то не существовало бы замкнутости. Замкнутость присутствует именно потому, что применение этой функции к абсолютно любым элементам этого множества порождает элемент этого же множества. А свойство замыкания является ключевым фактором в возможности построения иерархических структур.
Иерархические структуры — это такие структуры, части которых содержат в себе ещё более элементарные части, которые в свою очередь содержат ещё более элементарные части.
В примере выше cons соединяет не просто какие-то элементарные части. Она соединяет пары, которые в свою очередь состоят из частей и так далее. Это может происходить бесконечно, опять же, благодаря свойству замыкания.
Последовательности
Как же всё-таки проще всего представить последовательности? Мы используем стрелочную диаграмму, которая показывает самый простой, но не единственный способ представления последовательностей. На верхнем уровне у нас будет пара, первый элемент которой объект данных, например, число, второй элемент — это фактически ссылка на другую пару, в которой опять же первый элемент — объект данных и дальше опять ссылка на следующую пару. И так далее до самой последней вложенной пары, где первый элемент — также данные, а второй элемент - специальный маркер, который обозначает, что список закончился, и дальше смотреть не нужно.
Как это записывается кодом? По сути — это последовательность cons'ов, которые вложены друг в друга.
Мы приняли за аксиоматический факт, что у последнего элемента маркер окончания списка это null. Если мы его встречаем значит дальше проверять и смотреть не нужно, список закончился.
Интерфейс
Давайте рассмотрим интерфейс работы со списками. Начиная с текущего момента и на протяжении всего курса, мы будем постоянно с ними работать, строить поверх них абстракции. Поэтому нам нужно научиться выполнять какие-то базовые операции со списками и построить данный слой абстракции поверх пар.
Если бы мы действовали в лоб, то создание списка было бы примерно таким:
Но как видите, это довольно громоздкая запись, здесь слишком много шума и сложно увидеть реальную структуру списка. А ведь список состоит всего из четырёх чисел. Поэтому мы сделали функцию-конструктор для создания списков:
Это маленькая функция, которая принимает на вход просто числа через запятую и возвращает список. Т.е. она внутри делает то, что описывает код выше. Функция l работает с переменным числом аргументов (оно не задано жёстко и вы можете передавать любое количество). Внутри она имеет кое-какие особенности, с которыми мы ещё не знакомы и чтобы их изучить, нужно продвинуться вперёд гораздо дальше. Поэтому просто используйте её, как чёрный ящик, представляя, что вызывая конструктор l(1, ...) вы получаете запись cons(1, ...).
Почему мы сделали именно так? Назвали конструктор l одной буквой. Всё очень просто. Потому что мы часто будем строить вложенные списки и это очень просто делать, если у вас короткое название функции. Кстати, если бы оно было длинное, то вложенные функции банально привели бы к распуханию этой структуры. При этом для добавления элемента в список мы используем функцию cons. Но эта функция будет определена уже в списках, т.е. она не является функцией пар по той причине, что это другой слой абстракции. Если мы посмотрим на внутреннюю структуру, то увидим следующее:
Если распечатать список, то можно увидеть, что десятки здесь уже нет:
Что это означает? Это означает, что у нас присутствует неизменяемость. Т.е. добавление в список, как это было с парами, не изменяет его, а возвращает новый список. Поэтому, если вы хотите его использовать, то необходимо создать, например, $list2.
И ещё несколько базовых операций, которые обязательно будут нужны для работы со списками. Они очень похожи на car и cdr. Первая из них — это head, так называемая голова. Данная функция берёт из списка первый элемент. Когда идёт работа со списками, существует понятийный аппарат, в котором присутствует и данное определение.
- Первый элемент списка (т.е. последний добавленный) называется "голова", а всё остальное — это хвост. Соответственно, head — это взять голову, tail — это взять хвост. Хвост — это список минус первый элемент. Мы говорим первый, так как находится всегда слева, но по сути это то, что добавляется в список последним. В данном случае tail($list), учитывая, что наш список был l(1, 2, 3, 4), это список l(2, 3, 4). Если мы будем применять к списку tail много раз, то постепенно будем получать список меньшего размера, пока в конце концов не получим null.
Ещё одна функция — это isEmpty. Она проверяет пустой ли список, т.е. внутри она делает такую проверку:
Видно, что если мы передаём просто конструктор, вызванный без параметров, то isEmpty считает, что это пустой список. Если хотя бы один параметр был передан, то список уже не пустой.
Почему мы не делаем такую проверку $list === null, а вводим новую функцию? Думаю вам уже должно быть понятно. Мы ведь всё время говорим о том, что необходимо использовать абстракцию. То, что внутри используется null — это всего лишь особенность реализации. Сегодня null, завтра что-то ещё. И если вы используете те функции, которые предоставляет библиотека, вам не придётся в будущем переписывать весь код.
Повышаем уровень абстракции
В этом уроке мы реализовали абстрактный тип данных Список на основе ранее пройденных пар. Ключевой особенностью списка является его интерфейс, а именно функции, реализующие такие операции, как "получить голову" (возвращает первый элемент списка), "получить хвост" (возвращает новый список, полученный из исходного списка отсечением у него первого элемента), "добавить новую голову" (добавление элемента в начало списка). И здесь следует обратить внимание на несколько вещей.
Во-первых, не стоит отождествлять между собой пары и списки. Пары в данном случае были использованы как подходящий инструмент для создания списка. Абстрактный список (см. первый абзац) может быть реализован в конкретных структурах данных. В нашем случае это односвязный список. Важной характеристикой этой структуры данных является то, что каждый элемент списка, помимо определённого хранимого значения (число, строка, дата, адрес, имя и любая другая информация), содержит ссылку на другой такой же по структуре элемент. Таким образом, между элементами списка существует связь, и мы можем последовательно "путешествовать" (перемещаться) от текущего элемента к следующему, от начала списка к его концу.
И, как видим, пары вполне позволяют реализовать особенности списка как структуры данных. Ведь пара, помимо основного значения, может содержать ещё другую пару аналогичной структуры (значение + ссылка на другую пару) — и такая последовательность может длиться бесконечно.
Однако, надо понимать, что сделать это можно не только с помощью пар. Cписки можно представить на основе массивов, а также некоторых других типов данных. Поэтому, если мы пользуемся списком, то к нему нельзя напрямую применять функции по работе с парами даже если мы знаем, что данный конкретный список состоит из пар. Для этого вводятся специальные функции для работы со списками, учитывающие его внутреннюю реализацию, которая может изменяться.
Во-вторых, коль скоро мы реализовали список именно на основе пар, то следует понимать, что не каждая пара, с которой мы имеем дело, какой бы «сложной» она ни была, является списком. У списка есть начало и конец, и мы должны иметь возможность последовательно перемещаться от одного элемента к другому. Для того, чтобы понять, что мы достигли последнего элемента списка и перемещаться дальше уже нет смысла, мы вводим специальный маркер конца списка — пустую пару l(). По предварительному соглашению в роли маркера могут также выступать другие подходящие значения, например, null. Как только мы встречаем очередную пару, в cdr которой находится пустая пара, это означает, что очередное значение списка, лежащее в car пары, является последним элементом списка.
Отметим также, что у пустого списка (списка, в котором нет ни одного элемента) не может быть ни головы, ни хвоста. Поэтому соответствующие операции, примененные к пустому списку (взятие head или tail), приводят к возникновению ошибки. Чтобы её избежать, вводят функцию для проверки списка на «пустоту» (например, isEmpty), а далее берут голову или хвост у списка, предварительно убедившись, что он не является пустым.
Ниже приведены примеры пар, которые нельзя назвать списками. Они не реализуют требования, предъявляемые к односвязным спискам: в них отсутствует последовательная связь между элементами и/или нет маркера конца списка:
Теперь покажем пары, которыми мы можем смело пользоваться как списками:
В уроке мы отметили, что следующие две записи, конструирующие список, являются эквивалентными:
Для генерации списка мы создали функцию l, несмотря на то, что у нас уже есть конструктор cons. Это необходимо по нескольким причинам. Отметим наиболее важные из них:
- Выделив код создания списка в отдельную функцию и дав ей имя l (сокращ. от list), мы ввели отдельную и понятную абстракцию, отвечающую за создание списков (и ничего другого!). Выше говорилось, что списки могут быть реализованы не только на парах, но и на массивах, а также других типах данных. Так вот, наша абстракция l позволяет отвлечься от внутреннего устройства списка. Тот, кто будет пользоваться этой функцией, не обязан знать, какими способами она конструирует список, а в случае необходимости может изменить его внутреннее устройство "незаметно и безболезненно" для пользователей l.
- Хорошая абстракция делает код более понятным и повышает его читабельность. Теперь, видя в коде l, мы однозначно понимаем, что здесь происходит. Тогда как в ином случае, цепляясь глазом за множественные, разбросанные среди строк cons, мы каждый раз должны будем определять, что же делает данная конкретная пара: то ли создаёт список, то ли хранит временные данные, то ли печёт пирожки ?! ;)
Посмотрите на «безликие» нагромождения пар, создающие разные сущности, которые мы проходили в прошлых курсах. Только комментарии помогают понять, что здесь происходит.
А вот как выглядит код с введёнными абстракциями. Комментарии здесь уже не нужны:
Естественно, введение функции устраняет дублирование кода. Каждый раз писать при создании списка такое нагромождение кода в виде вложенных cons, демонстрируя при этом все внутренности создаваемой конструкции, — не лучший стиль написания кода.
<!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 19:54:45 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="FYgvlHXZKj-hU4mXxZbhVmXUhc5_vH-uwoEiVgNh-AD6WeSjh6eHXxcQrQ_JmREhpd2oZHeLgQx_YbgCUWYfbg";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>Представление последовательностей | PHP: Последовательности</title>
<meta name="description" content="Представление последовательностей / PHP: Последовательности: Вспоминаем пары и рассматриваем представление списков с помощью пар">
<link rel="canonical" href="https://ru.hexlet.io/courses/php-sequences/lessons/list/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Представление последовательностей">
<meta property="og:title" content="PHP: Последовательности">
<meta property="og:description" content="Представление последовательностей / PHP: Последовательности: Вспоминаем пары и рассматриваем представление списков с помощью пар">
<meta property="og:url" content="https://ru.hexlet.io/courses/php-sequences/lessons/list/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="fAk1zOAdzUM0-NSQts2N8mtdJBdH_ur3TDaTmVnJ7N6T2P77EmNgI4K78Ai6wn2Fq1QJvU_JFFXx1gnNC84LsA" />
<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/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MywicHVyIjoiYmxvYl9pZCJ9fQ==--61c43d4881ca8feecc6f37dfafdc4e304f34b52f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Software%20engineer-bro.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-26T19:54:45.341Z","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":"Mj5C9hQeQBxYbjrtr6JgLXxtq_GZ4H868Uy9GNkEhkfd74nB5mDtfO4tHnWjrZBavGSGW5HXgZhMrCdMiwNhKQ","topics":[{"id":45938,"title":"Добрый вечер.\n\nhttps://ru.hexlet.io/code_reviews/298489\n\nОтслеживает ли программа, которая проверяет правильно ли сделано упражнение, какой процесс (рекурсивный или итеративный) генерирует функция, которую я написал?\n\nИли это будет лежать на моей совести, если функция породит другой процесс, а не тот, который указан в задании, и упражнение пройдёт проверку.\n\nОстальные функции уже прошли тесты. Уже доделываю функцию concat(). Меня не покидает ощущение, что я обхожу какие-то негласные правила.\n\nНе хотелось бы невольно сжульничать и сделать упражнение не так, как задумано составителем курса.","plain_title":"Добрый вечер. https://ru.hexlet.io/code_reviews/298489 Отслеживает ли программа, которая проверяет правильно ли сделано упражнение, какой процесс (рекурсивный или итеративный) генерирует функция, которую я написал? Или это будет лежать на моей совести если функция породит другой процесс, а не тот, который указан в задании, и упражнение пройдёт проверку. Остальные функции уже прошли тесты. Уже доделываю функцию concat(). Меня не покидает ощущение, что я обхожу какие-то негласные правила. Не хотелось бы невольно сжульничать и сделать упражнение не так как задумано составителем курса. ","creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":99226,"body":"Приветствую, Михаил!\n\nВсё что происходит при тестировании решения вы можете увидеть в файле с тестами — _tests/SolutionsTest.php_. Тесты проверяют результаты работы реализуемых по заданию функций. Так как для решения одной и той же задачи в программировании может быть множество вариантов, мы не можем тестировать особенности реализации, только результат работы. Если для решения рекомендуется использовать особый подход или не следует использовать какие-то функции, для отработки изучаемого материала, то об этом прямо говорится в условиях задачи.","topic_id":45938},{"creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"id":99575,"body":"https://ru.hexlet.io/code_reviews/298489\n\nДобрый день. \n\nВ условии задачи не указано какой процесс должен генерироваться при выполнении функции has(). Поэтому я написал её так, как посчитал нужным. Без оглядки на тип процесса.\n\nВ условии указано, что функцию reverse() необходимо реализовать так, чтобы был использован итеративный процесс. При этом не указано, в какой парадигме действовать: в декларативной или императивной. Поэтому будем считать, что моё решение соответствует определению итеративного процесса (начальное состояние, проверка окончания процесса, получение нового состояния), хотя это и реализовано через цикл, то есть императивно. \n\nОстались только вопросы по функции concat().\n\nВ условии написано:\n\n_Реализуйте функцию concat(), которая соединяет два списка, используя **рекурсивный процесс** (попробуйте сначала представить, как работала бы функция copy(), которая принимает на вход список и возвращает его копию)._\n\nФункция copy() должна возвращать список.\n\nСама функция copy() должна генерировать рекурсивный процесс или можно в неё добавить вспомогательную функцию и уже она будет генерировать рекурсивный процесс, возвращая значения, попутно добавляя в массив, а потом уже из массива эти значения передать в функцию l().\n\nИли необходимо сразу накапливать значения через функцию l()?","topic_id":45938},{"creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"id":99232,"body":"**Роман Ашиков**, спасибо.","topic_id":45938}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":46380,"title":"Добрый день.\n\nВ тексте задания написано, что _все создаваемые функции, в рамках этого задания, должны быть реализованы независимо друг от друга, то есть их нельзя использовать для реализации друг друга._\n\nНо также указано, что прежде чем реализовывать функцию concat(), следует _попробовать сначала представить, как работала бы функция copy(), которая принимает на вход список и возвращает его копию_\n\nМожно ли в рамках этого задания для реализации функции concat() сначала реализовать функцию copy(), а потом использовать её в concat()?\n\nЕсли да, то она тоже должна использовать рекурсивный процесс?","plain_title":"Добрый день. В тексте задания написано, что все создаваемые функции, в рамках этого задания, должны быть реализованы независимо друг от друга, то есть их нельзя использовать для реализации друг друга. Но также указано, что прежде чем реализовывать функцию concat(), следует попробовать сначала представить, как работала бы функция copy(), которая принимает на вход список и возвращает его копию Можно ли в рамках этого задания для реализации функции concat() сначала реализовать функцию copy(), а потом использовать её в concat()? Если да, то она тоже должна использовать рекурсивный процесс? ","creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":100123,"body":"Приветствую, Михаил!\n\nВ условиях речь о том, что реализация `copy()` очень похожа на `concat()` и нужно будет чуть-чуть изменить её определение, чтобы она начала объединять списки. Использовать `copy()` внутри `concat()` не нужно. Да, функция будет рекурсивной.","topic_id":46380}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":60325,"title":"Добрый день! \nВроде функция reverse заботает, но не до конца раскрывается список. \nПрошу помочь разобраться, в чем проблема: \nhttps://ru.hexlet.io/code_reviews/485845","plain_title":"Добрый день! Вроде функция reverse заботает, но не до конца раскрывается список. Прошу помочь разобраться, в чем проблема: https://ru.hexlet.io/code_reviews/485845 ","creator":{"public_name":"Yana Kramareva","id":377011,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":127392,"body":"Попробуйте использовать `l()` вместо рекурсивного вызова `concat()`. Что получится?\n\nПодсказка, которую я дал выше, подходит именно к вашей ситуации. Изначально функции друг от друга не зависят. Но всё же поменял прядок вызова в тестах.","topic_id":60325},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":127283,"body":"Попробуйте сначала реализовать функцию `concat()`. Она как раз подойдёт для слияния списков.","topic_id":60325},{"creator":{"public_name":"Yana Kramareva","id":377011,"is_tutor":false},"id":127376,"body":"Добрый день! Подскажите, пожалуйста, зачем в функции concat реализовывать рекурсивный процесс? \nУ нас же есть функция l(), которая соединяет списки. \nТ.к. функция concat следует за функцией reverse, то тестирование прекращается при падении функции reverse.\nДо concat дело не доходит. \nПодскажите, пожалуйста, что я делаю не так? \nhttps://ru.hexlet.io/code_reviews/485845","topic_id":60325}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}},{"id":46143,"title":"https://ru.hexlet.io/code_reviews/298489\n\nДобрый день. \n\nВ условии задачи не указано какой процесс должен генерироваться при выполнении функции has(). Поэтому я написал её так, как посчитал нужным. Без оглядки на тип процесса.\n\nВ условии указано, что функцию reverse() необходимо реализовать так, чтобы был использован итеративный процесс. При этом не указано, в какой парадигме действовать: в декларативной или императивной. Поэтому будем считать, что моё решение соответствует определению итеративного процесса (начальное состояние, проверка окончания процесса, получение нового состояния), хотя это и реализовано через цикл, то есть императивно. \n\nОстались только вопросы по функции concat().\n\nВ условии написано:\n\n_Реализуйте функцию concat(), которая соединяет два списка, используя **рекурсивный процесс** (попробуйте сначала представить, как работала бы функция copy(), которая принимает на вход список и возвращает его копию)._\n\nФункция copy() должна возвращать список.\n\nСама функция copy() должна генерировать рекурсивный процесс или можно в неё добавить вспомогательную функцию и уже она будет генерировать рекурсивный процесс, возвращая значения, попутно добавляя в массив, а потом уже из массива эти значения передать в функцию l()?\n\nИли необходимо сразу накапливать значения через функцию l()?","plain_title":"https://ru.hexlet.io/code_reviews/298489 Добрый день. В условии задачи не указано какой процесс должен генерироваться при выполнении функции has(). Поэтому я написал её так, как посчитал нужным. Без оглядки на тип процесса. В условии указано, что функцию reverse() необходимо реализовать так, чтобы был использован итеративный процесс. При этом не указано, в какой парадигме действовать: в декларативной или императивной. Поэтому будем считать, что моё решение соответствует определению итеративного процесса (начальное состояние, проверка окончания процесса, получение нового состояния), хотя это и реализовано через цикл, то есть императивно. Остались только вопросы по функции concat(). В условии написано: Реализуйте функцию concat(), которая соединяет два списка, используя рекурсивный процесс (попробуйте сначала представить, как работала бы функция copy(), которая принимает на вход список и возвращает его копию). Функция copy() должна возвращать список. Сама функция copy() должна генерировать рекурсивный процесс или можно в неё добавить вспомогательную функцию и уже она будет генерировать рекурсивный процесс, возвращая значения, попутно добавляя в массив, а потом уже из массива эти значения передать в функцию l(). Или необходимо сразу накапливать значения через функцию l()? ","creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":99618,"body":"Добрый день, Михаил!\n\nЯ исправил условия задачи по функции `reverse()`. Спасибо за фидбек! Имеется в виду итеративный процесс с использованием рекурсии.\n\n> Сама функция copy() должна генерировать рекурсивный процесс или можно в неё добавить вспомогательную функцию и уже она будет генерировать рекурсивный процесс, возвращая значения, попутно добавляя в массив, а потом уже из массива эти значения передать в функцию l()?\n\nНужно разобраться в разнице между рекурсивным процессом и итеративным рекурсивным процессом. Во втором случае как раз используется вспомогательная функция, которая на каждом шаге подготавливает результат и передаёт его дальше в следующий рекурсивный вызов.\n\nПо рекурсивному и итеративному процессу рекомендую, если вы еще не читали, статью в нашем блоге — [Рекурсия, рекурсивный процесс и итеративный процесс](https://ru.hexlet.io/blog/posts/recursive)\n\n`concat()` предлагается реализовать без использования внутренней функции. Подскажу, что можно рекурсивно собрать итоговый список с помощью `cons()`. Нужно лишь передать в эту функцию правильные аргументы.\n\n","topic_id":46143},{"creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"id":99674,"body":"**Роман Ашиков**, благодарю. Продвинулся в решении задачи: https://ru.hexlet.io/code_reviews/298489\n\nСтатью прочитал.\nДогадался какие нужны аргументы для функции cons().","topic_id":46143},{"creator":{"public_name":"Михаил Сидаченко","id":278872,"is_tutor":false},"id":99732,"body":"Роман, к функции concat() ещё не приступал. Пока только переделал реализацию функции reverse().\n\n","topic_id":46143},{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":99687,"body":"Михаил, вам удалось решить задачу? В ревью не вижу реализацию функции `concat()`.","topic_id":46143}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Представление последовательностей","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":1228,"slug":"php_sequences_list_exercise","name":null,"state":"active","kind":"exercise","language":"php","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"Все создаваемые функции, в рамках этого задания, должны быть реализованы независимо друг от друга, то есть их нельзя использовать для реализации друг друга.\n\n## src/solution.php\n\nРеализуйте 3 функции:\n\n* `has()` – проверяет, является ли переданное значение элементом списка\n* `concat()` – соединяет два списка, используя рекурсивный процесс (попробуйте сначала представить, как работала бы функция `copy()`, которая принимает на вход список и возвращает его копию)\n* `reverse()` – переворачивает список, используя итеративный процесс\n\n```php\n<?php\n\nuse function Php\\Pairs\\Data\\Lists\\l;\n\nuse function App\\Solution\\has;\nuse function App\\Solution\\reverse;\nuse function App\\Solution\\concat;\n\n$numbers = l(3, 4, 5, 8);\n$numbers2 = l(3, 2, 9);\n\nhas($numbers, 8); // true\nhas($numbers, 0); // false\nreverse($numbers2); // (9, 2, 3)\nconcat($numbers, $numbers2); // (3, 4, 5, 8, 3, 2, 9)\n```\n","prepared_readme":"Все создаваемые функции, в рамках этого задания, должны быть реализованы независимо друг от друга, то есть их нельзя использовать для реализации друг друга.\n\n## src/solution.php\n\nРеализуйте 3 функции:\n\n* `has()` – проверяет, является ли переданное значение элементом списка\n* `concat()` – соединяет два списка, используя рекурсивный процесс (попробуйте сначала представить, как работала бы функция `copy()`, которая принимает на вход список и возвращает его копию)\n* `reverse()` – переворачивает список, используя итеративный процесс\n\n```php\n<?php\n\nuse function Php\\Pairs\\Data\\Lists\\l;\n\nuse function App\\Solution\\has;\nuse function App\\Solution\\reverse;\nuse function App\\Solution\\concat;\n\n$numbers = l(3, 4, 5, 8);\n$numbers2 = l(3, 2, 9);\n\nhas($numbers, 8); // true\nhas($numbers, 0); // false\nreverse($numbers2); // (9, 2, 3)\nconcat($numbers, $numbers2); // (3, 4, 5, 8, 3, 2, 9)\n```\n","has_solution":true,"entity_name":"Представление последовательностей"},"units":[{"id":3866,"name":"theory","url":"/courses/php-sequences/lessons/list/theory_unit"},{"id":3867,"name":"quiz","url":"/courses/php-sequences/lessons/list/quiz_unit"},{"id":3885,"name":"exercise","url":"/courses/php-sequences/lessons/list/exercise_unit"}],"links":[],"ordered_units":[{"id":3866,"name":"theory","url":"/courses/php-sequences/lessons/list/theory_unit"},{"id":3867,"name":"quiz","url":"/courses/php-sequences/lessons/list/quiz_unit"},{"id":3885,"name":"exercise","url":"/courses/php-sequences/lessons/list/exercise_unit"}],"id":1780,"slug":"list","state":"approved","name":"Представление последовательностей","course_order":200,"goal":"Вспоминаем пары и рассматриваем представление списков с помощью пар","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"## Пары\n\nНаше погружение в последовательности мы начнём с того, что проработаем уровень абстракции для работы со списками. Давайте вспомним, как работают пары. С помощью пар, а конкретно функции-конструктора `cons`, мы можем соединять, например, числа:\n\n```php\n<?php\n\nuse function Php\\Pairs\\Pairs\\cons;\nuse function Php\\Pairs\\Pairs\\toString;\n\ncons(5, 8);\n\n$pair = cons(5, cons(2, 9));\n```\n\nТакже можем соединять любые объекты данных, включая те же пары. В данном примере мы соединяем 5 как число и другую пару. Если мы распечатаем, то увидим, что у пары такое представление:\n\n```php\n<?php\n\ntoString($pair); // (5, (2, 9))\n```\n\n### Способы соединить 1, 2, 3 и 4\n\n\n\nСуществует более одного способа соединять различные числа, т.е. представлять их разными структурами данных. Например, числа 1, 2, 3, 4 можно соединить в единую иерархическую конструкцию, как на примере слева на так называемой стрелочной диаграмме. Мы видим, что в паре на самом верху левый элемент является парой, правый — тоже пара и в каждой из этих пар элементы представлены конкретными числами.\n\nПри этом может быть и совершенно другой вариант, в котором левый элемент является парой, а правый, например, является числом 4. Левый элемент в свою очередь делится на элемент, в котором есть число и элемент, являющийся парой и т.д. Мы можем это делать бесконечно много раз.\n\nПод диаграммами описано представление в виде кода с помощью вложенных `cons`. Причём слева симметричное вложение, а справа немного сдвинутое.\n\nТо, что мы получили, называется — иерархической структурой. Её создание обеспечивается возможностью, которая называется — **свойство замыкания (множества)**. Чтобы понять, что это обозначает, давайте познакомимся с понятием множества.\n\n### Что такое множество\n\n\n\nМножество — это довольно интуитивное понятие. Для математики оно является одним из ключевых. Через множество определено множество вещей. Определение чистых математических функций, с которыми мы работаем, полностью построено на теоретико-множественном подходе. Так вот, что такое множество? Множество — это какой-то набор. Мы можем сказать, что это совокупность объектов данных. Например, в данном случае мы видим, что это числа. Различные типы чисел. При этом видно, что здесь множества вложены друг в друга, т.е. у множеств есть отношения друг с другом, существуют операции над множествами, например, пересечение, объединение и т.д. Причём это не просто какая-то математическая теория. Множества очень активно используются в практике программирования. Те же самые базы данных SQL основаны на реляционной алгебре, которая очень тесно связанна с теорией множеств. И те же джойны, которые используются в SQL следует воспринимать и анализировать именно на основе понимания теории множеств.\n\nКроме этого, поскольку мы говорим о парах, мы можем себе вообразить некоторое множество, которое называется **множество всех пар**. Сейчас нам это понадобится.\n\n### Замыкание (абстрактная алгебра)\n\nЭто не то замыкание, про которое мы уже говорили, связанное с запоминанием контекста. Это замыкание из математики, из абстрактной алгебры и звучит оно примерно так:\n\n_Множество замкнуто относительно операции, если применение операции к элементам этого множества даёт результат, который так же является элементом этого множества._\n\nВозможно, это слишком формальное определение, которое звучит очень абстрактно. На самом деле оно крайне простое. Всё, что оно означает: используя функцию `cons`, множество всех пар замкнуто относительно функции `cons`. Почему? Потому что когда вы с помощью функции `cons` что-либо объединяете, вы снова получаете пару. Если полученную пару снова объединить функцией `cons` в ещё одну пару, получится, опять же, пара. Но как мы помним, множество всех пар включает все возможные виды пар. Какую бы пару вы ни создали, она будет туда включена, т.е. функция `cons` всегда порождает пару. Если бы в какой-то момент она порождала что-то другое, то не существовало бы замкнутости. Замкнутость присутствует именно потому, что применение этой функции к абсолютно любым элементам этого множества порождает элемент этого же множества. А свойство замыкания является ключевым фактором в возможности построения иерархических структур.\n\nИерархические структуры — это такие структуры, части которых содержат в себе ещё более элементарные части, которые в свою очередь содержат ещё более элементарные части.\n\n```php\n<?php\n\ncons(cons(3, 5), cons(2, 9));\n```\n\nВ примере выше `cons` соединяет не просто какие-то элементарные части. Она соединяет пары, которые в свою очередь состоят из частей и так далее. Это может происходить бесконечно, опять же, благодаря свойству замыкания.\n\n### Последовательности\n\n\n\nКак же всё-таки проще всего представить последовательности? Мы используем стрелочную диаграмму, которая показывает самый простой, но не единственный способ представления последовательностей. На верхнем уровне у нас будет пара, первый элемент которой объект данных, например, число, второй элемент — это фактически ссылка на другую пару, в которой опять же первый элемент — объект данных и дальше опять ссылка на следующую пару. И так далее до самой последней вложенной пары, где первый элемент — также данные, а второй элемент - специальный маркер, который обозначает, что список закончился, и дальше смотреть не нужно.\n\nКак это записывается кодом? По сути — это последовательность cons'ов, которые вложены друг в друга.\n\n```php\n<?php\n\ncons(1,\n cons(2,\n cons(3,\n cons(4, null))));\n```\n\nМы приняли за аксиоматический факт, что у последнего элемента маркер окончания списка это `null`. Если мы его встречаем значит дальше проверять и смотреть не нужно, список закончился.\n\n\n### Интерфейс\n\n```php\n<?php\n\nuse function Php\\Pairs\\Data\\Lists\\l;\nuse function Php\\Pairs\\Data\\Lists\\cons;\nuse function Php\\Pairs\\Data\\Lists\\head;\nuse function Php\\Pairs\\Data\\Lists\\tail;\nuse function Php\\Pairs\\Data\\Lists\\isEmpty;\nuse function Php\\Pairs\\Data\\Lists\\toString;\n\n// cons(1, cons(2, cons(3, cons(4, null))));\n$list = l(1, 2, 3, 4);\n\ncons(10, $list); // (10, 1, 2, 3, 4)\ntoString($list); // (1, 2, 3, 4)\n\nhead($list); // 1\ntail($list); // l(2, 3, 4)\n\n// list === null\nisEmpty(l(4)); // false\nisEmpty(l()); // true\n```\n\nДавайте рассмотрим интерфейс работы со списками. Начиная с текущего момента и на протяжении всего курса, мы будем постоянно с ними работать, строить поверх них абстракции. Поэтому нам нужно научиться выполнять какие-то базовые операции со списками и построить данный слой абстракции поверх пар.\n\nЕсли бы мы действовали в лоб, то создание списка было бы примерно таким:\n\n```php\n<?php\n\ncons(1, cons(2, cons(3, cons(4, null))));\n```\n\nНо как видите, это довольно громоздкая запись, здесь слишком много шума и сложно увидеть реальную структуру списка. А ведь список состоит всего из четырёх чисел. Поэтому мы сделали функцию-конструктор для создания списков:\n\n```php\n<?php\n\n$list = l(1, 2, 3, 4);\n```\n\nЭто маленькая функция, которая принимает на вход просто числа через запятую и возвращает список. Т.е. она внутри делает то, что описывает код выше. Функция `l` работает с переменным числом аргументов (оно не задано жёстко и вы можете передавать любое количество). Внутри она имеет кое-какие особенности, с которыми мы ещё не знакомы и чтобы их изучить, нужно продвинуться вперёд гораздо дальше. Поэтому просто используйте её, как чёрный ящик, представляя, что вызывая конструктор `l(1, ...)` вы получаете запись `cons(1, ...)`.\n\nПочему мы сделали именно так? Назвали конструктор `l` одной буквой. Всё очень просто. Потому что мы часто будем строить вложенные списки и это очень просто делать, если у вас короткое название функции. Кстати, если бы оно было длинное, то вложенные функции банально привели бы к распуханию этой структуры. При этом для добавления элемента в список мы используем функцию `cons`. Но эта функция будет определена уже в списках, т.е. она не является функцией пар по той причине, что это другой слой абстракции. Если мы посмотрим на внутреннюю структуру, то увидим следующее:\n\n```php\n<?php\n\ncons(10, $list); // (10, 1, 2, 3, 4)\n```\n\nЕсли распечатать список, то можно увидеть, что десятки здесь уже нет:\n\n```php\n<?php\n\ntoString($list); // (1, 2, 3, 4)\n```\n\nЧто это означает? Это означает, что у нас присутствует неизменяемость. Т.е. добавление в список, как это было с парами, не изменяет его, а возвращает новый список. Поэтому, если вы хотите его использовать, то необходимо создать, например, `$list2`.\n\nИ ещё несколько базовых операций, которые обязательно будут нужны для работы со списками. Они очень похожи на `car` и `cdr`. Первая из них — это `head`, так называемая голова. Данная функция берёт из списка первый элемент. Когда идёт работа со списками, существует понятийный аппарат, в котором присутствует и данное определение.\n\n1. Первый элемент списка (т.е. последний добавленный) называется \"голова\", а всё остальное — это хвост. Соответственно, `head` — это взять голову, `tail` — это взять хвост. Хвост — это список минус первый элемент. Мы говорим первый, так как находится всегда слева, но по сути это то, что добавляется в список последним. В данном случае `tail($list)`, учитывая, что наш список был `l(1, 2, 3, 4)`, это список `l(2, 3, 4)`. Если мы будем применять к списку `tail` много раз, то постепенно будем получать список меньшего размера, пока в конце концов не получим `null`.\n\nЕщё одна функция — это `isEmpty`. Она проверяет пустой ли список, т.е. внутри она делает такую проверку:\n\n```php\n<?php\n\n// list === null\nisEmpty(l(4)); // false\nisEmpty(l()); // true\n```\n\nВидно, что если мы передаём просто конструктор, вызванный без параметров, то `isEmpty` считает, что это пустой список. Если хотя бы один параметр был передан, то список уже не пустой.\n\nПочему мы не делаем такую проверку `$list === null`, а вводим новую функцию? Думаю вам уже должно быть понятно. Мы ведь всё время говорим о том, что необходимо использовать абстракцию. То, что внутри используется `null` — это всего лишь особенность реализации. Сегодня `null`, завтра что-то ещё. И если вы используете те функции, которые предоставляет библиотека, вам не придётся в будущем переписывать весь код.\n\n## Повышаем уровень абстракции\n\nВ этом уроке мы реализовали [абстрактный тип данных](https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)) **Список** на основе ранее пройденных **пар**. Ключевой особенностью списка является его интерфейс, а именно функции, реализующие такие операции, как \"получить голову\" (возвращает первый элемент списка), \"получить хвост\" (возвращает новый список, полученный из исходного списка отсечением у него первого элемента), \"добавить новую голову\" (добавление элемента в начало списка). И здесь следует обратить внимание на несколько вещей.\n\nВо-первых, не стоит отождествлять между собой пары и списки. Пары в данном случае были использованы как подходящий _инструмент_ для создания списка. **Абстрактный список** (см. первый абзац) может быть реализован в **конкретных структурах данных**. В нашем случае это [односвязный список](https://ru.wikipedia.org/wiki/Связный_список#.D0.9E.D0.B4.D0.BD.D0.BE.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA_.28.D0.BE.D0.B4.D0.BD.D0.BE.D0.BD.D0.B0.D0.BF.D1.80.D0.B0.D0.B2.D0.BB.D0.B5.D0.BD.D0.BD.D1.8B.D0.B9_.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA.29). Важной характеристикой этой структуры данных является то, что каждый элемент списка, помимо определённого хранимого значения (число, строка, дата, адрес, имя и любая другая информация), содержит ссылку на другой такой же по структуре элемент. Таким образом, между элементами списка существует **связь**, и мы можем последовательно \"путешествовать\" (перемещаться) от текущего элемента к следующему, от начала списка к его **концу**.\n\nИ, как видим, пары вполне позволяют реализовать особенности списка как структуры данных. Ведь пара, помимо основного значения, может содержать ещё другую пару аналогичной структуры (значение + ссылка на другую пару) — и такая последовательность может длиться бесконечно.\n\n```php\n<?php\n\ncons(1, cons(2, cons(3, cons(4, cons(5, ...\ncons('one', cons('two', cons('three', cons('four', cons('five', ...\n```\n\nОднако, надо понимать, что сделать это можно не только с помощью пар. Cписки можно представить на основе массивов, а также некоторых других типов данных. Поэтому, если мы пользуемся **списком**, то к нему нельзя напрямую применять функции по работе с парами даже если мы знаем, что данный конкретный список состоит из пар. Для этого вводятся специальные функции для работы со списками, учитывающие его внутреннюю реализацию, которая может изменяться.\n\nВо-вторых, коль скоро мы реализовали список именно на основе **пар**, то следует понимать, что не каждая пара, с которой мы имеем дело, какой бы «сложной» она ни была, является списком. У списка есть начало и конец, и мы должны иметь возможность последовательно перемещаться от одного элемента к другому. Для того, чтобы понять, что мы достигли последнего элемента списка и перемещаться дальше уже нет смысла, мы вводим специальный **маркер конца списка** — пустую пару `l()`. По предварительному соглашению в роли маркера могут также выступать другие подходящие значения, например, `null`. Как только мы встречаем очередную пару, в `cdr` которой находится пустая пара, это означает, что очередное значение списка, лежащее в `car` пары, является **последним элементом списка**.\nОтметим также, что у пустого списка (списка, в котором нет ни одного элемента) не может быть ни головы, ни хвоста. Поэтому соответствующие операции, примененные к пустому списку (взятие `head` или `tail`), приводят к возникновению ошибки. Чтобы её избежать, вводят функцию для проверки списка на «пустоту» (например, `isEmpty`), а далее берут голову или хвост у списка, предварительно убедившись, что он не является пустым.\n\nНиже приведены примеры пар, которые нельзя назвать списками. Они не реализуют требования, предъявляемые к односвязным спискам: в них отсутствует последовательная связь между элементами и/или нет маркера конца списка:\n\n```php\n<?php\n\ncons(1, cons(cons(3, null), 2));\ncons(1, cons(2, cons(3, 4)));\ncons(cons(1, 2), cons(3, cons(4, null)));\n```\n\nТеперь покажем пары, которыми мы можем смело пользоваться как списками:\n\n```php\n<?php\n\ncons(1, cons(2, cons(3, null)));\ncons('This', cons('is', cons('a', cons('list', null))));\n```\n\nВ уроке мы отметили, что следующие две записи, конструирующие список, являются эквивалентными:\n\n```php\n<?php\n\ncons(1, cons(2, cons(3, cons(4, cons(5, null))))); // (1, 2, 3, 4, 5)\nl(1, 2, 3, 4, 5); // (1, 2, 3, 4, 5)\n```\n\nДля генерации списка мы создали функцию `l`, несмотря на то, что у нас уже есть конструктор `cons`. Это необходимо по нескольким причинам. Отметим наиболее важные из них:\n\n- Выделив код создания списка в отдельную функцию и дав ей имя `l` (сокращ. от `list`), мы ввели отдельную и понятную абстракцию, отвечающую за создание **списков** (и ничего другого!). Выше говорилось, что списки могут быть реализованы не только на парах, но и на массивах, а также других типах данных. Так вот, наша абстракция `l` позволяет отвлечься от внутреннего устройства списка. Тот, кто будет пользоваться этой функцией, не обязан знать, какими способами она конструирует список, а в случае необходимости может изменить его внутреннее устройство \"незаметно и безболезненно\" для пользователей `l`.\n- Хорошая абстракция делает код более понятным и повышает его читабельность. Теперь, видя в коде `l`, мы однозначно понимаем, что здесь происходит. Тогда как в ином случае, цепляясь глазом за множественные, разбросанные среди строк `cons`, мы каждый раз должны будем определять, что же делает данная конкретная пара: то ли создаёт список, то ли хранит временные данные, то ли печёт пирожки ?! ;)\n\nПосмотрите на «безликие» нагромождения **пар**, создающие разные сущности, которые мы проходили в прошлых курсах. Только комментарии помогают понять, что здесь происходит.\n\n```php\n<?php\n\ncons(5, cons(2, cons(7, cons(11, cons(6, cons(14, null)))))); // создание списка\ncons(3, 17); // создание точки\ncons(8, 17); // создание рационального числа\ncons(cons(3, 33), cons(-2, -22)); // создание отрезка\n$rectangle = cons(cons(-2, 23), cons(5, 11)); // создание прямоугольника\n2 * (car(cdr($rectangle)) + cdr(cdr($rectangle))); // вычисление периметра прямоугольника\n```\n\nА вот как выглядит код с введёнными абстракциями. Комментарии здесь уже не нужны:\n\n```php\n<?php\n\nlist(5, 2, 7, 11, 6, 14);\nmakePoint(3, 17);\nmakeRational(8, 17);\nmakeSegment(makePoint(3, 33), makePoint(-2, -22));\n$point = makePoint(-2, 23);\n$rectangle = makeRectangle($point, 5, 11);\nperimeter($rectangle);\n```\n\nЕстественно, введение функции устраняет дублирование кода. Каждый раз писать при создании списка такое нагромождение кода в виде вложенных `cons`, демонстрируя при этом все внутренности создаваемой конструкции, — не лучший стиль написания кода.\n\n```php\n<?php\n\nuse function Php\\Pairs\\Data\\Lists\\l;\nuse function Php\\Pairs\\Data\\Lists\\head;\nuse function Php\\Pairs\\Data\\Lists\\tail;\n\n$list = l(5, 3, 9);\nprint_r(head(tail($list))); // => 3\n```\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":3865,"name":"theory","url":"/courses/php-sequences/lessons/intro/theory_unit"}],"links":[{"id":424109,"name":"Библиотека для работы с парами php-pairs","url":"https://github.com/hexlet-components/php-pairs"}],"ordered_units":[{"id":3865,"name":"theory","url":"/courses/php-sequences/lessons/intro/theory_unit"}],"id":1779,"slug":"intro","state":"approved","name":"Введение","course_order":0,"goal":"Знакомимся с курсом и проектом «Генератор HTML», который будем постепенно разрабатывать на курсе","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Этот курс — логическое продолжение предыдущего курса про [Составные данные](https://ru.hexlet.io/courses/php-compound-data). Теперь мы будем говорить о составных данных в чуть более сложном и продвинутом их виде. Как это часто бывает на Хекслете, данный курс будет не про PHP и не про изучение его возможностей, а про некоторые фундаментальные вещи, которые обязан знать программист, не зависимо от того, на каком языке он пишет код. PHP в данном случае всего лишь способ выражения той идеи, которую мы хотим донести.\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```php\n<?php\n\nuse function Php\\Html\\Tags\\HtmlTags\\make;\nuse function Php\\Html\\Tags\\HtmlTags\\append;\nuse function Php\\Html\\Tags\\HtmlTags\\addChild;\nuse function Php\\Html\\Tags\\HtmlTags\\toString;\nuse function Php\\Html\\Tags\\HtmlTags\\node;\n\n$ul1 = node('ul');\n$ul2 = addChild($ul1, node('li', 'hello'));\n$ul3 = addChild($ul1, node('li', 'world'));\n\n$html1 = make();\n$html2 = append($html1, $ul3);\n```\n\nСейчас не обязательно пытаться полностью понять данный код. Он иллюстрирует, что в конечном итоге получится библиотека, в которой есть обычные функции, позволяющие нам строить древовидную структуру. Которая с одной стороны список,а с другой может быть сложней, чем список. Например, она может быть деревом, о чём мы поговорим позже. В конечном итоге после того, как мы создали эту структуру, мы можем её распечатать и увидеть, что генерируется такое представление:\n\n```php\n<?php\n\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```php\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\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Код который вы видите выше написан на JavaScript. Здесь как раз показан пример того, как в браузере происходит ма��ипуляция DOM, т.е. реальным представлением HTML, как он представлен в программном коде. Опять же, не нужно рассчитывать на то, что вы сейчас поймёте этот код. Важно, что та библиотека, которую мы разрабатываем, является по сути локальным представлением того, что происходит в браузере. Зная принципы её работы, умея самостоятельно её написать, вы будете легко ориентироваться в том, как это происходит в браузере. По сути мы делаем прототип настоящего DOM. Конечно, сложность очень сильно отличается, потому что DOM в браузере очень навороченная вещь. Наша библиотека гораздо проще, но главное это понять общий принцип.\n\n### Темы\n\n- списки, множества, деревья\n- отображение, фильтрация, агрегация\n- стандартные интерфейсы\n- уровневое проектирования\n\n##### Какие темы мы рассмотрим в этом курсе?\n\nИх много, и они достаточно серьёзные.\n\n1. Первое — это структуры данных. Мы рассмотрим списки, познакомимся с понятием множеств и с деревьями.\n\n2. Мы научимся с ними работать посредством тройки функций: отображение (`map`), фильтрация (`filter`) и агрегация (`reduce`). Это тройка методов, которые используются для обработки различных списков, множеств, деревьев и других структур данных, которые существуют в PHP. Когда в будущем мы будем работать с внутренними структурами языка, вы увидите, что это основные способы манипуляции структурами в PHP и, кстати, во всех функциональных языках и даже не в функциональных. Во всех достаточно продвинутых языках программирования, которые поддерживают функции высшего порядка, реализована эта тройка. А код с использованием данных методов является чаще всего каноническим, т.е. так принято и правильно писать, а не использовать, например, циклы, о чём мы позже обязательно поговорим.\n\n3. Мы познакомимся с таким подходом, как стандартные интерфейсы. Ярким примером данного подхода является конструктор для детей Lego.\n\n4. Также мы познакомимся с уровневым проектированием, которое касается не только программирования, а всей инженерной технической области. Именно оттуда оно берет начало и именно оттуда уровневому проектированию можно учиться.\n\n## Превосходство Хекслета\n\n- функциональный стиль и неизменяемость\n- СИКП\n- фокус на программировании, а не синтаксисе языка\n- двигаемся вперед не с помощью изучения новых фич языка, а путем комбинирования изученных инструментов и развития абстрактного мышления\n- нет нового синтаксиса\n\nНапоследок, несколько аспектов, почему курс сделан именно так. Почему и зачем мы освещаем эту тему на Хекслете.\n\nЭтот курс продолжает традицию предыдущего курса. Всё, что мы здесь делаем, будет неизменяемым. Мы используем функциональный стиль. Потому что введение состояния привносит множество сложностей и проблем, и на данном этапе для понимания темы это совершенно не нужно. Поэтому мы оставляем за скобками изменение. Библиотека может показаться вам немного странной, но чуть позже вы поймёте, почему она реализована именно так.\n\nПомимо этого курс продолжает традицию и также основан на СИКПе ([Структура и интерпретация компьютерных программ](https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book.html)). В нём отсутствует новый синтаксис. Мы в очередной раз подчёркиваем, что этот курс не про изучение PHP, он про изучение программирования. Пройдя его, вы действительно поймёте и, наконец, осознаете разницу между тем, что такое программировать и что такое знать синтаксис языка.\n\nВсё, что происходит в этом курсе — мы просто берём то, что уже изучили, начинаем это комбинировать и получаем какие-то новые возможности нашей системы, более сложное поведение. Данный подход развивает абстрактное мышление. Многие вещи, которые мы делаем в этом курсе, направлены не только на то, чтобы вы понимали, что такое программирование, но и постепенно начинали развивать свой мозг и тренировать его для того, чтобы он мог переваривать всё более и более сложные сущности и концепции, потому что изучение нового синтаксиса не привносит нового развития в части того, как вы размышляете. Оно не позволит вам автоматически строить действительно сложные программы. А вот абстрактное мышление — это как раз та вещь, которая влияет на это больше всего. Некоторые уроки и практические задания могут показаться весьма сложными, потому что в этом курсе будет много кода и он будет заставлять ваш мозг кипеть. В каком-то смысле это лакмусовая бумажка. Если вы пройдёте курс, действительно в нём разберётесь и поймёте его, то скорее всего дальнейшее обучение пройдет для вас достаточно легко и, в целом, в программировании у вас всё будет хорошо получаться.\n\nЖелаю удачи! Вперёд!\n"},"id":215,"slug":"php-sequences","challenges_count":10,"name":"PHP: Последовательности","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы познакомитесь с последовательностями на PHP. Вы узнаете о стандартных интерфейсах, уровневом проектировании и функциях высшего порядка. В итоге вы научитесь на практике использовать функции `array_map()`, `array_filter()` и `array_reduce()`. Знания из этого курса помогут проектировать функции так, чтобы их можно было легко соединять друг с другом.","kind":"additional","updated_at":"2026-01-20T11:45:36.099Z","language":"php","duration_cache":36000,"skills":["Строить сложные структуры данных на базе более простых","Проектировать функции так, чтобы их можно было легко соединять друг с другом","Обрабатывать коллекции представленные списками с помощью функций высшего порядка (map/filter/reduce)","Разделять код на уровни выстраивая правильное взаимодействие между слоями"],"keywords":["функции высшего порядка","стандартные интерфейсы","уровневое проектирование"],"lessons_count":9,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzM1NiwicHVyIjoiYmxvYl9pZCJ9fQ==--f4100696d17c00a34b5a10d844648f03da291aa3/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":28,"slug":"php-sicp","title":"СИКП на PHP","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4010,"duration_in_months":1},"id":44,"slug":"php-sicp","title":"СИКП на PHP","subtitle":"Навык понимать программы на глубоком уровне, уверенно проходить собеседования и решать сложные задачи","subtitle_for_lists":"Навык фундаментального понимания программ на PHP","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"php-sicp","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MywicHVyIjoiYmxvYl9pZCJ9fQ==--61c43d4881ca8feecc6f37dfafdc4e304f34b52f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Software%20engineer-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/php-sequences/lessons/list/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">PHP: Последовательности</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":"PHP: Последовательности"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><h2 id="heading-2-1">Пары</h2>
<p>Наше погружение в последовательности мы начнём с того, что проработаем уровень абстракции для работы со списками. Давайте вспомним, как работают пары. С помощью пар, а конкретно функции-конструктора <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>, мы можем соединять, например, числа:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
use function Php\Pairs\Pairs\cons;
use function Php\Pairs\Pairs\toString;
cons(5, 8);
$pair = cons(5, cons(2, 9));</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Также можем соединять любые объекты данных, включая те же пары. В данном примере мы соединяем 5 как число и другую пару. Если мы распечатаем, то увидим, что у пары такое представление:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
toString($pair); // (5, (2, 9))</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h3 id="heading-3-2">Способы соединить 1, 2, 3 и 4</h3>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzM1OCwicHVyIjoiYmxvYl9pZCJ9fQ==--43c8fdec5392f9969738e5bfe8692e9e2e58ec4a/cons.png" alt="Соединение пар" loading="lazy"/></p>
<p>Существует более одного способа соединять различные числа, т.е. представлять их разными структурами данных. Например, числа 1, 2, 3, 4 можно соединить в единую иерархическую конструкцию, как на примере слева на так называемой стрелочной диаграмме. Мы видим, что в паре на самом верху левый элемент является парой, правый — тоже пара и в каждой из этих пар элементы представлены конкретными числами.</p>
<p>При этом может быть и совершенно другой вариант, в котором левый элемент является парой, а правый, например, является числом 4. Левый элемент в свою очередь делится на элемент, в котором есть число и элемент, являющийся парой и т.д. Мы можем это делать бесконечно много раз.</p>
<p>Под диаграммами описано представление в виде кода с помощью вложенных <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>. Причём слева симметричное вложение, а справа немного сдвинутое.</p>
<p>То, что мы получили, называется — иерархической структурой. Её создание обеспечивается возможностью, которая называется — <strong>свойство замыкания (множества)</strong>. Чтобы понять, что это обозначает, давайте познакомимся с понятием множества.</p>
<h3 id="heading-3-3">Что такое множество</h3>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzM1OSwicHVyIjoiYmxvYl9pZCJ9fQ==--a60f74d1f8482608a9db80944fc8f6d67b52fdb0/numbers.png" alt="Множества чисел" loading="lazy"/></p>
<p>Множество — это довольно интуитивное понятие. Для математики оно является одним из ключевых. Через множество определено множество вещей. Определение чистых математических функций, с которыми мы работаем, полностью построено на теоретико-множественном подходе. Так вот, что такое множество? Множество — это какой-то набор. Мы можем сказать, что это совокупность объектов данных. Например, в данном случае мы видим, что это числа. Различные типы чисел. При этом видно, что здесь множества вложены друг в друга, т.е. у множеств есть отношения друг с другом, существуют операции над множествами, например, пересечение, объединение и т.д. Причём это не просто какая-то математическая теория. Множества очень активно используются в практике программирования. Те же самые базы данных SQL основаны на реляционной алгебре, которая очень тесно связанна с теорией множеств. И те же джойны, которые используются в SQL следует воспринимать и анализировать именно на основе понимания теории множеств.</p>
<p>Кроме этого, поскольку мы говорим о парах, мы можем себе вообразить некоторое множество, которое называется <strong>множество всех пар</strong>. Сейчас нам это понадобится.</p>
<h3 id="heading-3-4">Замыкание (абстрактная алгебра)</h3>
<p>Это не то замыкание, про которое мы уже говорили, связанное с запоминанием контекста. Это замыкание из математики, из абстрактной алгебры и звучит оно примерно так:</p>
<p><em>Множество замкнуто относительно операции, если применение операции к элементам этого множества даёт результат, который так же является элементом этого множества.</em></p>
<p>Возможно, это слишком формальное определение, которое звучит очень абстрактно. На самом деле оно крайне простое. Всё, что оно означает: используя функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>, множество всех пар замкнуто относительно функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>. Почему? Потому что когда вы с помощью функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code> что-либо объединяете, вы снова получаете пару. Если полученную пару снова объединить функцией <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code> в ещё одну пару, получится, опять же, пара. Но как мы помним, множество всех пар включает все возможные виды пар. Какую бы пару вы ни создали, она будет туда включена, т.е. функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code> всегда порождает пару. Если бы в какой-то момент она порождала что-то другое, то не существовало бы замкнутости. Замкнутость присутствует именно потому, что применение этой функции к абсолютно любым элементам этого множества порождает элемент этого же множества. А свойство замыкания является ключевым фактором в возможности построения иерархических структур.</p>
<p>Иерархические структуры — это такие структуры, части которых содержат в себе ещё более элементарные части, которые в свою очередь содержат ещё более элементарные части.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(cons(3, 5), cons(2, 9));</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В примере выше <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code> соединяет не просто какие-то элементарные части. Она соединяет пары, которые в свою очередь состоят из частей и так далее. Это может происходить бесконечно, опять же, благодаря свойству замыкания.</p>
<h3 id="heading-3-5">Последовательности</h3>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzM2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--a006b688d95c4ed61f000d2f36d7a67fe9bab351/sequence.png" alt="Последовательности" loading="lazy"/></p>
<p>Как же всё-таки проще всего представить последовательности? Мы используем стрелочную диаграмму, которая показывает самый простой, но не единственный способ представления последовательностей. На верхнем уровне у нас будет пара, первый элемент которой объект данных, например, число, второй элемент — это фактически ссылка на другую пару, в которой опять же первый элемент — объект данных и дальше опять ссылка на следующую пару. И так далее до самой последней вложенной пары, где первый элемент — также данные, а второй элемент - специальный маркер, который обозначает, что список закончился, и дальше смотреть не нужно.</p>
<p>Как это записывается кодом? По сути — это последовательность cons'ов, которые вложены друг в друга.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(1,
cons(2,
cons(3,
cons(4, null))));</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Мы приняли за аксиоматический факт, что у последнего элемента маркер окончания списка это <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">null</code>. Если мы его встречаем значит дальше проверять и смотреть не нужно, список закончился.</p>
<h3 id="heading-3-6">Интерфейс</h3>
<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"><?php
use function Php\Pairs\Data\Lists\l;
use function Php\Pairs\Data\Lists\cons;
use function Php\Pairs\Data\Lists\head;
use function Php\Pairs\Data\Lists\tail;
use function Php\Pairs\Data\Lists\isEmpty;
use function Php\Pairs\Data\Lists\toString;
// cons(1, cons(2, cons(3, cons(4, null))));
$list = l(1, 2, 3, 4);
cons(10, $list); // (10, 1, 2, 3, 4)
toString($list); // (1, 2, 3, 4)
head($list); // 1
tail($list); // l(2, 3, 4)
// list === null
isEmpty(l(4)); // false
isEmpty(l()); // true</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Давайте рассмотрим интерфейс работы со списками. Начиная с текущего момента и на протяжении всего курса, мы будем постоянно с ними работать, строить поверх них абстракции. Поэтому нам нужно научиться выполнять какие-то базовые операции со списками и построить данный слой абстракции поверх пар.</p>
<p>Если бы мы действовали в лоб, то создание списка было бы примерно таким:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(1, cons(2, cons(3, cons(4, null))));</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Но как видите, это довольно громоздкая запись, здесь слишком много шума и сложно увидеть реальную структуру списка. А ведь список состоит всего из четырёх чисел. Поэтому мы сделали функцию-конструктор для создания списков:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$list = l(1, 2, 3, 4);</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Это маленькая функция, которая принимает на вход просто числа через запятую и возвращает список. Т.е. она внутри делает то, что описывает код выше. Функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code> работает с переменным числом аргументов (оно не задано жёстко и вы можете передавать любое количество). Внутри она имеет кое-какие особенности, с которыми мы ещё не знакомы и чтобы их изучить, нужно продвинуться вперёд гораздо дальше. Поэтому просто используйте её, как чёрный ящик, представляя, что вызывая конструктор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l(1, ...)</code> вы получаете запись <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons(1, ...)</code>.</p>
<p>Почему мы сделали именно так? Назвали конструктор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code> одной буквой. Всё очень просто. Потому что мы часто будем строить вложенные списки и это очень просто делать, если у вас короткое название функции. Кстати, если бы оно было длинное, то вложенные функции банально привели бы к распуханию этой структуры. При этом для добавления элемента в список мы используем функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>. Но эта функция будет определена уже в списках, т.е. она не является функцией пар по той причине, что это другой слой абстракции. Если мы посмотрим на внутреннюю структуру, то увидим следующее:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(10, $list); // (10, 1, 2, 3, 4)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если распечатать список, то можно увидеть, что десятки здесь уже нет:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
toString($list); // (1, 2, 3, 4)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Что это означает? Это означает, что у нас присутствует неизменяемость. Т.е. добавление в список, как это было с парами, не изменяет его, а возвращает новый список. Поэтому, если вы хотите его использовать, то необходимо создать, например, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">$list2</code>.</p>
<p>И ещё несколько базовых операций, которые обязательно будут нужны для работы со списками. Они очень похожи на <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">car</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cdr</code>. Первая из них — это <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">head</code>, так называемая голова. Данная функция берёт из списка первый элемент. Когда идёт работа со списками, существует понятийный аппарат, в котором присутствует и данное определение.</p>
<ol>
<li>Первый элемент списка (т.е. последний добавленный) называется "голова", а всё остальное — это хвост. Соответственно, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">head</code> — это взять голову, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tail</code> — это взять хвост. Хвост — это список минус первый элемент. Мы говорим первый, так как находится всегда слева, но по сути это то, что добавляется в список последним. В данном случае <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tail($list)</code>, учитывая, что наш список был <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l(1, 2, 3, 4)</code>, это список <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l(2, 3, 4)</code>. Если мы будем применять к списку <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tail</code> много раз, то постепенно будем получать список меньшего размера, пока в конце концов не получим <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">null</code>.</li>
</ol>
<p>Ещё одна функция — это <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">isEmpty</code>. Она проверяет пустой ли список, т.е. внутри она делает такую проверку:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
// list === null
isEmpty(l(4)); // false
isEmpty(l()); // true</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Видно, что если мы передаём просто конструктор, вызванный без параметров, то <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">isEmpty</code> считает, что это пустой список. Если хотя бы один параметр был передан, то список уже не пустой.</p>
<p>Почему мы не делаем такую проверку <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">$list === null</code>, а вводим новую функцию? Думаю вам уже должно быть понятно. Мы ведь всё время говорим о том, что необходимо использовать абстракцию. То, что внутри используется <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">null</code> — это всего лишь особенность реализации. Сегодня <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">null</code>, завтра что-то ещё. И если вы используете те функции, которые предоставляет библиотека, вам не придётся в будущем переписывать весь код.</p>
<h2 id="heading-2-7">Повышаем уровень абстракции</h2>
<p>В этом уроке мы реализовали <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)" rel="noopener noreferrer" target="_blank">абстрактный тип данных</a> <strong>Список</strong> на основе ранее пройденных <strong>пар</strong>. Ключевой особенностью списка является его интерфейс, а именно функции, реализующие такие операции, как "получить голову" (возвращает первый элемент списка), "получить хвост" (возвращает новый список, полученный из исходного списка отсечением у него первого элемента), "добавить новую голову" (добавление элемента в начало списка). И здесь следует обратить внимание на несколько вещей.</p>
<p>Во-первых, не стоит отождествлять между собой пары и списки. Пары в данном случае были использованы как подходящий <em>инструмент</em> для создания списка. <strong>Абстрактный список</strong> (см. первый абзац) может быть реализован в <strong>конкретных структурах данных</strong>. В нашем случае это <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D1%8F%D0%B7%D0%BD%D1%8B%D0%B9_%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA#.D0.9E.D0.B4.D0.BD.D0.BE.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA_.28.D0.BE.D0.B4.D0.BD.D0.BE.D0.BD.D0.B0.D0.BF.D1.80.D0.B0.D0.B2.D0.BB.D0.B5.D0.BD.D0.BD.D1.8B.D0.B9_.D1.81.D0.B2.D1.8F.D0.B7.D0.BD.D1.8B.D0.B9_.D1.81.D0.BF.D0.B8.D1.81.D0.BE.D0.BA.29" rel="noopener noreferrer" target="_blank">односвязный список</a>. Важной характеристикой этой структуры данных является то, что каждый элемент списка, помимо определённого хранимого значения (число, строка, дата, адрес, имя и любая другая информация), содержит ссылку на другой такой же по структуре элемент. Таким образом, между элементами списка существует <strong>связь</strong>, и мы можем последовательно "путешествовать" (перемещаться) от текущего элемента к следующему, от начала списка к его <strong>концу</strong>.</p>
<p>И, как видим, пары вполне позволяют реализовать особенности списка как структуры данных. Ведь пара, помимо основного значения, может содержать ещё другую пару аналогичной структуры (значение + ссылка на другую пару) — и такая последовательность может длиться бесконечно.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(1, cons(2, cons(3, cons(4, cons(5, ...
cons('one', cons('two', cons('three', cons('four', cons('five', ...</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Однако, надо понимать, что сделать это можно не только с помощью пар. Cписки можно представить на основе массивов, а также некоторых других типов данных. Поэтому, если мы пользуемся <strong>списком</strong>, то к нему нельзя напрямую применять функции по работе с парами даже если мы знаем, что данный конкретный список состоит из пар. Для этого вводятся специальные функции для работы со списками, учитывающие его внутреннюю реализацию, которая может изменяться.</p>
<p>Во-вторых, коль скоро мы реализовали список именно на основе <strong>пар</strong>, то следует понимать, что не каждая пара, с которой мы имеем дело, какой бы «сложной» она ни была, является списком. У списка есть начало и конец, и мы должны иметь возможность последовательно перемещаться от одного элемента к другому. Для того, чтобы понять, что мы достигли последнего элемента списка и перемещаться дальше уже нет смысла, мы вводим специальный <strong>маркер конца списка</strong> — пустую пару <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l()</code>. По предварительному соглашению в роли маркера могут также выступать другие подходящие значения, например, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">null</code>. Как только мы встречаем очередную пару, в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cdr</code> которой находится пустая пара, это означает, что очередное значение списка, лежащее в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">car</code> пары, является <strong>последним элементом списка</strong>.
Отметим также, что у пустого списка (списка, в котором нет ни одного элемента) не может быть ни головы, ни хвоста. Поэтому соответствующие операции, примененные к пустому списку (взятие <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">head</code> или <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tail</code>), приводят к возникновению ошибки. Чтобы её избежать, вводят функцию для проверки списка на «пустоту» (например, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">isEmpty</code>), а далее берут голову или хвост у списка, предварительно убедившись, что он не является пустым.</p>
<p>Ниже приведены примеры пар, которые нельзя назвать списками. Они не реализуют требования, предъявляемые к односвязным спискам: в них отсутствует последовательная связь между элементами и/или нет маркера конца списка:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(1, cons(cons(3, null), 2));
cons(1, cons(2, cons(3, 4)));
cons(cons(1, 2), cons(3, cons(4, null)));</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Теперь покажем пары, которыми мы можем смело пользоваться как списками:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(1, cons(2, cons(3, null)));
cons('This', cons('is', cons('a', cons('list', null))));</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В уроке мы отметили, что следующие две записи, конструирующие список, являются эквивалентными:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(1, cons(2, cons(3, cons(4, cons(5, null))))); // (1, 2, 3, 4, 5)
l(1, 2, 3, 4, 5); // (1, 2, 3, 4, 5)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Для генерации списка мы создали функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code>, несмотря на то, что у нас уже есть конструктор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>. Это необходимо по нескольким причинам. Отметим наиболее важные из них:</p>
<ul>
<li>Выделив код создания списка в отдельную функцию и дав ей имя <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code> (сокращ. от <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">list</code>), мы ввели отдельную и понятную абстракцию, отвечающую за создание <strong>списков</strong> (и ничего другого!). Выше говорилось, что списки могут быть реализованы не только на парах, но и на массивах, а также других типах данных. Так вот, наша абстракция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code> позволяет отвлечься от внутреннего устройства списка. Тот, кто будет пользоваться этой функцией, не обязан знать, какими способами она конструирует список, а в случае необходимости может изменить его внутреннее устройство "незаметно и безболезненно" для пользователей <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code>.</li>
<li>Хорошая абстракция делает код более понятным и повышает его читабельность. Теперь, видя в коде <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l</code>, мы однозначно понимаем, что здесь происходит. Тогда как в ином случае, цепляясь глазом за множественные, разбросанные среди строк <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>, мы каждый раз должны будем определять, что же делает данная конкретная пара: то ли создаёт список, то ли хранит временные данные, то ли печёт пирожки ?! ;)</li>
</ul>
<p>Посмотрите на «безликие» нагромождения <strong>пар</strong>, создающие разные сущности, которые мы проходили в прошлых курсах. Только комментарии помогают понять, что здесь происходит.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
cons(5, cons(2, cons(7, cons(11, cons(6, cons(14, null)))))); // создание списка
cons(3, 17); // создание точки
cons(8, 17); // создание рационального числа
cons(cons(3, 33), cons(-2, -22)); // создание отрезка
$rectangle = cons(cons(-2, 23), cons(5, 11)); // создание прямоугольника
2 * (car(cdr($rectangle)) + cdr(cdr($rectangle))); // вычисление периметра прямоугольника</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>А вот как выглядит код с введёнными абстракциями. Комментарии здесь уже не нужны:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
list(5, 2, 7, 11, 6, 14);
makePoint(3, 17);
makeRational(8, 17);
makeSegment(makePoint(3, 33), makePoint(-2, -22));
$point = makePoint(-2, 23);
$rectangle = makeRectangle($point, 5, 11);
perimeter($rectangle);</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Естественно, введение функции устраняет дублирование кода. Каждый раз писать при создании списка такое нагромождение кода в виде вложенных <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">cons</code>, демонстрируя при этом все внутренности создаваемой конструкции, — не лучший стиль написания кода.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
use function Php\Pairs\Data\Lists\l;
use function Php\Pairs\Data\Lists\head;
use function Php\Pairs\Data\Lists\tail;
$list = l(5, 3, 9);
print_r(head(tail($list))); // => 3</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/php-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">СИКП на PHP</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Навык фундаментального понимания программ на PHP</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/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MywicHVyIjoiYmxvYl9pZCJ9fQ==--61c43d4881ca8feecc6f37dfafdc4e304f34b52f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Software%20engineer-bro.png" alt="СИКП на PHP" 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/php-sequences/lessons/list/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 9</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/php-sequences/lessons/list/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>