Pull to refresh

Comments 23

Не надо переусложнять.

Функциональное программирование – это строго математичная вещь, там почти всё расписано до аксиом формальным символьным выводом. А паттерны проектирования - некие практические наблюдения, советы, обобщающие практику. Не пытайтесь объяснить первое через второе, это значительный шаг в сторону удаления от ясности ума.

Ну и замыкания в ФП не "замораживают" значения, они только сохраняют лексический контекст. Захват значений в замыканиях – это сиплюсплюсовская объектно-ориентированная примочка.

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

Функция - синглтон в любых языках. Функции создаются в момент интерпретации кода программы (в том числе при eval). У функции есть явные и неявные параметры. К неявным относятся: лексический контекст и объектный контекст (ссылка на объект). Последний в некоторых языках всё же явный. Замыкание - экземпляр функции с хотя бы одним заданным неявным параметром. Это буквально рантайм структура из 2-3 ссылок. JIT компилятор может оптимизировать такие замыкания, на лету создавая новые функции, где неявные аргументы подставлены в код и оптимизированы.

Я говорю только о том, что не надо предельно математически ясное в ФП понятие функции, которое имеет формальное аксиоматическое определение семантики (можно посмотреть, например, на 66 странице R7RS) объяснять мутным словом "синглтон".

Хотя, конечно, функцию можно привести как пример синглтона. Но условный и размытый, как всё в паттернах проектирования. А если я порождаю несколько копий кода функции и дальше, например, занимаюсь модификацией этого кода (в том языке, который это позволяет)? Это тогда уже и не синглтон будет. Вопрос практики применения.

Я в своей статье рассматривал "синглтон" в контексте lifestyle - сколько экземпляров одной функции порождается в процессе выполнения программы. Вот и по мнению коллеги @nin-jin функции в программах создаются в одном экземпляре (если я правильно понял его коммент выше). Паттерн ОО-проектирования "синглтон" к моим рассуждениям отношения не имеет. Но очень легко запутаться, когда два разных понятия имеют одно и то же имя.

Функция как код - создаётся один раз, замыкание как код + захваченный контекст - создаётся каждый раз новое.

замыкания в ФП не "замораживают" значения, они только сохраняют лексический контекст

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

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

Ну банально

* (defun mycar (p) (lambda () (car p)))
MYCAR
* (defvar p1 (cons 1 1))
P1
* (defvar p2 (cons 2 2))
P2
* (defvar c1 (mycar p1))
C1
* (defvar c2 (mycar p2))
C2
* (funcall c1)
1
* (funcall c2)
2

Лексически у нас одна лямбда в первой строке, но дальше создаются разные инстансы с разным поведением.

каждая из которых в отдельности может являться синглтоном.

Всё таки синглтон подразумевает, что при конструировании объекта мы каждый раз получаем одну и ту же сущность. А так то любой объект можно считать синглтоном самого себя )

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

c1 = (lambda () (car p)) {в контексте p=p1}

c2 = (lambda () (car p)) {в контексте p=p2}

и это две отчётливо разные лямбды.

Просто так исторически повелось, что контекст имени в синтаксисе опускается. Но в семантике-то он присутствует.

Если один код в разных контекстах генерирует разные объекты - это уже в любом случае не синглтон.

Если смотреть на функцию именно как на символическую запись её кода в отрыве от контекста, то да.

Из статьи складывается ощущение, что ФП - следующий этап развития после ООП, а ПП - вообще какой-то мезозой. Однако, стоит отметить, что ФП и объектная декомпозиция - вещи ортогональные. Объекты, точно так же могут быть неизменяемыми и содержать лишь чистые методы. И наоборот, грязные методы могут описывать идемпотентные инварианты между атрибутами объектов. Вот вам пример на ОРП на подумать:

