Статьи

Две сказки: объектно-ориентированное программирование и функциональное программирование

Вступление

Мне было ужасно вести постоянный блог и (как естественная часть работы разработчика) я создал много дерьма. Недавно, ползая в старых хранилищах лома, я нашел кое-что, что по иронии судьбы все еще имеет смысл! И это была другая точка зрения о связи между ООП (объектно-ориентированным программированием) и ФП (функциональным программированием). Некоторые предположения сделаны здесь относительно читателя, Она / он:

  • Знает и понимает ООП
  • Понимает FP (немного знакомо, может быть, любитель)
  • Знает и понимает фразу » Закрытие «

Фон

Когда появляется эта многоядерная вещь, появляется более (реальная) потребность найти новые способы проинструктировать машины использовать эти ядра; и это в значительной степени совершенно новый способ мышления по сравнению с тем приятным мирным потоком старых добрых вычислений, который проходил построчно в гармонии. Это вызвало флешбэк почти 20-30 лет назад (а в некоторых случаях даже до этого), когда какой-то культ инопланетян начал странные вещи о странных способах обработки состояний и составления вычислений; забытый кодекс Некоторые имена часто встречаются в наши дни, например, Erlang, который не имеет изменяемого состояния, а некоторые имена, которые не так часто встречаются, как Smalltalk (целая виртуальная машина как состояние), но действуют как влиятельные протоколы.

Почему это воспоминание сейчас? Потому что старомодные конструкции (нравится это или нет) были (в основном) работой ученых-математиков, тех, кто стремится к (математическому) совершенству. Не компании-поставщики решений «Энтерпрайз», которые занимаются созданием новых проблем для людей и организаций, а затем взимают с них плату за решение этих проблем. Вот почему работа тех умов, которые когда-то начинали этот мир (компьютеров), вновь начинает сиять; начало новой эры (на самом деле мы только что вышли из темного леса спагетти и едва видим дорогу).

Там ООП и обратно ФП

Давайте начнем говорить более естественным образом (для нас!). Для этой части мы называем комбинацию состояния (изменяемого или нет) и функциональности, которая применяется к ним, вычислением . C # используется в качестве псевдоязыка для описания вещей, который почти такой же, как и любой другой язык программирования в семействе C. Чтобы избежать сложностей, возникающих при обработке ссылочных типов и типов значений , мы заключаем все наши состояния в ссылочный тип с именем Ref <T> ; определяется как:

public class Ref<T>
{
    public Ref() { }
    public Ref(T t) { Value = t; }

    public T Value { get; set; }
}

Наши вычисления — это просто добавление значений к начальному значению (0) и сохранение количества операций:

class Computation
{
    Ref<int> _counter = new Ref<int>(0);
    Ref<int> _accumulated = new Ref<int>(0);

    void Increase(int count)
    {
        _counter.Value += count;
    }

    public int Add(int i)
    {
        _accumulated.Value += i;
        Increase(1);

        return _accumulated.Value;
    }

    public int AccumulatedValue()
    {
        return _accumulated.Value;
    }

    public int NumberOfOperations()
    {
        return _counter.Value;
    }
}

Что мы имеем здесь?
Государство , который проживает в
_counter и 
_accumulated и куча вычислений, которые применимы к этому состоянию. Довольно ООПиш, верно? Теперь можно
вызвать эти
методы, чтобы изменить состояние или прочитать его (в оригинальном определении Ofcourse OOP речь шла об отправке сообщений между объектами, а не об вызове методов; это играет сейчас ту же роль. Но это уже другая история).

Например, это вычисление может быть использовано как:

var computation = new Computation();

computation.Add(10);
computation.Add(3);

var numberOfComputationsSoFar = computation.NumberOfOperations();
var accumulatedValueSoFar = computation.AccumulatedValue();

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

Теперь мы собираемся сделать поворот, перейдя в FPish. Посмотрите на это внимательно:

var _counter = new Ref<int>(0);
var _accumulated = new Ref<int>(0);

Action<int> increase = (int count) => { _counter.Value += count; };
Func<int, int> add = (int i) =>
{
    _accumulated.Value += i;
    increase(1);

    return _accumulated.Value;
};
Func<int> accumulatedValue = () => _accumulated.Value;
Func<int> numberOfOperations = () => _counter.Value;

Что ты видишь? Куча функций, верно? В конце концов, мы здесь занимаемся функциональным программированием (если вы опытный ФП, пожалуйста, представьте, что вам пока что нравится эта точка зрения на ФП). И они разделяют одно и то же закрытие .

Таким образом, если рассматривать это в перспективе , объект можно рассматривать как набор функций, имеющих одно и то же замыкание . Или ФП ООП наизнанку. Здесь есть образец дуальности. И мы фактически используем это ежедневно, как методы расширения в C #. Они разделяют (публичное) закрытие, которое является объектом, который они расширяют. Некоторые из этих методов расширения изменяют состояние объекта, а некоторые из них являются преобразователями, которые создают новый объект с новым состоянием, основанным на ранее заданном состоянии исходного объекта (например, LINQ).

Дальнейшие мысли

Некоторые друзья, возможно, читали это терпеливо; наблюдая, как я игнорирую неизменность, как основную часть FP. Ну, я просто не согласен с этой точкой зрения. Неизменность не была частью многих больших пап, таких как Лисп и О’Камл. Я согласен, что неизменность имеет много (даже бесчисленное множество) преимуществ. Но я могу («могу» здесь подразумевать границы человеческого разума и его содержимого, к которому у меня есть доступ) не вижу никакого практического или скорее прагматичного способа слияния этого с нашей нынешней средой. Наиболее практичный способ, который мне нравится больше всего, — это метод Erlang (не сам Erlang; просто попробуйте использовать его FFI, оставьте один только синтаксис — у которого много любовников — я надеюсь, что Elixir привнесет некоторые макросы [DllImport] в BEAM); передача сообщений (возврат к исходным определениям ООП). Другой подход будет иметь некоторый локальный изменяемый контекст.Или как то так:

class Computation
{
    readonly object _lock = new object();

    lock(_lock)
    {
        Ref<int> _counter = new Ref<int>(0);
        Ref<int> _accumulated = new Ref<int>(0);
    }

    //...
}

В C # поможет весело во многих ситуациях.

Если вы когда-нибудь задумывались, где эти понятия объединяются и что-то формируют, вам следует медитировать над этими тремя словами: «функция», «это», «JavaScript».

Но я бы очень хотел услышать от всех их истории!