Автор оригинала: Agustin Chiappe Berrini.
Это первая статья, которую я создал, и это будет размещено на месте, в которой могут быть прочитаны другими. Основные причины этого являются:
- Мне действительно нравится программирование и говорить о программировании.
- Я хочу улучшить свой письменный английский.
Из-за того, что по этой причине я хотел бы получить какие-либо отзывы о моем письме. Не стесняйтесь делать это, это сделает меня счастливым.
«Функциональное программирование» становится необработанным словом. Есть тонны постов на тоннах блогов о том, почему это главный способ разработки программного обеспечения. Одной из причин часто цитируемых является ленивая оценка. JavaScript имеет известность быть функциональным, но ему не хватает родного способа сделать большинство вещей, которые обычно считаются такими. Опять же, одна из которых ленивая оценка. В этой статье я постараюсь объяснить, что лениво это, почему прохладная вещь и как использовать во время программирования в JavaScript.
Какие
Также называется вызовом (в отличие от Call-name), является стратегией, которая задерживает оценку выражения до ее необходимости. Он также вспоминает значение выражения, чтобы избежать повторных оценок.
Почему
Предположим, этот код:
const someValue = expensiveFunction(); ... // Tons of other operations that don't involve someValue, including user interactions console.log(someValue);
Как это, ДорогоFuntion
будет выполнен в первую очередь. Затем куча несвязанных выражений включает создание пользовательского интерфейса. В самом конце программы, и впервые мы используем Повышает
(Результат дорогоефункция
), чтобы распечатать его в терминале.
Таким образом, мы работали очень требовательной функцией в начале, чтобы использовать его возвращенное значение в конце программы. Если Дорогофункция
Были ли ограниченные функцией CPU, мы бы помешали в пользовательский опыт, вероятно, блокируя браузер.
Конечно, в этом небольшом простом примере можно избежать этой проблемы, просто переупорядя материал:
... // Tons of other operations that don't involve someValue, including user interactions console.log(expensiveFunction());
И теперь мы выполняем функцию, когда нам это нужно. Однако жизнь не всегда так просто! В сложных проектах нам может понадобиться ссылку Повышает
В других частях которых у нас нет контроля, и мы не сможем знать, когда стоимость будет сначала необходима.
Теперь предположим, что у нас есть волшебное ключевое слово в JavaScript называется ленивый
Отказ Если вы добавите ленивый
В начале заявления декларации стоимость объявленной переменной не будет выполнена еще не требуется. Теперь у нас есть, что это эквивалентно нашему второму коду:
lazy const someValue = expensiveFunction(); ... // Tons of other operations that don't involve someValue, including user interactions console.log(someValue);
Тада! Теперь мы решили две проблемы. Дорогофункция
не выполняется еще конец, и мы определили константу Повышает
В начале, так, на случай, если нам нужно будет ссылаться на него в любом другом месте, мы можем сделать это безопасно.
Повторная оценка
Хороший вопрос может поднять из этого примера: если переменная ссылается дважды … Было бы выполнено дважды? Другими словами, бы Дорогофункция
Беги два раза в этом примере?
lazy const someValue = expensiveFunction(); ... // Tons of other operations that don't involve someValue, including user interactions console.log(someValue); ... // Other stuff console.log(someValue);
Ответ – нет. И причина – это мемузаризация: методика кэширования в память значение, рассчитанное ранее.
Хотя ленивая оценка и мемузаризация не то же самое, они обычно собираются вместе. Когда мы ссылаемся на первый, мы обычно имеем в виду второй. Этот случай – хороший пример: наше воображаемое ключевое слово «помнит» значение Повышает
После того, как выполняется в первый раз. И если это нужно позже снова, он вернется, что вместо этого снова расчета. Мы действительно не хотим бегать Дорогофункция
дважды.
Как
К сожалению, у нас нет волшебной клавиатуры по имени ленивый
в JavaScript. Но у нас есть закрытия и функции первого класса, и это более чем достаточно.
export default const lazy = function (creator) { let res; let processed = false; return function () { if (processed) return res; res = creator.apply(this, arguments); processed = true; return res; }; };
Это очень простая реализация требований, которые вряд ли нужно больше объяснения. Давайте попробуем простой пример, чтобы увидеть, как он работает:
let counter = 0; const lazyVal = lazy(() => { counter += 1; return 'result'; }); console.log(counter); // 0 console.log(lazyVal()); // result console.log(counter); // 1 console.log(lazyVal()); // result console.log(counter); // 1
Вау, это было легко, не так ли. Теперь у нас есть способ создавать ленивые переменные. Мы также имеем способ сослаться на эти переменные без оценки их (используя LazyVal
) И еще один для их оценки (призыв LazyVal ()
).
И хотя это соответствует минимальным требованиям, это далеко от идеального. Проблема, которую у нас будет, это то, что нет возможности сказать, если переменной ленивый! На небольших кусках кода это не проблема, но когда у вас есть проект с тысячами файлов каждый с соткой строк, отслеживание типа переменных трудно, даже если мы часто комментируем.
Но это не все: если мы используем TypeScript, мы просто потеряли тип переменной! Теперь компилятор подумает, что Lazyval
Это функция (которая технически есть), и мы будем упустить много ценной проверки времени компиляции.
Итак, давайте увеличим наши требования. В дополнение к всему вышесказанному, мы будем:
- Я хочу быть в состоянии проверить, во время выполнения, если переменная ленивая.
- Если я использую TypeScript, я хочу сказать компилятору, что переменная ленива дополнительно с типом его.
И это приведет нас к итерации номер два нашего решения. Отныне я буду использовать Tymdercript, чтобы сделать все более очевидно. Для тех, кто не знаком с этим, имейте в виду, что это просто суперс JavaScript. Если вы игнорируете все ссылки типа, у вас будет хороший старый ECMAScript 2016:
export interface Lazy{ (): T; isLazy: boolean; }; export default const lazy = (getter: () => T): Lazy => { let evaluated: boolean = false; let _res: T = null; const res = >function (): T { if (evaluated) return _res; _res = getter.apply(this, arguments); evaluated = true; return _res; } res.isLazy = true; return res; };
Так-то лучше! Теперь я смогу позволить компилятору проверить, является ли переменной ленивой или нет, и тип упомянутой переменной. И если я использую простой JavaScript, я могу проверить во время выполнения, используя islazy
Отказ
Операции
Мы находимся в хорошем месте. Тем не менее, есть что-то, что нам не хватает: нам нужен способ управлять ленивыми ценностями. Конечно, мы можем сделать что-то вроде этого:
const actualVal1 = lazyVal1(); const actualVal2 = lazyVal2(); console.log(actualVal1 + actualVal2)
Но это не очень круто! Мы оцениваем все на месте. И если мы хотим, чтобы операция была ленивой, нам нужно было бы сделать что-то вроде:
const newVal = lazy(() => { const actualVal1 = lazyVal1(); const actualVal2 = lazyVal2(); return actualVal1 + actualVal2; });
Что очень уродливое. Кроме того, как только нам нужно цепи более одной операции, это станет очень трудно понять (и поддерживать) код.
Таким образом, нам нужен способ управлять ленивыми ценностями и, по возможности, чтобы получить эти операции легко.
Наша первая попытка будет добавить к интерфейсу функцию с этой подписью:
then(modifier: (a: T) => Lazy ): Lazy ;
Эта функция получит модификатор, запускает этот модификатор на нашем текущем значении и возвращает результат. Модификатор в этом контексте является операцией, выполненной по значению ленивой переменной, которая возвращает новую различную ленивую переменную. Для тех, кто этого не знал, путем реализации этой функции мы будем преобразовать нашу ленивую структуру в монаде. Что такое монад, почему они потрясающие и как их использовать, является предметом совершенно другой статьи.
Все это может звучать немного запутанным. Как однажды сказал очень умный человек: «Говорить дешево, покажи мне код!»:
export interface Lazy{ (): T; isLazy: boolean; then (modifier: (a: T) => Lazy ): Lazy ; }; export default const lazy = (getter: () => T): Lazy => { let evaluated: boolean = false; let _res: T = null; const res = >function (): T { if (evaluated) return _res _res = getter.apply(this, arguments); evaluated = true; return _res; } res.then = (modifier: (a: T) => Lazy ): Lazy => modifier(res()); res.isLazy = true; return res; };
Хотя это немного лучше, это все еще очень запутанно. Итак, давайте добавим пример того, как его использовать:
let counter = 0; const lazyVal = lazy(() => { counter += 1; return 1; }); const lazyOp = lazyVal .then((v1) => lazy(() => { counter += 1; return v1 + 1; })); console.log(counter); // 1 console.log(lazyOp()); // 2 console.log(counter); // 2 console.log(lazyOp()); // 2 console.log(counter); // 2
Хорошие вещи об этой реализации: операция ленивая, и она позволяет легко цеплять операции:
const lazyOp = lazyVal .then((v1) => lazy(() => v1 + 1; )) .then((v1) => lazy(() => v1 * 8 ));
Очень легко, действительно. Тяжелая часть этой реализации, тем не менее, это то, что она немного вегима (почему мне нужно набрать ленивый
каждый раз?) И что оригинальное ленивое значение оценивается, как только мы позвоним тогда Нет, когда нам действительно нужна ценность (то есть в самой операции), как вы можете видеть, как вы смотрите на выходы в первом примере.
Это очень неудачный, на самом деле. Мы хотим отложить оценку оригинальной ленивой стоимости как можно больше. Предпочтительно, когда выполняется сама операция.
Поэтому нам нужен лучший интерфейс для выполнения операций. Для того, чтобы решить, что один раз реализовать, мы должны думать о наших Ленивый
Как будто это контейнер значения. Что мы делаем, когда мы хотим изменить контейнер, например Массив
? Очень просто: мы карта
Это. Мы хотим сделать то же самое в этом случае. Для этого мы добавим новое определение на наш интерфейс:
map(modifier: (a: T) => T1): Lazy ;
Этот вид метода позволит нам уменьшить многообразие нашей программы (нам не нужно возвращать ленивую структуру в функции), а также отсрочить оценку как можно больше, поскольку вы увидите в следующем реализации:
export interface Lazy{ (): T; then (modifier: (a: T) => Lazy ): Lazy ; map (mapper: (a: T) => T1): Lazy ; isLazy: boolean; }; const lazy = (getter: () => T): Lazy => { let evaluated: boolean = false; let _res: T = null; const res = >function (): T { if (evaluated) return _res _res = getter.apply(this, arguments); evaluated = true; return _res; } res.isLazy = true; res.then = (modifier: (a: T) => Lazy ): Lazy => modifier(res()); res.map = (mapper: (a: T) => T1): Lazy => lazy(() => mapper(res())); return res; }; export default lazy;
Посмотрите, что мы там делали? Мы возвращаем новое ленивое значение на самой карте. Это позволяет нам определить содержание его до руки, размещая все выполнение там и обеспечение того, чтобы мы задерживаем оценку. Давайте посмотрим на пример этого:
let counter = 0; const lazyVal = lazy(() => { counter += 1; return 1; }); const lazyOp = lazyVal.map(v => { counter += 1; return v + 1; }); console.log(counter); // 0 console.log(lazyOp()); // 2 console.log(counter); // 2 console.log(lazyOp()); // 2 console.log(counter); // 2
Это гораздо лучше! Если вы видите, первый раз мы напечатали счетчик
Мы получили ноль, индикатор, который оценка еще не случилась! Но как насчет цепочки операций?
const lazyOp = lazy(expensiveFunction) .map((v1) => v1 + 1) .map((v1) => v1 * 8) .map(someOtherExpensiveFunction);
Это какой-то сексуальный код.
Теперь у нас очень удобно и простой в использовании библиотека для ленивой оценки в JavaScript, которые могут быть безопасно применены в приложениях реального мира. Если вы заинтересованы в коде, вы можете проверить Github Repository или Пакет NPM Отказ
В моей следующей статье о функциональном программировании и JavaScript я говорю о монаде и как улучшить свой опыт кодирования с ними.