// чистый объект
class TaxCalculator extends PureObject {
    amount() { return 0 }
    vat() { return this.amount() * 0.2 }
    sales() { return this.amount() * 0.07 }
    total() { return this.vat() + this.sales() }
}

class MyBasket extends ReactiveObject {

    // чистая функция, но грязный метод
    @mem cost( next ) { return next ?? 100 }

    // это вообще процедура в объекте
    @mem change( diff ) {
      this.cost( this.cost() + diff )
    }

    // а тут у нас ленивая фабрика с реактивным связыванием
    @mem taxes() {
        return new TaxCalculator({
            amount: ()=> this.cost()
        })
    }

    // вызывается каждый раз, когда налоги реально меняются
    @mem loging() {
      console.log( 'taxes: ', this.taxes().total() )
    }

}

Мне сложно "с листа" понять, о чём этот код - мне не хватает бэкграунда. Например, PureObject , ReactiveObject - я не знаю, что это за классы. @mem - это похоже на декоратор, я тоже не знаю, что он делает. Вы показываете "верхушку айсберга" и спрашиваете, что "под водой". Я не знаю. Реактивность не находится в фокусе моих интересов, чтобы я этим заинтересовался. Я даже "чистый объект" не осилил (так и не понял, чем он отличается от "грязного"?). Почему тут наследование от PureObject , а в примере - от $mol_object? В общем, это для меня слишком сложно. Но спасибо, что предложили подумать.

Пример в ООП части, я бы не согласился что это ООП, по-моему это процедура calculate обернутая в неймспейс TaxCalculator.

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

Я бы такой код написал в ООП.

class TaxOfPrice {
    constructor(price, tax) {
        this.price = price;
        this.tax = tax;
    }

    valueOf() {
        return (this.price * this.tax).toFixed(2);
    }

    toString() {
        return this.valueOf();
    }
}

class Tax {
    constructor(tax) {
        this.tax = tax;
    }
    
    valueOf() {
        return this.tax;
    }
}

const vat = new Tax(0.2);
const sales = new Tax(0.07);

console.log(`of price 100 vats = ${new TaxOfPrice(100, vat)}`);                   // of price 100 vats = 20.00
console.log(`of price 100 sales = ${new TaxOfPrice(100, sales)}`);                // of price 100 sales = 7.00
console.log(`of price 100 vats and sales = ${new TaxOfPrice(100, vat + sales)}`); // of price 100 vats and sales = 27.00


И это не синглтоны =). Как по мне в ООП синглтоны не очень нужны именно для ООП. Они скорее нужны иногда для какой-то оптимизации на уровне компьютера. а на уровне ООП по-моему не нужны - чисто логически.

Напоминает дискуссии средневековых схоластиков на тему "сколько ангелов уместится на острие иглы" )

А чисто по практическим соображениям - допустим, у вас есть чудный код, который вот прямо сейчас надо поправить, а разработчика уже нет.
Посадите за компьютер хоть кого-то, кто js последний раз в школе учил - и он разберется, что делает функция:

function vat(amount) {
    return calculateTax(0.2, amount); // 20% НДС
}

Но теперь вам нужно еще добавить еще расчет налогов сотрудника:
- взять ФОТ, из него вычесть, сколько там, 18% соц. фонд, из него же 3% медстрах, из ОСТАВШЕГОСЯ 13% НДФЛ, а остаток выдать в руки сотруднику (цифры точно не помню, не суть важно)

Ваш "новый программист" берет за основу старый код, практически копипасту, и пишет:

function to_ss(amount){
  return calculateTax(0.18, amount); // 18% соц.
}
function to_ms(amount){
  return calculateTax(0.03, amount); // 3% мед.
}
function to_ndfl(amount){
  return calculateTax(0.13, amount); // 13% НДФЛ.
}
const ss = to_ss(1000);
const ms = to_ms(1000);
const ndfl = to_ndfl(1000 - ss - ms);
const pay = 1000 - ss - ms - ndfl;

