О замыканиях в JavaScript.
В JavaScript, на самом деле, не "настоящие" замыкания. Я бы даже сказал, что в JS нет замыканий, а есть некая отдалённо напоминающая их эмуляция.
Суть замыканий (closures) в двух вещах:
- в теле функции можно использовать переменные, которые определены до этой функции
- значения этих переменных "фиксируются" ("запираются") в момент определения функции
Посмотрим пример на Elixir-е — "настоящем" функциональном языке:
Прикол в том, что в момент определения функции значение этой переменной "фиксируется": внутри функции теперь это уже не какой-то там
Поэтому в последней строке
В императивных языках, которые эмулируют эту функциональность (простите за тавтологию) функциональных языков, всё не так:
Конечно, одно из типовых использований замыканий — в "фабрике функций" (функциях, которые возвращают другие функции). И когда вы пишете код "внешней" функции (= самой фабрики), ничто не мешает тщательно контролировать самого себя на предмет того, какие переменные "утекают" во "внутреннюю" функцию (= результат работы фабрики, который будет возвращён коду, вызвавшему фабрику). И убедиться, что эти переменные никто не будет изменять вне внутренней функции, или их изменение учтено by design — например, с "неправильными" JS-замыканиями можно тривиально реализовать функцию, которая считает, сколько раз она была вызвана — а с "правильными" замыканиями это, фактически, невозможно:
В JavaScript, на самом деле, не "настоящие" замыкания. Я бы даже сказал, что в JS нет замыканий, а есть некая отдалённо напоминающая их эмуляция.
Суть замыканий (closures) в двух вещах:
- в теле функции можно использовать переменные, которые определены до этой функции
- значения этих переменных "фиксируются" ("запираются") в момент определения функции
Посмотрим пример на Elixir-е — "настоящем" функциональном языке:
x = 2Итак, мы внутри функции
plus_2 = fn(n) -> n + x end
plus_2.(10) # => 12
x = 3
plus_2.(10) # => 12
plus_2
используем переменную x
, которая определена до этой функции.Прикол в том, что в момент определения функции значение этой переменной "фиксируется": внутри функции теперь это уже не какой-то там
x
, а конкретно оказавшаяся там двойка. На место свободной переменной x
, фактически, подставляется конкретное содержащееся там в момент определения значение.Поэтому в последней строке
plus_2
всё также продолжает прибавлять двойку к своему единственному аргументу n
, а не новое значение x
. Исходное значение x
"замкнулось" внутри функции.В императивных языках, которые эмулируют эту функциональность (простите за тавтологию) функциональных языков, всё не так:
// jsКак видим, в JS (и Ruby) функция не "запомнила" (не подставила) значение переменной внутри её тела. Вместо этого, она каждый раз заново обращается к этой переменной, используя текущее значение.
let x = 2
plus_2 = n => n + x
plus_2(10) // 12
x = 3
plus_2(10) // 13
# ruby
x = 2
plus_2 = ->(n) { n + x }
plus_2.(10) # 12
x = 3
plus_2.(10) # 13
Конечно, одно из типовых использований замыканий — в "фабрике функций" (функциях, которые возвращают другие функции). И когда вы пишете код "внешней" функции (= самой фабрики), ничто не мешает тщательно контролировать самого себя на предмет того, какие переменные "утекают" во "внутреннюю" функцию (= результат работы фабрики, который будет возвращён коду, вызвавшему фабрику). И убедиться, что эти переменные никто не будет изменять вне внутренней функции, или их изменение учтено by design — например, с "неправильными" JS-замыканиями можно тривиально реализовать функцию, которая считает, сколько раз она была вызвана — а с "правильными" замыканиями это, фактически, невозможно:
// js#programming #fp
timesRunFactory = () => {
let count = 0;
return () => {
count++;
return count
}
}
timesRun = timesRunFactory();
timesRun(); // 1
timesRun(); // 2
...
"Функциональное программирование" (ФП) - модный термин.
Автор имеет опыт разработки веб-приложений на Elixir-е (с использованием фреймворка Phoenix). Можно ли Elixir считать функциональным языком? Большинство отвечают, мол, конечно, это один из референтных представителей группы. Тот же JavaScript или Ruby могут назвать "языком с *элементами* ФП", но никому не придёт в голову назвать "полноценным" "функциональным языком".
Мне кажется, точно также, как отсутствие диплома о техническом образовании не делает человека гуманитарием (впрочем, верно и обратное), также и отсутствие на уровне базовых конструкций языка "экземпляров классов" не делает язык "функциональным". В случае Elixir, отсутствие "экземпляров" не делает даже язык "не объектно-ориентированным".
В самом деле, ООП (объектно-ориентированный подход/программирование) зиждется на следующей формуле: "состояние" + "поведение". У нас есть некие автономные чёрные ящики (объекты), у которых в каждый момент времени есть некое внутреннее (не видимое снаружи) состояние и есть возможность получать сообщения.
Сейчас обычно говорят не "получать сообщения", а "вызывать метод". Впрочем, обратите внимание, что в Ruby проверка на то, существует ли у объекта метод, делается с помощью метода `respond_to?`— "отвечает ли?". Наследие Smalltalk, где объекты таки посылали друг другу именно "сообщения". В каком-нибудь Objective C (базовом языке для разработки под iOS) до сих пор официально "посылают сообщения".
Но, простите, а что наши разработанные на Elixir "процессы" делают, когда работают в экосистеме Erlang-а? Посылают друг другу сообщения. И так, Elixir-овский "процесс":
— имеет внутреннее состояние (минимальное разнообразие типовых способов работы с состоянием заложено в стандартную библиотеку, называющуюся OTP);
— отвечают на "сообщения".
QED, господа — Elixir это объектно-ориентированный язык (где роль "классов" играют "модули", роль "объектов" — "процессы", роль "методов" — сообщения и функции).
Отличие Elixir в том, что, когда вы пишете код, язык вас вынуждает явно себе представлять, как ваши объекты "раскладываются" по процессам (параллельным потокам выполнения) — какой объект живёт в каком "домике"-процессе на каком "островке"-сетевом узле. Традиционный объектно-ориентированный язык позволяет инкапсулировать даже сетевой вызов к REST-ресурсу, избаловывая программиста этими непрозрачными стенками абстракций.
#programming #fp #ruby #javascript #elixir
Автор имеет опыт разработки веб-приложений на Elixir-е (с использованием фреймворка Phoenix). Можно ли Elixir считать функциональным языком? Большинство отвечают, мол, конечно, это один из референтных представителей группы. Тот же JavaScript или Ruby могут назвать "языком с *элементами* ФП", но никому не придёт в голову назвать "полноценным" "функциональным языком".
Мне кажется, точно также, как отсутствие диплома о техническом образовании не делает человека гуманитарием (впрочем, верно и обратное), также и отсутствие на уровне базовых конструкций языка "экземпляров классов" не делает язык "функциональным". В случае Elixir, отсутствие "экземпляров" не делает даже язык "не объектно-ориентированным".
В самом деле, ООП (объектно-ориентированный подход/программирование) зиждется на следующей формуле: "состояние" + "поведение". У нас есть некие автономные чёрные ящики (объекты), у которых в каждый момент времени есть некое внутреннее (не видимое снаружи) состояние и есть возможность получать сообщения.
Сейчас обычно говорят не "получать сообщения", а "вызывать метод". Впрочем, обратите внимание, что в Ruby проверка на то, существует ли у объекта метод, делается с помощью метода `respond_to?`— "отвечает ли?". Наследие Smalltalk, где объекты таки посылали друг другу именно "сообщения". В каком-нибудь Objective C (базовом языке для разработки под iOS) до сих пор официально "посылают сообщения".
Но, простите, а что наши разработанные на Elixir "процессы" делают, когда работают в экосистеме Erlang-а? Посылают друг другу сообщения. И так, Elixir-овский "процесс":
— имеет внутреннее состояние (минимальное разнообразие типовых способов работы с состоянием заложено в стандартную библиотеку, называющуюся OTP);
— отвечают на "сообщения".
QED, господа — Elixir это объектно-ориентированный язык (где роль "классов" играют "модули", роль "объектов" — "процессы", роль "методов" — сообщения и функции).
Отличие Elixir в том, что, когда вы пишете код, язык вас вынуждает явно себе представлять, как ваши объекты "раскладываются" по процессам (параллельным потокам выполнения) — какой объект живёт в каком "домике"-процессе на каком "островке"-сетевом узле. Традиционный объектно-ориентированный язык позволяет инкапсулировать даже сетевой вызов к REST-ресурсу, избаловывая программиста этими непрозрачными стенками абстракций.
#programming #fp #ruby #javascript #elixir
Элементы ФП в императивных языках
Мода на "функциональное программирование" задаётся, несомненно, как раз привнесением в более распространённые императивные языки пресловутых "элементов ФП".
Среди всех таких элементов можно (условно — реальная "генеалогия" чуть сложнее) выделить два класса:
1) Элемент LISP: лямбда-функции. В Ruby "лямбды" изначально были заложены в язык в виде "блоков кода" (конечно, в оригинальном LISP это сделано несколько более органично). Вслед за взрывным ростом популярности Ruby и большим признанием удобства использования "лямбд" со стороны прикладных программистов, стали потихоньку добавлять их и в другие языки: кажется, первым среди невымирающих динозавров была Java 8. Ну а теперь даже в C++ вы можете встретить нечто похожее.
2) Элемент LISP: итераторы. Аналогично с итераторами: map, select, reduce, zip, и т. д. и т. п. можно найти в любом уважающем себя современном языке. Популяризирует их в наше время JavaScript, в очередной ревизии которого они наконец появились, чтобы язык не выглядел совсем уж позорно на фоне любительской поделки фанатиков Rails — CoffeScript-а (как раз после появления "лямбд" и итераторов в JS "коффискрипт" и умер).
3) Элемент Haskell: "монады". Мало кто, в сущности, знает хоть что-то о системе категорий, но антураж "матана" привлекает неуверенных в себе программистов. Точно также, как психолог в связи с массовостью профессии и отсутствием профессиональных стандартов считается неполноценным психиатром, и программист на заре развития программирования мог бы считаться неполноценным математиком. В какой-то мере сохраняется подобное смутное чувство и сегодня. Из всего Haskell-а (этакого "языка программирования от математиков") выдёргивают обычно так называемые "монады". Элементарная, в сущности, концепция, которая на языке ООП описывалась бы простым интерфейсом контейнера с 3-8 методами, превращается в жупел, которым неполноценные математики, так и не сумевшие стать программистами, формируют неуверенность в своём интеллекте у программистов, которые не желают становиться математиками.
4) Элемент Haskell: pattern matching. Опять же, JavaScript популяризирует концепцию в наше время, переназвав её как "destructuring". На мой взгляд, впрочем, в таком терминологическом изменении нет ничего страшного: надо быть ближе к народу. Destructuring позволяет, например, прямо в сигнатуре функции (в перечислении аргументов) "выдернуть" ключ хеша или элемент массива из входящего параметра (таким образом "сопоставив", match, входящую структуру некоему "образцу", pattern). Ruby идёт с отставанием: на коленке сделанный
А кроме этих четырёх пунктов, что-то больше ничего и не приходит на ум.
А вам каких "элементов ФП" не хватает в любимых языках?
#programming #fp #ruby #javascript #elixir
Мода на "функциональное программирование" задаётся, несомненно, как раз привнесением в более распространённые императивные языки пресловутых "элементов ФП".
Среди всех таких элементов можно (условно — реальная "генеалогия" чуть сложнее) выделить два класса:
1) Элемент LISP: лямбда-функции. В Ruby "лямбды" изначально были заложены в язык в виде "блоков кода" (конечно, в оригинальном LISP это сделано несколько более органично). Вслед за взрывным ростом популярности Ruby и большим признанием удобства использования "лямбд" со стороны прикладных программистов, стали потихоньку добавлять их и в другие языки: кажется, первым среди невымирающих динозавров была Java 8. Ну а теперь даже в C++ вы можете встретить нечто похожее.
2) Элемент LISP: итераторы. Аналогично с итераторами: map, select, reduce, zip, и т. д. и т. п. можно найти в любом уважающем себя современном языке. Популяризирует их в наше время JavaScript, в очередной ревизии которого они наконец появились, чтобы язык не выглядел совсем уж позорно на фоне любительской поделки фанатиков Rails — CoffeScript-а (как раз после появления "лямбд" и итераторов в JS "коффискрипт" и умер).
3) Элемент Haskell: "монады". Мало кто, в сущности, знает хоть что-то о системе категорий, но антураж "матана" привлекает неуверенных в себе программистов. Точно также, как психолог в связи с массовостью профессии и отсутствием профессиональных стандартов считается неполноценным психиатром, и программист на заре развития программирования мог бы считаться неполноценным математиком. В какой-то мере сохраняется подобное смутное чувство и сегодня. Из всего Haskell-а (этакого "языка программирования от математиков") выдёргивают обычно так называемые "монады". Элементарная, в сущности, концепция, которая на языке ООП описывалась бы простым интерфейсом контейнера с 3-8 методами, превращается в жупел, которым неполноценные математики, так и не сумевшие стать программистами, формируют неуверенность в своём интеллекте у программистов, которые не желают становиться математиками.
4) Элемент Haskell: pattern matching. Опять же, JavaScript популяризирует концепцию в наше время, переназвав её как "destructuring". На мой взгляд, впрочем, в таком терминологическом изменении нет ничего страшного: надо быть ближе к народу. Destructuring позволяет, например, прямо в сигнатуре функции (в перечислении аргументов) "выдернуть" ключ хеша или элемент массива из входящего параметра (таким образом "сопоставив", match, входящую структуру некоему "образцу", pattern). Ruby идёт с отставанием: на коленке сделанный
case... in
в свежей версии это хорошо, но мало: даёшь "перегрузку" функций в зависимости от структуры аргумента, как в том же Elixir.А кроме этих четырёх пунктов, что-то больше ничего и не приходит на ум.
А вам каких "элементов ФП" не хватает в любимых языках?
#programming #fp #ruby #javascript #elixir