Re[3]: Базовое отличие ООП от ФП
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.03.24 14:50
Оценка: 9 (1)
Здравствуйте, sergii.p, Вы писали:

SP>А если сравнивать ООП с ФП, то иммутабельность видимо не является принципиальным отличием.

Является.
SP>Ведь без изменения состояния можно работать и в ООП парадигме.
Можно, но тогда от ООП почти ничего не остаётся.
SP>Принципиальным отличием, как мне кажется, является то как мы вносим зависимости.

SP>В ООП через интерфейсы:


SP>
SP>struct Out { virtual void write(const Data&) const = 0; };

SP>struct Console { virtual void write(const Data&) const { ... }; };

SP>struct File { virtual void write(const Data&) const { ... }; };

SP>void foo(const Data& d, Out& out) {
SP>    out.write(d);
SP>}

SP>void main(){
SP>    const auto console = Console{};
SP>    const auto file = File{};
SP>    const Data d{42};
SP>    foo(d, console);
SP>    foo(d, file);
SP>}
SP>

Здесь нет никаких интерфейсов; языки с номинативной типизацией (например, C или C++) просто не скомпилируют этот код. Для того, чтобы в foo можно было передавать Console и File, нам нужно унаследовать их от Out.


SP>В ФП — через функции:


Не обязательно. Вот вы в том, что вы назвали "ООП", просто завернули функции write внутрь структур.
В ФП вы можете сделать примерно то же самое — заверните функцию внутрь структуры:
struct Out { write: (Data) -> void}

То, что в ФП это кажется оверкиллом — ну, так это оттого, что вы выбрали вырожденный интерфейс.
Возьмите в качестве интерфейса что-нибудь поинтереснее — например, пару из NeutralElement и Combine:
public interface IGroup<T>
{
  public T NeutralElement {get;}
  public T Combine(T a, T b)
} 

public class IntAddition: IGroup<int>
{
   public int NeutralElement { get => 0; }
   public int Combine(int a, int b) => a + b;
}

public static class H
{
   public static T Reduce<T>(this IEnumerable<T> input, IGroup<T> group)
   {
      var r = group.NeutralElement;
      foreach(var i in input)
        r = group.Combine(r, i);
      return r;
   }
}

public static class Program
{
   public static void Main()
   {
      var t = new[] {4, 8, 15, 16, 23, 42};
      var sum = t.Reduce(new IntAddition()); 
   }
}


Вот вам ООП подход.
В ФП вместо ООП тут будет не функция, а структура, с точно такой же топологией.
   public static T Reduce(this IEnumerable<T> input, (Func<T> neutralElement, Func<T, T, T> combine))
   {
      var r = group.neutralElement();
      foreach(var i in input)
        r = group.combine(r, i);
      return r;
   }
public static class Program
{
   public static void Main()
   {
      var t = new[] {4, 8, 15, 16, 23, 42};
      var intAddition = (()=>0, (int x, int y)=>x+y);
      var sum = t.Reduce(intAddition); 
   }
}

Видим, что всё работает точно так же, как и следовало ожидать. То есть мы по-прежнему инжектим зависимость через "интерфейс", просто теперь это не какая-то специальная конструкция в языке, а просто обычная неизменяемая структура с полями-функциями.
Отличия начинаются ровно в том месте, где у нас члены этого интерфейса начинают что-то менять.
например, так:

public interface IAccumulator<T>
{
  public T Current{get;}
  public void Accumulate(T value);
} 

public class AddAccumulator: IAccumulator<int>
{
   private _value = 0;
   public int Current{ get => _value; }
   public void Accumulate(int value) => _value += value;
}

public static class H
{
   public static T Reduce<T>(this IEnumerable<T> input, IAccumulator<T> accumulator)
   {
      foreach(var i in input)
        accumulator.Accumulate(i);
      return accumulator.Current;
   }
}

public static class Program
{
   public static void Main()
   {
      var t = new[] {4, 8, 15, 16, 23, 42};
      var sum = t.Reduce(new AddAccumulator()); 
   }
}

Вот для такого кода написать прямой аналог на ФП уже не получится, потому что в каноническом ФП мы не можем менять состояние существующих объектов.
Строгое ФП потребует от нас поменять сигнатуры методов и слегка переколбасить код.
Что-то вроде
public struct record Accumulator<T>(T value, Func<T, Accumulator<T>> accumulate);

public static class H
{
   public static T Reduce<T>(this IEnumerable<T> input, Accumulator accumulator)
   {
      foreach(var i in input)
        accumulator = accumulator.Accumulate(i);
      return accumulator.Current;
   }
   public static Accumulator<T> CreateAccumulator<T>(T value, Func<T, T, T> combine)
     => new Accumulator(value, (T x) => CreateAccumulator(combine(value, x), combine);
}


public static class Program
{
   public static void Main()
   {
      var t = new[] {4, 8, 15, 16, 23, 42};
      var addAccumulator = H.CreateAccumulator(0, (x, y)=> x+y);
      var sum = t.Reduce(addAccumulator); 
   }
}

Вот как раз тут видно, почему ООП без изменяемого состояния — "не ООП".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.