Зарегистрируйтесь, чтобы продолжить обучение

Пары JS: Составные данные

Пары

Обратите внимание на то, что пары неизменяемы. Нельзя просто так взять и изменить пару. Можно только создать новую на основе предыдущей. Поначалу такой способ программирования может показаться необычным и сложным, так как надо перестроить свое мышление. Чем дальше вы будете продвигаться по курсам, тем больше он вам начнет нравиться. Вы увидите, как часто упрощается код и его отладка в отсутствие изменяемости.

Ошибки

Работая с парами, очень легко допустить ошибку, которая будет выглядеть так:

Argument must be a pair, but it was ...

Парой является только то, что создано с помощью конструктора cons. Если по какой-то причине в селекторы произошла передача не пары, то результатом будет как раз такая ошибка. Проверить это очень легко:

const pair = 3
car(pair) // Argument must be a pair, but it was '3'

То же самое, если передать селектору строку вместо пары:

const pair = 'hello'
car(pair) // Argument must be a pair, but it was 'hello'

Конспект урока

Мы уже написали несколько полезных функций для работы с точками и, в принципе, поняли, как работает эта абстракция. Теперь пришло время копнуть на уровень глубже и посмотреть, как же устроены наши точки.

import { cons, car, cdr } from 'hexlet-pairs'

// Конструктор
const pair = cons(8, 7)

car(pair) // 8
cdr(pair) // 7

const pair2 = cons(3, pair)

Устроены они достаточно просто и используют структуру данных, которая называется парой. Пар в самом языке JavaScript не существует, мы их реализовали с помощью отдельной библиотеки, и выше можно увидеть пример того, как они используются. Мы импортируем из библиотеки конструктор cons и селекторы car и cdr. Конструктор создает пару, а селекторы служат для извлечения из пары первого значения (с помощью car) и второго значения (с помощью cdr). Все достаточно просто и очень похоже на реализацию точек из прошлого урока.

Что интересно, элементами пары могут быть другие пары. В будущем это даст нам очень мощные возможности для того, чтобы строить более сложные структуры данных, в том числе списковые.

import { cons, car, cdr, toString } from '@hexlet/pairs'

const pair = cons('first', 'second')
console.log(car(pair)) // => first
console.log(cdr(pair)) // => second

// don't do it
console.log(pair) // => { [Function] pair: true }

console.log(toString(pair)) // => (first, second)

Давайте посмотрим, как представлены наши точки с помощью пар:

import { cons, car, cdr } from 'hexlet-pairs'

const makePoint = (x, y) => cons(x, y)

const getX = point => car(point)
const getY = point => cdr(point)

const toString = point => String(point)

Здесь все предельно просто: makePoint — это функция, которая принимает x и y и вызывает конструктор пары с этими аргументами. То же самое с селекторами: getX и getY принимают на вход точку и вызывают с этой точкой car и cdr соответственно.

Можно заметить, что сработало бы даже такое определение:

const makePoint = cons

const getX = car
const getY = cdr

Здесь все верно с синтаксической точки зрения и с точки зрения получения конечного результата. Но с таким способом определения есть некоторые проблемы: по сути, когда мы делаем такое присваивание, получается, что makePoint и cons являются одним и тем же объектом. Кто-то может сказать, что они ссылаются на одну и ту же функцию, но это уже тонкости реализации конкретного языка программирования. На практике это означает, что, запустив построенный таким образом код, вы не увидите вызова функций makePoint, getX или getY, потому что их фактически не существует. При отладке вы не найдете этих функций в трассировке стека. Вы можете захотеть увидеть все вызовы, например, makePoint, но вы точно не захотите отслеживать в вашей программе все вызовы cons, которые могут использоваться не только для точек, а вообще для любых библиотек. Поэтому мы не используем такое определение, но о нем нужно знать, чтобы понимать, как в целом все работает.

Теперь, используя пары, мы можем строить новые абстракции, расширяя нашу библиотеку графических примитивов. Мы вводим понятие отрезка, для которого мы создаем конструктор и селекторы:

const point1 = makePoint(1, 2)
const point2 = makePoint(10, -2)

const segment = makeSegment(point1, point2)

startSegment(segment) // (1, 2)
endSegment(segment) // (10, -2)

Нам нужно сделать две точки (потому что любой отрезок представлен двумя точками). После этого мы используем конструктор makeSegment и передаем туда наши точки, а с помощью селекторов startSegment и endSegment мы получаем точки. Важно, что мы получаем именно точки, потому что это тоже составные данные со своими селекторами, с помощью которых можно получать примитивные значения и производить над ними какие-либо манипуляции при необходимости.


Дополнительные материалы

  1. История возникновения cons/car/cdr

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff