Змеиный сахар или пишем свой 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) 

Вывод:

0 2 4 6 8 

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

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 ] 

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

ссылка на оригинал статьи https://habr.com/ru/post/505254/


Комментарии

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

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