Этого будет достаточно чтобы не попасть ни под санкции со стороны налоговой, ни под санкции со стороны какой-нибудь комиссии по труду.
Всё просто и наглядно, и на вопрос сотрудника "Э, где моя тыща?" - ответ виден сразу.

А теперь допустим что у вас был код вот такой:

const calculateTax = (taxRate) => (amount) => amount * taxRate;
const vat = calculateTax(0.2);       // 20% НДС
const salesTax = calculateTax(0.07); // 7% налог с продаж

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

Это к тому, что налицо усложнение, у этого усложнения должна быть практическая выгода. В чем она тут? Быстрее обрабатывается? Потребляет меньше памяти? Сложнее заменить опытных программистов?

Вот мне тут Игорь Иванович подсказывает:

const calculateTax = (taxRate) => (amount) => amount * taxRate;

const vat = calculateTax(0.2); // 20% НДС
const toSS = calculateTax(0.18); // 18% соц.
const toMS = calculateTax(0.03); // 3% мед.
const toNDFL = calculateTax(0.13); // 13% НДФЛ.

const ss = toSS(1000);
const ms = toMS(1000);
const ndfl = toNDFL(1000 - ss - ms);
const pay = 1000 - ss - ms - ndfl;

В общем, не сильно сложнее вашего варианта. Всё зависит от "заточки" мозгов разраба. Я, например, чистый JS'ник. "Висну" на первом же ts-декораторе - мозги не приучены читать такой код. После Java долго привыкал к PHP, после PHP долго привыкал к JS. Но отвыкаю быстро - полгода и нужно плющить мозг, чтобы понять о чём код. К стрелочным функциям в JS тоже не быстро приспособился, несколько месяцев ушло.

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

Если этот пример считать, то всё же функции можно сделать не синглтоном, просто это антипаттерн, который или для странного api, или для какой-то jit-компиляции (не совсем это) и применять.

Похоже на то, что стОит добавлять в заголовок технологию/язык программирования. Мне, как дотнетчику было вообще неясно о чем речь, пока не дошел до кода. Хотя замыкания в сишарпе есть, большинство даже не осознают их наличие, ибо они как неуловимый Джо. Их даже нет смысла классифицировать и как-то обозначать. При этом синглтоны разумеется есть и используются иногда, но они вообще не похожи на ЭТО, как и весь подход к ооп. "Функция это синглтон" воспринимается вообще как метафора, ибо это физически невозможно. Все написанное казалось абракадаброй, пока не обнаружил что это про жс. Предупреждать же надо.

Я предупредил:

Под катом я привожу результаты своих изысканий на примере очень простого функционала на JS, написанного в трёх разных стилях.

Как-то однажды знаменитый учитель Кх Ан вышел на прогулку с учеником Антоном. Надеясь разговорить учителя, Антон спросил: "Учитель, слыхал я, что объекты - очень хорошая штука - правда ли это?" Кх Ан посмотрел на ученика с жалостью в глазах и ответил: "Глупый ученик! Объекты - всего лишь замыкания для бедных."

Пристыженный Антон простился с учителем и вернулся в свою комнату, горя желанием как можно скорее изучить замыкания. Он внимательно прочитал все статьи из серии "Lambda: The Ultimate", и родственные им статьи, и написал небольшой интерпретатор Scheme с объектно-ориентированной системой, основанной на замыканиях. Он многому научился, и с нетерпением ждал случая сообщить учителю о своих успехах.

Во время следующей прогулки с Кх Аном, Антон, пытаясь произвести хорошее впечатление, сказал: "Учитель, я прилежно изучил этот вопрос, и понимаю теперь, что объекты - воистину замыкания для бедных." Кх Ан в ответ ударил Антона палкой и воскликнул: "Когда же ты чему-то научишься? Замыкания - это объекты для бедных!" В эту секунду Антон обрел просветление.

Sign up to leave a comment.

Articles