Хабрахабр

Змеиный сахар или пишем свой range в JavaScript

Многие любят Python… Новички восщищаются отсутствием точек с запятой, а продвинутые радуются действительной простотой. Сегодня речь и пойдет о том, как в JavaScript реализовать подобие той самой простоты Python, а конкретно функцию range.

В Python по функции range можно итерировать или, например, преобразовать в массив — list(range(begin, end)).

Но вопрос в том, можно ли мощностями JavaScript создать что-то подобное и при этом, чтобы решение выглядело нативным и простым?

Первое, что приходит в голову — написать подобный класс:

function range(from, to, step = 1){ this.current = from this.to = to this.step = step this.next = () => this.current++ % to}

Да, я написал, что next возвращает обрезанное значение, но это тоже проблема, которую нужно придумать как решать — выбрасывать ошибку при переходе за максимум или делать что-то другое?

В общем, подобное решение совсем не добавит удобств и проще будет написать стандартный for, можно ещё, например, добавить функцию end и после каждой итерации проверять, не вышли ли мы за границу, но это решение нет смысла развивать.

В ES6 появился новый примитивный тип данных — Symbol. Он открывает перед нами двери в метапрограммирование.

Чтобы решить нашу проблему с range мы будем использовать Symbol.iterator. Он дает возможность создавать объект-итератор по определенному протоколу.

Для того, чтобы объект стал итератором, в нем нужно определить функцию [Symbol.iterator](), но, так как мы хотим создать общую функцию, а не класс, то у нас будет функция, возвращающая объект-итератор.

Вот как это выглядит:

function range(from, to, step = 1){ return { [Symbol.iterator](){ return { current: from, to: to, from: from, step, next(){ const it = { done: this.current >= this.to, value: this.current } this.current += this.step return it } } } }}

Что происходит? У нас есть функция range, она возвращает, как раз таки, наш объект-итератор, в котором функция [Symbol.iterator]() возвращает объект. Самое важное, чтобы этот объект содержал функцию next иначе объект не будет итерируемым и вылезет ошибка при попытке его использования. Функция next должна (!) возвращать объект со свойствами done и value, где done (bool) сигнализирует об окончании итерирования, а value содержит текущее значение.

В принципе всё работает, и уже можно написать что-то питоно-подобное:

 for(let i of range(0, 10, 2)) console.log(i)

Вывод:

02468

На этом можно было бы остановиться… Но можно написать более элегантное решение с использованием функций-генераторов:

function range(from, to, step = 1){ return { *[Symbol.iterator](){ for(let val = from; val < to; val += step){ yield val; } } }}

Дополнительным плюсом будет тот факт, что у нас появляются другие возможности итераторов. Например, можно очень просто преобразовать наш range в массив:

[...range(0, 10, 2)]// [ 0, 2, 4, 6, 8 ]

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

Удачи!

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»