Возможно, вы уже слышали о так называемом «функциональном» программировании. Возможно, вы даже подумываете о том, что вам стоит его как-нибудь попробовать.

Ни в коем случае этого не делайте!



Функциональное программирование полно недочётов, оно не подходит для реальных проектов. Его применение приведёт к резкому падению производительности труда. Почему это так? Давайте выясним.

?> Часть 2

Функциональное программирование не может удовлетворить многогранным корпоративным требованиям



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

Полагаю, вышеприведённый текст особенно понятным не выглядит. Но совсем скоро всё встанет на свои места.

Так называемое «функциональное» программирование не имеет нормального механизма создания абстракций. Дело в том, что оно основано на математических правилах (эти правила, что совершенно понятно, не находят применения в реальном мире, находящемся снаружи стен учебных заведений). В отличие от ООП, функциональное программирование не делает никаких попыток пойти навстречу всем тем многочисленным, жёстким и сложным требованиям к ПО, которые предъявляют организации.

В следующем фрагменте кода продемонстрированы проблемы, широко распространённые там, где пользуются функциональным программированием:

import { filter, first, get } from 'lodash/fp';

const filterByType = type =>
  filter( x => x.type === type );

const fruits = [
  { type: 'apple', price: 1.99 },
  { type: 'orange', price: 2.99 },
  { type: 'grape', price: 44.95 }  
];

const getFruitPrice = type => fruits =>
  fruits
  |> filterByType(type)
  |> first
  |> get('price');

const getApplePrice = getFruitPrice('apple');

console.log('apple price', getApplePrice(fruits));

Если это всё ничего кроме злости у вас не вызывает, то знайте, что вы в этом не одиноки!

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

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

Функциональные программные решения не выдерживают проверку временем



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

В противовес вышеприведённому злополучному примеру функционального кода давайте взглянем на хорошо абстрагированный образец программы, написанной в соответствии с методологией ООП. Этот фрагмент кода делает то же самое, что и предыдущий, но в нём применены подходящие абстракции. Он, кроме того, не потеряет актуальности со временем:

class Fruit {
  constructor(type, price) {
    this.type = type;
    this.price = price;
  }
}

class AbstractFruitFactory {
  make(type, price) {
    return new Fruit(type, price);
  }
}

class AppleFactory extends AbstractFruitFactory {
  make(price) {
    return super.make("apple", price);
  }
}

class OrangeFactory extends AbstractFruitFactory {
  make(price) {
    return super.make("orange", price);
  }
}

class GrapeFactory extends AbstractFruitFactory {
  make(price) {
    return super.make("grape", price);
  }
}

class FruitRepository {
  constructor() {
    this.fruitList = [];
  }

  locate(strategy) {
    return strategy.locate(this.fruitList);
  }

  put(fruit) {
    this.fruitList.push(fruit);
  }
}

class FruitLocationStrategy {
  constructor(fruitType) {
    this.fruitType = fruitType;
  }

  locate(list) {
    return list.find(x => x.type === this.fruitType);
  }
}

class FruitPriceLocator {
  constructor(fruitRepository, locationStrategy) {
    this.fruitRepository = fruitRepository;
    this.locationStrategy = locationStrategy;
  }

  locatePrice() {
    return this.fruitRepository.locate(this.locationStrategy).price;
  }
}

const appleFactory = new AppleFactory();
const orangeFactory = new OrangeFactory();
const grapeFactory = new GrapeFactory();

const fruitRepository = new FruitRepository();
fruitRepository.put(appleFactory.make(1.99));
fruitRepository.put(orangeFactory.make(2.99));
fruitRepository.put(grapeFactory.make(44.95));

const appleLocationStrategy = new FruitLocationStrategy("apple");

const applePriceLocator = new FruitPriceLocator(
  fruitRepository,
  appleLocationStrategy
);

const applePrice = applePriceLocator.locatePrice();

console.log("apple", applePrice);

Как видите, вся основная функциональность здесь качественно абстрагирована. Этот — цельный код.

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

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

Серьёзный менеджмент нуждается в серьёзных возможностях



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

Как известно любому настоящему проект-менеджеру, трудящемуся в корпоративной среде, реальную ценность для бизнеса представляют лишь новые возможности приложений.

Программистам не нужно позволять тратить ресурсы на такие бесполезные занятия, как модульное тестирование и рефакторинг кода.

Очевидно то, что в этой сфере у так называемого «функционального» программирования имеются большие проблемы. Оно неоправданно упрощает выполнение таких ненужных действий, как рефакторинг и модульное тестирование. Это, в свою очередь, служит отвлекающим фактором для команды разработчиков. Члены этой команды могут случайно потратить время на эти бесполезные занятия вместо того, чтобы оснащать приложение новыми возможностями.

Следующий пример ясно показывает неполноценность функционального программирования. Использование этой методологии слишком сильно упрощает рефакторинг:

// до рефакторинга:

// calculator.js:
const isValidInput = text => true;

const btnAddClick = (aText, bText) => {
  if (!isValidInput(aText) || !isValidInput(bText)) {
    return;
  }
}


// после рефакторинга:

// inputValidator.js:
export const isValidInput = text => true;

// calculator.js:
import { isValidInput } from './inputValidator';

const btnAddClick = (aText, bText, _isValidInput = isValidInput) => {
  if (!_isValidInput(aText) || !_isValidInput(bText)) {
    return;
  }
}

Если вас воротит от простоты этого рефакторинга — знайте, что такие ощущения испытываете не только вы. До рефакторинга было шесть строк кода, после стало семь строк? Это что — шутка?

Сравним это с нормальным рефакторингом объектно-ориентированного кода:

// до рефакторинга:
public class CalculatorForm {
    private string aText, bText;
    
    private bool IsValidInput(string text) => true;
    
    private void btnAddClick(object sender, EventArgs e) {
        if ( !IsValidInput(bText) || !IsValidInput(aText) ) {
            return;
        }
    }
}


// после рефакторинга:
public class CalculatorForm {
    private string aText, bText;
    
    private readonly IInputValidator _inputValidator;
    
    public CalculatorForm(IInputValidator inputValidator) {
        _inputValidator = inputValidator;
    }
    
    private void btnAddClick(object sender, EventArgs e) {
        if ( !_inputValidator.IsValidInput(bText)
            || !_inputValidator.IsValidInput(aText) ) {
            return;
        }
    }
}

public interface IInputValidator {
    bool IsValidInput(string text);
}

public class InputValidator : IInputValidator {
    public bool IsValidInput(string text) => true;
}

public class InputValidatorFactory {
    public IInputValidator CreateInputValidator() => new InputValidator();
}

Именно так выглядит настоящее программирование! До рефакторинга было девять строк кода, а после стало 22. Для выполнения этой работы требуется приложить больше усилий. Это заставит программистов, работающих на компанию, дважды подумать перед тем, как ввязываться в подобную авантюру.

Ущербная концепция декларативного подхода к программированию



Так называемые «функциональные» программисты неоправданно гордятся собой из-за того, что пишут декларативный код. Но тут нечем гордиться — подобный код лишь создаёт иллюзию продуктивности.

Основная сфера ответственности любого разработчика должна заключаться в размышлениях о подходящих жёстких объектно-ориентированных абстракциях (этого, кроме того, требует любая серьёзная большая организация).

Взглянем на хорошо абстрагированный объектно-ориентированный код:

class CountryUserSelectionStrategy {
  constructor(country) {
    this.country = country;
  }
  
  isMatch(user) {
    return user.country === this.country;
  }
}

class UserSelector {
  constructor(repository, userSelectionStrategy) {
    this.repository = repository;
    this.userSelectionStrategy = userSelectionStrategy;
  }
  
  selectUser() {    
    let user = null;

    for (const u in users) {
      if ( this.userSelectionStrategy.isMatch(u) ) {
        user = u;
        break;
      }
    }
    
    return user;
  }
}

const userRepository = new UserRepository();
const userInitializer = new UserInitializer();
userInitializer.initialize(userRepository);

const americanSelectionStrategy = new CountryUserSelectionStrategy('USA');
const americanUserSelector = new UserSelector(userRepository, americanSelectionStrategy);

const american = americanUserSelector.selectUser();

console.log('American', american);

Присмотритесь к императивному циклу for (const u in users). Не обращайте внимания на второстепенный шаблонный объектно-ориентированный код, не связанный с выполняемой задачей. Его нужно было включить в программу для того, чтобы сделать этот пример соответствующим жёстким требованиям абстрагирования, предъявляемым любой серьёзной организацией к коду.

Декларативный код, с другой стороны, является слишком конкретным, он, что неправильно, заставляет разработчика ориентироваться на менее важные вещи вроде бизнес-логики. Сравните мощное решение корпоративного уровня, описанное выше, со следующим образцом неполноценного «декларативного» кода:

SELECT * FROM Users WHERE Country=’USA’;

Каждый раз, когда я вижу SQL-код, меня прямо-таки коробит от его декларативности. Почему SQL? Почему бы программистам не использовать адекватные абстракции корпоративного класса и не писать бы нормальный объектно-ориентированный код? Особенно учитывая то, что в их распоряжении уже есть всё необходимое. Это — просто взрыв мозга, иначе и не скажешь.

Моделирование реального мира



Объектно-ориентированное программирование — это гениально. В отличие от «функционального» программирования оно отлично подходит для моделирования объектов реального мира. Это возможно благодаря тому, что ООП поддерживает такие продвинутые технологии, как наследование, полиморфизм и инкапсуляция.

Любой уважающий себя программист должен ежедневно использовать наследование для достижения высокого уровня многократного использования кода. Как уже было сказано, наследование отлично подходит для моделирования реального мира. Кошки, например, всегда наследуют свои свойства и поведение от единственного абстрактного животного из реального мира. Жизнь зародилась в океане несколько миллиардов лет назад. В результате все млекопитающие (включая кошек) унаследовали свойства от некоей первозданной рыбы. Например, говоря объектно-ориентированным языком, это может быть нечто вроде garfield.fishHead — свойства, описывающего рыбью голову кота Гарфилда. То же самое можно сказать и о поведении, что, в терминологии ООП может выглядеть как garfield.swim() (плавание) и garfield.layCaviar() (икрометание). Никого не удивляет то, что кошки так сильно любят принимать ванны и плавать! Люди, в сущности, это то же самое. Человек, если захочет, легко может начать метать икру!

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

Функции всегда должны быть привязаны к объектам



То, что функции должны быть всегда привязаны к объектам, подсказывает нам здравый смысл. Кроме того, это отлично воспроизводит то, что мы можем видеть в реальном мире. В блокнотах имеется встроенный метод «записать». Это метод вызывается каждый раз, когда вы планируете что-то записать в блокнот. Вы можете этого и не осознавать, но и у вас есть методы. Например — нечто вроде .eat(veggies), позволяющий вам есть овощи, и .doHomework(), благодаря которому вы делали домашние задания когда учились в школе. Это — просто здравый смысл. Как иначе ваша мама, когда вы были помладше, заставляла бы вас есть овощи и делать домашние задания? Конечно, она напрямую вызывала эти ваши методы!

Ни одну работу в реальном мире нельзя выполнить, не привлекая профессионального менеджера, координирующего задачи. Его можно представить в виде объекта Manager. Молодёжи, возможно, нужен менеджер, помогающий удовлетворять базовые человеческие потребности, ну, скажем, вроде «смотреть Netflix и отдыхать».

Кто, в конце концов, будет координировать все составные части этого сложного процесса? А люди достаточно умные могут нанять и несколько менеджеров, поступив в точности так, как рекомендует ООП.

В реально мире создание чего-то нового и интересного, кроме того, требует специальной фабрики, которую можно представить в виде объекта Factory. У Леонардо да Винчи, например, была фабрика по производству картин — MonaLisaFactory, а Дональд Трамп строит секретную фабрику WallFactory.

Несложно заметить, что это — очередной гвоздь в «функциональный» гроб, так как функциональное программирование не пытается моделировать реальный мир. Функциям позволено существовать отдельно от объектов, а это просто неправильно.

Функциональное программирование, очевидно, не подходит для любой серьёзной работы, ориентированной на реальный мир.

Функциональное программирование не даёт возможностей для профессионального роста



Первое, и главное, на что должны обращать внимание разработчики, это их постоянное самосовершенствование и профессиональный рост. Для того чтобы в совершенстве овладеть ООП программист должен усвоить огромное количество знаний.

Во-первых, программисту нужно изучить продвинутые ООП-техники вроде наследования, абстракции, инкапсуляции и полиморфизма. Затем нужно хорошо освоить тьму паттернов проектирования (вроде паттерна «Синглтон») и начать использовать их в своём коде. Существует около 30 базовых паттернов проектирования, которые нужно очень хорошо знать. В идеале где-то в процессе изучения паттернов программист должен начать использовать в своём коде различные абстракции корпоративного уровня.

Следующий шаг — ознакомление с технологиями наподобие Domain-Driven Design, и изучение того, как разделять на части монолитные программные проекты. Кроме того, рекомендовано изучение подходящих инструментов для рефакторинга кода вроде Resharper, так как рефакторинг объектно-ориентированного кода — задача нетривиальная.

Для того чтобы достичь достойного уровня в сфере ООП нужно 20-30 лет. Но надо отметить, что даже большинство тех, у кого наберётся 30 лет опыта, не могут считаться настоящими мастерами ООП. Путь ООП-ученика тяжёл и наполнен неопределённостью. Объектно-ориентированного разработчика, в результате, ждёт учёба длиною в жизнь. Это ли не прекрасно?

А как насчёт несчастных функциональных программистов? К сожалению, им нужно изучить не так уж и много. Я сам учил нескольких джуниоров функциональному программированию на JavaScript. У них начало неплохо получаться примерно через полгода. Им просто понадобилось понять несколько базовых концепций, а затем надо было научиться быстро их применять. Где тут упоение от пожизненной учёбы? Я бы им не позавидовал.

Продолжение следует…

Уважаемые читатели! Что вам больше всего не нравится в функциональном программировании?

Комментарии (349)


  1. akryukov
    05.08.2019 12:39
    +15

    Слишком толсто. Попробуйте еще раз.


    1. PsyHaSTe
      05.08.2019 13:16
      +2

      Толстовато, но тем не менее. Уверенность, что функции привязвать к объектам это хорошее решение, что сабтайпинг должен происходить обязательно через наследование, что передать IFooBarBaz с единственным методом намного лучше чем просто лямбду, ...


      Ну и да, ФП и правда легче. В ФП паттернов-то всего ничего: IO/Reader/Middleware/..., и большинство из них монады, поэтому их и компоновать как становится понятно, даже если сам паттерн не до конца понятен.


      1. AllexIn
        05.08.2019 17:59
        +5

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


  1. mayorovp
    05.08.2019 12:43
    +3

    С SQL тут все-таки переборщили: это и правда неудобный язык. Расположение SELECT перед FROM приводит к постоянным использованием имён таблиц перед их объявлением, а это ломает контекстные подсказки в IDE. Не говорю уже о том, что мне вообще неизвестны редакторы SQL, которые бы не тупили и не тормозили.


    Ну и фабрики тут упомянуты зря: после появления делегатов/лямбд/стрелочных функций в известных мне языках они выглядят куда компактнее. Не говоря уже о том, что в представленной задаче шаблон Abstract Factory вообще не нужен.


    1. moscas
      05.08.2019 14:48

      Поэтому мы в DataGrip придумали postifix-completion :) d3nmt5vlzunoa1.cloudfront.net/datagrip/files/2019/03/02-PostfixCompletion.gif


      1. mayorovp
        05.08.2019 14:56

        Классно, но не интуитивно понятно.


        1. moscas
          05.08.2019 15:05

          Согласен. Надо просто знать, что он есть :)


    1. alexs0ff
      05.08.2019 14:50

      делегатов/лямбд/стрелочных функций

      Так это все делалось для приближения ООП к ФП.


      1. mayorovp
        05.08.2019 14:54

        Но от "приближения" оно не перестаёт быть ООП. Однако, оно перестаёт содержать страшные классы, введенные ради всего одного метода.


        1. alexs0ff
          05.08.2019 14:59

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


          1. mayorovp
            05.08.2019 15:00

            Если вы не утверждали, что ООП перестало быть ООП — то непонятно с чем вы спорите.


          1. PsyHaSTe
            05.08.2019 15:44

            Это больше карго-культ, если честно. Смысл вышек и самолетов… Простите, лямбд и стрелочных функций не в том, чтобы просто быть.


        1. MacIn
          05.08.2019 22:34
          +1

          Но это не нечто новое, привнесенно-ФПшное. Комбинирование ООП подхода с самыми обычными императивными функциями/процедурами без нужды держать классы ради одного метода без сохранения контекста есть, скажем, в том же Object Pascal-Delphi…


          1. mayorovp
            05.08.2019 23:05

            Вы сейчас точно мне отвечаете?


            1. MacIn
              06.08.2019 16:30

              Воспринимайте это как дополнение, а не возражение.


    1. snamef
      05.08.2019 23:21

      если ты сказал про селект. То куда удобнее, компактнее и читабольшее исользовать синтаксис ФП например стрима:
      select(actor, id, last_name -> text) -> filter(text -> text.startswith('A')).
      При этом фича ФП будет также присвоение функции имени переменной, то есть с возможностью использовать опять:

      aactor_queuery = select(actor, id, last_name -> text) -> filter(text -> text.startswith('A'))
      aactor_queuery -> filter(...)

      также рекурсия что то вроде:
      select_rec = select(table, id,last_name, next_id) -> filter(next_id in select_rec.id)


  1. hensew
    05.08.2019 12:44
    +4

    Только на последнем абзаце понял, что это стёб.


    1. AlexPu
      05.08.2019 13:01
      +2

      я это понял вот на этом абзаце:

      Дело в том, что оно основано на математических правилах (эти правила, что совершенно понятно, не находят применения в реальном мире, находящемся снаружи стен учебных заведений)


      Ну не то чтобы так вот сразу понял — в конце концов в мире полно двоечников, которые не смогли толком освоить университетский курс математики для гуманитарных специальностей… Просто интига после этой фразы как то совсем пропала… она, интрига начала сильно болеть на фразе

      Функциональное программирование полно недочётов, оно не подходит для реальных проектов


      ибо реальных проектов реализованных на ФЯП как грази… но всегда ведь можно сказать, что они все эти проекты реализованы с использованием неподходящих инструментов — в смысле можно сказать, и тебя за это не арестуют и не наложат штраф за потстрекательство к… к чему нибудь.

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


    1. isden
      07.08.2019 15:40

      Я после первого примера ФП кода понял, что что-то не так O_o


  1. jknight
    05.08.2019 12:51
    +3

    Астрологи объявили неделю жЫрных набросов. Объемы сальной прослойки в районе пуза увеличиваются вдвое.

    P.S. Годно, очень годно! Ждем часть 2.


  1. AriesUa
    05.08.2019 13:00
    +1

    Хороший стеб однако.

    Но тем не менее, не стоит быть фанатиком чего-то одного. Думаю, всегда можно найти компромис.


  1. Sabubu
    05.08.2019 13:04
    +6

    Вот вы попробовали потроллить, а функциональное программирование на самом деле плохо годится для реальных приложений. Примеры:


    • ФП работает с "чистыми" функциями. Но реальное приложение работает с файлами, БД, внешними сервисами, которые нарушают требования "чистоты", в итоге приходится придумывать костыли
    • в ФП переменные иммутабельны. Если у вас есть массив из 100 пользователей, и вы должны одному обновить рейтинг, вы должны сделать полную копию массива. И так на каждое изменение. Это негативно влияет на производительность и потребление памяти.(пример: реакт, где на любой чих пересоздается стейт заново и хорошо, если он у вас маленький).
    • в ФП переменные иммутабельны, а структуры это не объекты и они передаются по значению, а не по ссылке. Там, где вы в PHP пишете $user->updateKarma(), в ФП вы должны писать user2 = updateKarma(user), и не забыть заменить старые копии user на user2 во всех местах кода, во всех коллекциях и списках. Удачи!
    • в ФП нет исключений. В нормальном программировании вы просто пишете действия подряд, если произойдет ошибка, выбросится исключение и оставшиеся не будут выполняться. В ФП вам приходится лепить костыли, делая типы вроде Maybe и делая "пропуск" функции, если ей передано Maybe с ошибкой внутри.
    • в ФП нет ООП, которым удобно представлять объекты реального мира. Вместо этого там приходится делать разрозненные структуры и функции для работы с ними — то, для замены чего и придуман ООП. А ведь в ФП вы еще не можете модифицировать объект, с которым работаете.
    • в ООП функуция это просто последовательность шагов: 1) проверь, что такого емайла нет 2) добавь запись в БД 3) отправь письмо для подтверждения почты. В ФП же так не принято, а принято комбинировать функции в составные функции, из-за чего разбор кода превращается в кошмар (registrator = (formData) => combine(formValiadtor(rules), fieldExtractor('email'), emailNotInDbChecker(db), emailToDbAdder(db), emailSender(db, sendService)).

    Любимый паттерн разработчиков ФП. и JS — это сделать в одном файле определения функций (причем к которым нельзя перейти по клику или найти поиском), а в другом — их вызвать. Типичный пример (похожий код есть в jQuery):


    var attrs = ['id', 'name', 'age'];
    attrs.forEach((attr) => {
    root['get' + attr] = (x) => root[x];
    }


    Удачи вам найти определение функции getName поиском.


    А ФП вроде Хаскелл позволяет создать 10 вариантов функции с одинаковым именем, но разным типом аргументов. Удачи вам найти при рефакторинге, какая из функций (раскиданных по разным файлам) вызывается в том или ином случае.


    Но, конечно, в программах для вычисления чисел Фибоначчи ФП не знает себе равных.


    1. mayorovp
      05.08.2019 13:15
      +5

      в ФП нет исключений

      Вообще-то есть, аж в двух видах — в виде, собственно, исключения (которое условно типизируется как ?) и в виде Either-подобной монады.


      Любимый паттерн разработчиков ФП. и JS

      Первый раз такое вижу.


      Удачи вам найти определение функции getName поиском.

      Если речь идет о JS — то пишем getName в консоли и дальше браузер сам найдет эту функцию.


      А ФП вроде Хаскелл позволяет создать 10 вариантов функции с одинаковым именем, но разным типом аргументов

      Мало чем отличается от полиморфизма в ООП. Да, в ООП функция лежит всегда рядом с объектом — зато тип этого объекта будет известен только в рантайме.


      1. Huan
        05.08.2019 13:28

        Вы забыли, что JS сегодня не только в браузерах работает.


        1. mayorovp
          05.08.2019 13:31

          Запускаем ноду с ключом --inspect и подключаемся через chrome://inspect...


    1. DarthVictor
      05.08.2019 13:23
      +1

      У Вас получилось чуть потоньше, чем у автора, но всё еще есть куда стремится. Я бы поменял про исключения. Все-таки ```catch(AnyException e) { log(e); }``` – это говнокод даже по меркам современного индус-триального программирования.


      1. Athari
        06.08.2019 20:03

        Тем не менее, реальность такова, что подобный говнокод присутствует в каждом серьёзном приложении. Вопрос лишь в количестве.


    1. PsyHaSTe
      05.08.2019 13:27

      ФП работает с "чистыми" функциями. Но реальное приложение работает с файлами, БД, внешними сервисами, которые нарушают требования "чистоты", в итоге приходится придумывать костыли

      ООП работает с "объектами". Но реальное приложение работает с файлами, БД, внешними сервисами, которые реализованны на чистом С, в итоге приходится придумывать костыли


      в ФП переменные иммутабельны. Если у вас есть массив из 100 пользователей, и вы должны одному обновить рейтинг, вы должны сделать полную копию массива.

      Приложение на хаскелле который делает "копию на каждый чих" скорее всего будет производительнее вашего мутабельного варианта на Java/C#/… И да, copy elision, даже С++ умеет.


      в ФП переменные иммутабельны, а структуры это не объекты и они передаются по значению, а не по ссылке.

      Слава богу, в ФП не надо помнить что там по ссылке, а что по значению, можно просто писать бизнес-логику.


      в ФП нет исключений.

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


      в ФП нет ООП, которым удобно представлять объекты реального мира.

      Это шутка?


      в ООП функуция это просто последовательность шагов

      Да, только в ООП у вас 100500 зависимостей IFooBaz у которых тоже миллион зависимостей, что-то куда-то как-то передается, и проследить флоу становится нереально. Особенно если мы какой-нибудь DI заюзаем, с сессионным резолвом зависимостей и т.п. Ух как весело становится.


      Любимый паттерн разработчиков ФП. и JS — это сделать в одном файле определения функций (причем к которым нельзя перейти по клику или найти поиском), а в другом — их вызвать. Типичный пример (похожий код есть в jQuery):

      А где тут ООП? Обычный императивный код, который грязно мутирует глобальный стейт. Как вам такой ООП код:


      var attrs = ['id', 'name', 'age'];
      attrs.forEach((attr) => {
      root['get' + attr] = (x) => root[x];
      }

      Удачи вам найти определение класса реализующего обновление рута поиском.


      А ООП вроде Java позволяет создать 10 классов наследующихся от одного интерфейса, но разными имплементациями. Удачи вам найти при рефакторинге, какой из классов (раскиданных по разным файлам) вызывается в том или ином случае.


      1. vintage
        06.08.2019 18:31

        Приложение на хаскелле который делает "копию на каждый чих" скорее всего будет производительнее вашего мутабельного варианта на Java/C#/…

        Приведите бенчмарк что ли в подтверждение своих слов.


        Слава богу, в ФП не надо помнить что там по ссылке, а что по значению, можно просто писать бизнес-логику.

        А что вы делаете, когда выясняете, что приложение тормозит? Или скажете, что приложения написанные на ФП языке не тормозят?


        Одно время в дотнете помню ненаблюдаемое исключение в асинхронной операции могло уронить рантайм.

        Как видим, асинхронное программирование — та ещё ерунда.


        1. 0xd34df00d
          06.08.2019 18:50

          А что вы делаете, когда выясняете, что приложение тормозит?

          Профилирую, как это делается в любых языках.


          1. vintage
            06.08.2019 19:07

            Дальше-то вы что делаете?


            1. 0xd34df00d
              06.08.2019 19:28

              Зависит. Чаще всего меняю алгоритм, реже добавляю/убираю {-# LANGUAGE Strict #-} в каком-нибудь из модулей, ещё реже начинаю играться с аннотациями строгости конкретных функций и байндингов. Пару раз за всю практику переписывал код в ST.


              1. vintage
                06.08.2019 22:32

                То есть вам нужно знать как ваш код компилируется, чтобы подшаманить и он компилировался во что-то более эффективное. И не факт, что эти шаманства не дадут обратный эффект при обновлении компилятора. В JS это постоянная беда. JIT компилятор даже между запусками может всё по разному соптимизировать исходя из кучи эвристик. Идиомы типа "передача по ссылке" хотя бы детерминированы.


                1. 0xd34df00d
                  06.08.2019 22:39

                  Нет, зачем мне знать, как он компилируется? Речь ведь о семантике выполнения. Компилятор может сделать лучше, если он сам выведет более эффективную strictness annotation, но хуже он не сделает точно.


      1. Athari
        06.08.2019 20:24

        ООП работает с "объектами". Но реальное приложение работает с файлами, БД, внешними сервисами, которые реализованны на чистом С, в итоге приходится придумывать костыли

        "Чистый C" часто очень ООПный. Обычно есть "create"/"open"/"new", "delete"/"remove"/"close" и зоопарк функций, принимающих первым аргументом хэндл. Различие с ООП исключительно синтаксическое. Перекладывается подобный API из сишного в ООПный и обратно очень тонкими синтаксическими прослойками.


        Приложение на хаскелле который делает "копию на каждый чих" скорее всего будет производительнее вашего мутабельного варианта на Java/C#/… И да, copy elision, даже С++ умеет.

        Есть разные алгоритмы, и где-то выгоднее иммутабельность, а где-то она убивает производительность. Если нужна максимальная скорость, алгоритм всё равно будут писать на C++ или подобном языке.


    1. Virviil
      05.08.2019 13:34
      +1

      1. Любая работа с ресурсами — это костыли. Вообще все функции кроме чистых — это костыли. Либо это dispodsble, либо это RAII — все одно. Вот только ФП не создает иллюзии, что ты работаешь безопастно.
      2. Точно. Это позволяет программисту почаще задумываться о структурах данных, которые он использует — например в вашем примере с пользователями это был бы связный список или хеш, которые были бы практически столь же эффективны в io нагруженных приложениях. Но вот для складывания матриц в GPU ФП скорее всего не подходит.
      3. Благодаря function as first class, сопоставлению с образцом и грамотному написанию кода в ФП в большинстве случаев не используется операция присваивания (=) — "переменные" не нужны.
      4. Исключения — это костыль. В отличие от монады. И это математически доказал Дейкстра в своих основах структурного программирования, которые исключения жестко нарушают. Кроме того, "костыли" в виде Maybe уже написаны за нас. Использовать их — не сложнее чем написать ключевое слово raise.
      5. ФП не противоречит ООП. Кроме того, вызовы u.foo() и foo(u) ничем ни отличаются. Это хорошо видно к примеру в питоне, где метод первый аргументом принимает в обязательном порядке self. Модификация объекта — это головная боль с того момента, как программе появляется многопоточность. А она появляется во всех "серьезных" программах.
      6. Это называется декларативное программирование — вершина грамотной архитектуры и композиции программных систем. Приучить мозг, который ходит по шагам, к понимаю декларативных описаний занимает несколько недель — если конечно захотеть.


      1. mayorovp
        05.08.2019 13:39

        Что, даже линейные типы — костыли? :-)


      1. PsyHaSTe
        05.08.2019 13:55

        Любая работа с ресурсами — это костыли.

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


        Точно. Это позволяет программисту почаще задумываться о структурах данных, которые он использует — например в вашем примере с пользователями это был бы связный список или хеш, которые были бы практически столь же эффективны в io нагруженных приложениях. Но вот для складывания матриц в GPU ФП скорее всего не подходит.

        Сколько матриц на ООП вы сегодня сложили? Сириузли, когда у вас проблемы с производительностью, вы и ООП использовать не будете, потому что известная AoS vs SoA проблема.


        Благодаря function as first class, сопоставлению с образцом и грамотному написанию кода в ФП в большинстве случаев не используется операция присваивания (=) — "переменные" не нужны.

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


        4

        По этому пункту нет возражений, спасибо за уточнение.


        ФП не противоречит ООП.

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


        6

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


        1. vintage
          06.08.2019 18:17
          -1

          когда у вас проблемы с производительностью, вы и ООП использовать не будете, потому что известная AoS vs SoA проблема.

          Если я поравильно понял о чём вы, то это проблема лишь языков типа Java. В нормальных языках объекты можно размещать прямо в массиве. Не без ограничений, конечно, но всё же.


          1. mayorovp
            06.08.2019 18:28

            Нет, это прежде всего проблема для языков типа Си. В Java с этим все настолько плохо, по умолчанию, что проблема AoS vs SoA уходит на второй план. Но годится в качестве примера важности порядка размещения элементов в памяти.


            1. vintage
              06.08.2019 18:34

              Нету в языках типа Си такой проблемы. Ну кроме совсем маргинальных случаев.


              1. poxvuibr
                06.08.2019 18:52

                Ну массив из полиморфных объектов в C++ сделать не получится. А если не делать их полифорными, то ООП тут не при чём.


                1. vintage
                  06.08.2019 19:08

                  1. Можно и полиморфный массив сделать.
                  2. ООП и полиморфизм ортогональные понятия.


                  1. poxvuibr
                    06.08.2019 23:34

                    Можно и полиморфный массив сделать.

                    Как это сделать в C++?


                    ООП и полиморфизм ортогональные понятия.

                    Нет, это не так. Без полиморфизма не будет ООП.


                    1. vintage
                      06.08.2019 23:44

                      Как это сделать в C++?

                      Через Variant или как он там в C++ называется.


                      Нет, это не так. Без полиморфизма не будет ООП.

                      Вполне себе будет. Суть ООП в инкапсуляции, а не морфизмах.


                      1. poxvuibr
                        07.08.2019 00:02

                        Через Variant или как он там в C++ называется.

                        Покажите код, очень интересно.


                        Суть ООП в инкапсуляции, а не морфизмах.

                        Если убрать инкапсуляцию, то можно договориться, что поля, которые начинаются с _ или кончаются на _, трогать нельзя, как это делалось в php в джаваскрипте и по моему в питоне. И будет ООП.


                        Если убрать полиморфизм, то нельзя будет написать код, который ориентируется на то, что ему будут переданы объекты, реализующие интерфейс своим способом и на этом кончится ООП.


                        1. vintage
                          07.08.2019 14:42

                          Покажите код, очень интересно.

                          https://dlang.org/phobos/std_variant.html#VariantN


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

                          Это вы говорите про сокрытие. Инкапсуляции она ортогональна. https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%BA%D0%B0%D0%BF%D1%81%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)


                          Если убрать полиморфизм, то нельзя будет написать код, который ориентируется на то, что ему будут переданы объекты, реализующие интерфейс своим способом и на этом кончится ООП.

                          Не кончится. Кончится лишь полиморфизм.


                          1. poxvuibr
                            07.08.2019 15:05

                            Мало того, что в ответ на просьбу показать код на C++ вы показали.код на D, так он ещё и не создаёт массив полиморфных объектов ))


                            Это вы говорите про сокрытие. Инкапсуляции она ортогональна.

                            Не вопрос, можно сделать объект, в котором вообще нет данных. И, если он полиморфный, то это будет всё ещё ООП.


                            Не кончится. Кончится лишь полиморфизм.

                            Вы знаете ООП системы в которых не используется полиморфизм?


                            1. vintage
                              07.08.2019 16:40

                              на просьбу показать код на C++ вы показали.код на D

                              Аналогичный код на C++ вы можете написать самостоятельно. Шаблоны это позволяют.


                              он ещё и не создаёт массив полиморфных объектов

                              Он создаёт полиморфный контейнер. Можете сделать массив этих контейнеров — получится полиморфный массив.


                              можно сделать объект, в котором вообще нет данных. И, если он полиморфный, то это будет всё ещё ООП

                              А я говорил, что существование объектов без поведения или без состояния — это не будет ооп?


                              Вы знаете ООП системы в которых не используется полиморфизм?

                              1C :-D


                              1. poxvuibr
                                07.08.2019 19:26

                                Аналогичный код на C++ вы можете написать самостоятельно. Шаблоны это позволяют.

                                Мог бы, я бы не спрашивал )). Можно пример создания массива полиморфных объектов, которые наследуются от абстрактного класса Shape?


                                А я говорил, что существование объектов без поведения или без состояния — это не будет ооп?

                                Вы говорили, что суть ООП инкапсуляции. Значит если её убрать — будет уже не ООП?


                                1C :-D

                                С козырей зашли? ))


                                1. vintage
                                  08.08.2019 09:17

                                  Вы правда думаете, что я сейчас попрусь вспоминать этот убогий C++, чтобы вам что-то доказать?


                                  А если вам ножки отрезать, вы перестанете быть человеком? Так же и инкапсуляция никуда не девается, если объекту пока что не нужно состояние.


                                  1. poxvuibr
                                    08.08.2019 10:12

                                    Вы правда думаете, что я сейчас попрусь вспоминать этот убогий C++, чтобы вам что-то доказать?

                                    Действительно. Тогда можно пример создания массива полиморфных объектов, которые наследуются от абстрактного класса Shape, только на D?


                                    Так же и инкапсуляция никуда не девается, если объекту пока что не нужно состояние.

                                    А как тогда продемонстрировать, что суть ООП в инкапсуляции?


                                    1. vintage
                                      08.08.2019 11:52

                                      https://run.dlang.io/is/5GIyYv


                                      Это следует из определения объекта.


                                      1. poxvuibr
                                        08.08.2019 12:36

                                        Спасибо за пример. Вот эта строчка всё портит.


                                        alias AnyShape = Algebraic!(Cyrcle,Square,Rect);

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


                                        shapes[0].x.writeln; 

                                        не получается, подозреваю, что вызвать методы тоже не выйдет. Соответственно массива полиморфных объектов пока не получилось.


                                        Это следует из определения объекта.

                                        Объект это набор методов, которые чем-то манипулируют. Поля для объекта не обязательны.


                                        1. VolCh
                                          08.08.2019 13:13

                                          В классический объект входит и то, и другое. Вырожденные случаи (методы тоже не обзяательны) — так себе аргумент.


                                          1. poxvuibr
                                            08.08.2019 21:09

                                            В классический объект входят только методы, существование полей, да, подразумевается, но только подразумевается. Объект, в котором нет полей это нормальная штука.


                                        1. vintage
                                          08.08.2019 17:21
                                          -2

                                          Получается, что при добавлении ещё одного потомка нужно будет её поправить.

                                          Ужас-то какой..


                                          Смысл полиморфизма в том, чтобы можно было добавлять реализации не внося такие правки.

                                          Это уже ваши фантазии.


                                          написать shapes[0].x.writeln; не получается

                                          https://run.dlang.io/is/asBYYV


                                          вызвать методы тоже не выйдет.

                                          А давайте вы поверите мне на слово, что всё это тоже не реализуемо?


                                          Объект это набор методов, которые чем-то манипулируют. Поля для объекта не обязательны.

                                          Главное, что клиентскому коду не надо знать что там внутри объекта.


                                          1. poxvuibr
                                            08.08.2019 21:03

                                            Ужас-то какой… [что смысл полиморфизма в том, чтобы можно было добавлять реализации не внося такие правки] Это уже ваши фантазии

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


                                            А давайте вы поверите мне на слово, что всё это тоже реализуемо?

                                            Охотно верю, возможности языка впечатляют.


                                            Главное, что клиентскому коду не надо знать что там внутри объекта.

                                            То есть объединение методов и данных в одном объекте это не самое важное? А важно, чтобы клиент не знал как реализованы методы, да? Кстати, такие методы, реализация которых выбирается в рантайме, как раз называюся полиморфными )))


                                        1. playermet
                                          08.08.2019 17:59

                                          Тогда можно пример создания массива полиморфных объектов, которые наследуются от абстрактного класса Shape
                                          Если нужно именно это, а не массив из вообще любых типов, то это типовая задача в плюсах.

                                          Пример кода на скорую руку. И ничего не нужно добавлять для новых потомков.

                                          A variant, который выше предлагалось использовать, предназначен для других случаев — когда нужно хранить несовместимые между собой типы. Например, shape, string и double.


                                          1. vintage
                                            08.08.2019 20:06

                                            Мы обсуждали вопрос хранения объектов единым массивом, а не массива ссылок на объекты, находящиеся где попало.


                                            1. poxvuibr
                                              08.08.2019 21:06

                                              Кстати, вопрос, один элемент массива будет требовать памяти столько, сколько занимает наибольший тип? Или они каким-то чудом ещё и пакуются?


                                              1. playermet
                                                08.08.2019 21:51

                                                один элемент массива будет требовать памяти столько, сколько занимает наибольший тип?
                                                В моем примере на плюсах будет занимать столько, сколько по факту занимают объекты, плюс оверхед на указатели.

                                                В примерах с variant выше само собой будет занимать как наибольший тип, плюс оверхед на идентификатор типа.


                                            1. playermet
                                              08.08.2019 21:49

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

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


                      1. 0xd34df00d
                        07.08.2019 06:14

                        Через Variant или как он там в C++ называется.

                        Через variant не получится, это ж не тот полиморфизм (и вообще не полиморфизм, а ADT на костылях).


                        1. vintage
                          07.08.2019 16:36

                          Один и тот же код работает работает с разными типами. Это полиморфизм по определению.


                          1. 0xd34df00d
                            07.08.2019 16:55

                            Во-первых, не один и тот же. Чаще всего при обработке variant'а у вас по отдельной ветке на каждый, собственно, вариант (а если и нет, то это параметрический полиморфизм).


                            Во-вторых, если у меня есть код на хаскеле типа


                            data Shape = Circle | Rectangle | Triangle
                            
                            describe :: Shape -> String
                            describe Circle = "it's so round!"
                            describe Rectangle = "yay rectangle"
                            describe Triangle = "yay triangle"

                            то это что, полиморфизм, по-вашему?


                            1. vintage
                              07.08.2019 17:07

                              Чаще всего при обработке variant'а у вас по отдельной ветке на каждый

                              Есть полиморфный код, которому не важно содержимое. Есть мономорфный, который пишется разный для разных вариантов.


                              У вас больше одного, у меня другого — бывает. Но даже если полиморфного кода у вас мало, он от этого не перестаёт быть полиморфным.


                              это параметрический полиморфизм

                              А параметрический полиморфизм — это внезапно уже не полиморфизм? Или как это работает?


                              у меня есть код на хаскеле

                              А я не понимаю хаскель.


                              1. 0xd34df00d
                                07.08.2019 17:09

                                А параметрический полиморфизм — это внезапно уже не полиморфизм? Или как это работает?

                                Контекст дискуссии намекал (по крайней мере, мне), что речь о сабтайпинг-полиморфизме.


                                1. vintage
                                  07.08.2019 17:31

                                  Речь шла о создании массива, который может содержать разные типы данных.


                            1. Druu
                              08.08.2019 04:40

                              Во-вторых, если у меня есть код на хаскеле типа

                              В хаскеле же объединения размеченные, по-этому варианты типами не являются. Вы же не можете создать значение типа Circle или ф-ю, которая принимает только Circle.


                              1. 0xd34df00d
                                08.08.2019 04:47

                                Это вопрос писанины на клавиатуре:


                                data Shape = Circle CircleData | Rectangle RectData | Triangle TriData

                                для соответствующих типов CircleData, RectData, TriData. Хранит ли массив [Shape] разные типы данных?


                                1. Druu
                                  08.08.2019 07:24

                                  Это вопрос писанины на клавиатуре:

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


                                  Хранит ли массив [Shape] разные типы данных?

                                  Конечно же, нет. Он хранил бы разные типы данных, если бы в массиве были значения типов CircleData/ReactData/TriData. Но там вместо них значения типа Shape.
                                  Если вы напишите ф-ю, которая возвращает некоторый CircleData, то вы результат этой ф-и в массив ваш положить не сможете. Вам надо будет этот CircleData дата скормить конструктору, который вернет Shape.


                              1. mayorovp
                                08.08.2019 08:51

                                Но на том же С++ std::variant точно так же является типом-суммой, а не типом-объединением.


              1. mayorovp
                06.08.2019 19:43

                А вас не смущает, что та же статья в Википедии приводит примеры из Си?


                1. vintage
                  06.08.2019 22:36

                  Какая статья?


                  1. mayorovp
                    06.08.2019 22:52

                    https://en.wikipedia.org/wiki/AoS_and_SoA


                    А вы о какой подумали?


                    1. vintage
                      06.08.2019 23:38

                      О той, которая на том самом сайте, которая идёт после какой-то.


                      Собственно мы о разных проблемах говорили.


      1. Yuuri
        05.08.2019 15:46
        +1

        Но вот для складывания матриц в GPU ФП скорее всего не подходит.

        Кстати, внезапно подходит, см. фреймворк accelerate.


      1. sshikov
        05.08.2019 19:55

        Вообще-то Дейкстра изначально такого про исключения не говорил. И все-таки, они не эквивалентны goto, что бы там ни говорили.


        1. PsyHaSTe
          05.08.2019 20:32

          Есть неплохая статья на тему, почему эксепшны не очень хороши.


          1. sshikov
            05.08.2019 20:54

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


            1. PsyHaSTe
              05.08.2019 21:38

              Безусловно.


      1. vintage
        06.08.2019 18:24

        Либо это dispodsble, либо это RAII — все одно. Вот только ФП не создает иллюзии, что ты работаешь безопастно.

        Почему абстракции, позволяющие безопасно работать с ресурсами, вы называете иллюзией?


        Исключения — это костыль. В отличие от монады. И это математически доказал Дейкстра в своих основах структурного программирования

        Можно ссылку на это математическое доказательство?


        Модификация объекта — это головная боль с того момента, как программе появляется многопоточность.

        Нет там никакой головной боли при использовании правильных абстракций.


        Это называется декларативное программирование — вершина грамотной архитектуры и композиции программных систем.

        ФП не является декларативным по определению. В ФП описывается не результат, а функция генерации результата из входных параметров.


    1. nexmean
      05.08.2019 14:05
      -1

      Ложь в каждом из пунктов.


    1. develop7
      05.08.2019 14:27
      -1

      [х] дискуссия с переводом


    1. 0xd34df00d
      05.08.2019 15:06
      +2

      ФП работает с "чистыми" функциями. Но реальное приложение работает с файлами, БД, внешними сервисами, которые нарушают требования "чистоты", в итоге приходится придумывать костыли

      Нет, не с чистыми в смысле отсутствия эффектов. Чистота функций — это про то, что эффекты функции объявлены в её сигнатуре. IO — ни в коей мере не костыли.


      Если у вас есть массив из 100 пользователей, и вы должны одному обновить рейтинг, вы должны сделать полную копию массива. И так на каждое изменение. Это негативно влияет на производительность и потребление памяти.

      Если вам не нужна старая копия массива, то GC её тут же подберёт, и массив даже из nursing area (и, как следствие, из кеша процессора) выбраться не успеет.


      Ну и есть линейные типы, которые тут тоже помогают.


      Там, где вы в PHP пишете $user->updateKarma(), в ФП вы должны писать user2 = updateKarma(user), и не забыть заменить старые копии user на user2 во всех местах кода, во всех коллекциях и списках. Удачи!

      А это, кстати, интересный вопрос. Ну, просто почему-то так оказывается, так пишется ФП-код, что это не является проблемой.


      В нормальном программировании вы просто пишете действия подряд, если произойдет ошибка, выбросится исключение и оставшиеся не будут выполняться. В ФП вам приходится лепить костыли, делая типы вроде Maybe и делая "пропуск" функции, если ей передано Maybe с ошибкой внутри.

      В ФП с do-нотацией вы тоже пишете функции подряд, и оставшиеся не будут выполняться. Пропуск выполняется за вас.


      А ФП вроде Хаскелл позволяет создать 10 вариантов функции с одинаковым именем, но разным типом аргументов.

      Правда? Можно пример?


      Удачи вам найти при рефакторинге, какая из функций (раскиданных по разным файлам) вызывается в том или ином случае.

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


      Но, конечно, в программах для вычисления чисел Фибоначчи ФП не знает себе равных.

      Или в компиляторах. Или в статических анализаторах. Или в скрейперах. Или в веб-серверах.


      1. vintage
        06.08.2019 18:13

        Чистота функций — это про то, что эффекты функции объявлены в её сигнатуре. IO — ни в коей мере не костыли.

        У вас какое-то очень своеобразное представление о чистоте.
        https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D1%82%D0%BE%D1%82%D0%B0_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8


        1. 0xd34df00d
          06.08.2019 18:21

          Ну так IO-функция каждый раз возвращает один и тот же экшн и является детерминированной. В чём расхождение?


          1. vintage
            06.08.2019 18:38

            В том, что давая неверное определение, вы вводите людей в заблуждение.


            1. 0xd34df00d
              06.08.2019 18:52

              Чем оно неверное-то? Чем функция, возвращающая описание действия, не чистая?


              1. vintage
                06.08.2019 19:11
                -3

                Два разных текста с двумя разными смыслами. Дальнейших разъяснений от меня можете не ждать.


      1. iboltaev
        07.08.2019 12:58

        Или в компиляторах. Или в статических анализаторах. Или в скрейперах. Или в веб-серверах.

        BigData еще.


    1. Yuuri
      05.08.2019 15:41

      Вот это уже гораздо тоньше, чуть сам не кинулся объяснять, где вы неправы :)


      1. PsyHaSTe
        05.08.2019 15:45

        Судя по карме комментатора это его искренние размышления, а не троллинг, как хотелось бы думать.


    1. iboltaev
      05.08.2019 17:18

      Толсто.

      ФП работает с «чистыми» функциями. Но реальное приложение работает с файлами, БД, внешними сервисами, которые нарушают требования «чистоты», в итоге приходится придумывать костыли

      И как это мешает бизнес-логику реализовать на чистых функциях, а ввод-вывод оставить грязным функциям? IO-монада, там, вот это все.
      в ФП переменные иммутабельны. Если у вас есть массив из 100 пользователей, и вы должны одному обновить рейтинг, вы должны сделать полную копию массива. И так на каждое изменение. Это негативно влияет на производительность и потребление памяти.(пример: реакт, где на любой чих пересоздается стейт заново и хорошо, если он у вас маленький).

      Лишний повод почитать структуры данных. Immutable hash trie (через который в ФП реализуются immutable массивы) решает проблему.
      в ФП нет исключений. В нормальном программировании вы просто пишете действия подряд, если произойдет ошибка, выбросится исключение и оставшиеся не будут выполняться. В ФП вам приходится лепить костыли, делая типы вроде Maybe и делая «пропуск» функции, если ей передано Maybe с ошибкой внутри.

      Зато есть Either (это в scala), Option и до черта всякого остального. Пропуск функции делать не нужно, монада сделает это за вас.
      в ФП нет ООП, которым удобно представлять объекты реального мира. Вместо этого там приходится делать разрозненные структуры и функции для работы с ними — то, для замены чего и придуман ООП. А ведь в ФП вы еще не можете модифицировать объект, с которым работаете.

      ФП плох потому, что в нем нет ООП)
      Вообще есть до черта всего. Алгебраические типы данных. Coproduct-ы. Функторы. Аппликативные Фнукторы. Монады, в конце-то концов. Полугруппы, моноиды, группы. Этим всем вполне можно представлять объекты реального мира.
      в ООП функуция это просто последовательность шагов: 1) проверь, что такого емайла нет 2) добавь запись в БД 3) отправь письмо для подтверждения почты. В ФП же так не принято, а принято комбинировать функции в составные функции, из-за чего разбор кода превращается в кошмар (registrator = (formData) => combine(formValiadtor(rules), fieldExtractor('email'), emailNotInDbChecker(db), emailToDbAdder(db), emailSender(db, sendService)).

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


      1. 0xd34df00d
        05.08.2019 17:21
        +1

        Immutable hash trie (через который в ФП реализуются immutable массивы) решает проблему.

        Ну вообще надо быть честным, hashtables иногда даёт такую производительность, которая на чистых (unordered-)containers недостижима.


      1. vintage
        06.08.2019 18:10
        -1

        а ввод-вывод оставить грязным функциям? IO-монада, там, вот это все.

        Наличие стандартной затычки красноречиво свидетельствует о протечке абстракции.


        Лишний повод почитать структуры данных. Immutable hash trie (через который в ФП реализуются immutable массивы) решает проблему.

        Вот и почитайте сколько стоит работа с этими структурами по сравнению с обычным массивом. Как по памяти, так и про процессору.


        Зато есть Either (это в scala), Option и до черта всякого остального. Пропуск функции делать не нужно, монада сделает это за вас.

        А в языках с поддержкой исключений — компилятор делает эту монаду и Either за вас. Причём в нормальных реализациях ещё и cost-free, без 100500 условных переходов, которые так любит конвейер процессора.


        до черта всего. Алгебраические типы данных. Coproduct-ы. Функторы. Аппликативные Фнукторы. Монады, в конце-то концов. Полугруппы, моноиды, группы. Этим всем вполне можно представлять объекты реального мира.

        Можно и из буханки хлеба сделать троллейбус. Только зачем?


        1. 0xd34df00d
          06.08.2019 18:49
          -1

          А в языках с поддержкой исключений — компилятор делает эту монаду и Either за вас.

          И за меня выражение вроде «попытайся выполнить вот этот список действий и верни первое успешное» нагенерит для каждого конкретного типа ошибки? Или надо будет самому обвязки писать?


          1. vintage
            06.08.2019 19:13
            +1

            Не очень понял о чём вы.


        1. iboltaev
          07.08.2019 13:10

          «я не пробовал, но осуждаю»?


          1. vintage
            07.08.2019 16:57

            Пробовал.


    1. sshikov
      05.08.2019 19:50

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

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

      Ну да, иммутабельность чего-то стоит, несомненно. Но в тоже время она почти даром дает нам например такие вещи, как возможность версионирования значения, и соответственно, undo.

      И так почти по всем пунктам.

      И что еще характерно, почти все пункты уже аргументированно разобрали, а комментарий все же имеет +24. Это я не к тому, что он плохой — по большей части тут рассмотрены вполне осмысленные вопросы, которые стоит себе задавать. Но он односторонний — а значит в конечном счете будет как-то способствовать распространению все тех же искаженных представлений об ФП.


      1. iboltaev
        07.08.2019 13:15

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

        Да вполне терпимо стоит. По асимптотике функциональные структуры данных такие же, как и мутабельные. Зато параллелизм из коробки.


        1. sshikov
          07.08.2019 21:08

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


    1. chirkin
      05.08.2019 19:57

      Что-то это уже для меня слишком тонко. Где тут шутка, а где реальное мнение? :)


    1. iago
      06.08.2019 11:56

      А кто просит писать на чисто функциональном языке? Я например пишу на swift, кое-где применяю паттерны из ФП (без фанатизма, где реально удобно), но это не отменяет ООП-шного стиля там, где он работает лучше. Мне кажется, дискуссия на тему «ФП полностью убьет ООП» является специальной олимпиадой, так же как «коробка передач убьет двигатели, они больше не нужны»


  1. sbnur
    05.08.2019 13:05

    Что было раньше: декларация или операция?


    1. Yuuri
      05.08.2019 15:54

      Декларация. «И сказал Бог: да будет свет. И стал свет».


      1. Nimtar
        06.08.2019 00:20
        +1

        А один евангелист рекурсии дополняет: "В начале было Слово, И Слово было у Бога, И Слово было Бог"


        1. leon_nikitin
          06.08.2019 06:21
          -2

          Ни к чему такое писать. Разве без этого никак?


  1. papasha_mueller
    05.08.2019 13:45

    Кстати говоря, если измерять «производительность труда» в LoC за единицу времени — то заголовок вполне себе верный.


  1. netricks
    05.08.2019 13:51
    +2

    В этой статье присутствует ложная дихотомия функционального программирования и ООП. Эти подходы совершенно не исключают друг-друга.

    Совсем непонятно про наследование. В каком месте функциональное программирование отменяет концепцию наследования???

    Функциональное программирование позволяет получить строгий вывод в терминах объектов. Функциональная парадигма в части своей основной идеи интуитивна ничуть не менее кошечек и собачек.


    1. PsyHaSTe
      05.08.2019 14:00

      В этой статье присутствует ложная дихотомия функционального программирования и ООП. Эти подходы совершенно не исключают друг-друга.

      Если вы уберете наследование из ООП вы потеряете всё ООП.
      Если вы уберете требования чистоты из ФП вы потеряете всё ФП.


      В этом и суть, я не вижу возможностей для "промежуточного" подхода, который бы работал. Заигрывания в паттерн матчинг или там функции высшего порядка это мишура, которая используется в ФП, но никак его не определяет.


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


      В итоге, что остается от ООП? Какая концепция в нем есть, которая дает какие-то ощутимые преимущества?


      1. poxvuibr
        05.08.2019 14:16
        -1

        Если вы уберете наследование из ООП вы потеряете всё ООП.

        Нет, это не так, ООП потеряется только если убрать полиморфизм. Наследование там не главное.


        1. PsyHaSTe
          05.08.2019 14:52

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


          1. poxvuibr
            05.08.2019 16:41

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


            В ООП центровым является понятие объекта, который есть совокупность методов, предназначенных для манипуляции состоянием и получения информации об объекте. Если методы не полиморфные, это уже не ООП, а элементы ООП.


            В ФП центровым понятием является функция, функция должна быть чистой. Если функции не чистые, то уже не ФП, а элементы ФП


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


            1. 0xd34df00d
              05.08.2019 17:05

              В ФП центровым понятием является функция, функция должна быть чистой. Если функции не чистые, то уже не ФП, а элементы ФП

              Это ИМХО бессмысленная интерпретация, если относиться к ней формально. Даже в С все функции чистые, просто все функции живут в IO.


              1. leon_nikitin
                05.08.2019 17:07

                как это все чистые? Чистота определяется тем, что на одни и те же параметры функция выдаст один и тот же результат. Что такое параметры в С, думаю, не надо здесь объяснять. Так же, думаю не надо рассказывать здесь про существование статических переменных внутри функции, а, значит, что IO тут не совсем не существенно.


                1. 0xd34df00d
                  05.08.2019 17:15

                  Давайте немножко издалека начнём.


                  Чистая ли функция putStrLn :: String -> IO (), просто выводящая строку на экран?
                  Чистая ли функция


                  greet :: IO ()
                  greet = do
                    str <- getLine
                    putStrLn $ "Hello, " <> str

                  ?


                  Это ведь хаскель, а в хаскеле все функции чистые, не так ли?


                  Чистая ли функция


                  stateful :: MonadState MyState m => m Int
                  stateful = do
                    myState <- get
                    pure $ if something myState then 1 else 2

                  ?


                  1. PsyHaSTe
                    05.08.2019 17:34
                    +3

                    Смысл в том, что функция putStrLn не выводит на экран ничего, а создает соответствующий IO. А println именно что выводит, она не создает никакую структуру, которую можно потом проинтерпретировать.


                    Натянуть на глобус си и считать его чистым при большом желании можно, но зачем? Все понимают, о чем идет речь.


                    1. 0xd34df00d
                      05.08.2019 17:44

                      А println именно что выводит, она не создает никакую структуру, которую можно потом проинтерпретировать.

                      Это неважно, есть очевидный изоморфизм между живущими в IO программами на хаскеле и нечистыми программами на какой-нибудь джаве (может, даже на С есть, но мне лень анализировать низкоуровневые свойства С).


                      Натянуть на глобус си и считать его чистым при большом желании можно, но зачем?

                      Чтобы понять, что контроль над эффектами важнее, чем какая-то там не очень понятная чистота, которую разные люди понимают по-разному (и не всегда приходят при этом к логическим противоречиям).


                      1. mayorovp
                        05.08.2019 17:52

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


                      1. PsyHaSTe
                        05.08.2019 18:05
                        +1

                        Это неважно, есть очевидный изоморфизм между живущими в IO программами на хаскеле и нечистыми программами на какой-нибудь джаве (может, даже на С есть, но мне лень анализировать низкоуровневые свойства С).

                        Изоморфизм есть только в слчае лени, а её нет.


                        Если вы сделаете let _ = putStrLn то ничего не произойдет. Если вы сделаете () _ = println(...) то эффект будет.


                        Точно так же вы можете дропнуть весь IO при желании, и его эффекты не сработают.


                        Отправиться в прошлое в си и отменить печать в консоль вы не сможете.


                        1. 0xd34df00d
                          05.08.2019 18:11

                          Если вы сделаете let _ = putStrLn то ничего не произойдет. Если вы сделаете () _ = println(...) то эффект будет.

                          Потому что это разные вещи: оттого, что я в С объявлю функцию void _() { printf(...); }, тоже ничего не произойдёт. И лень тут ни при чём, в очень «энергичном» идрисе первая строка тоже ничего не выведет.


                          Точно так же вы можете дропнуть весь IO при желании, и его эффекты не сработают.

                          Что эквивалентно невызову функции.


                          Ну и кстати, по-вашему, запросит ли следующая функция что-то с клавиатуры или нет?


                          stupid = do
                            str <- getLine
                            print "yay"


                          1. PsyHaSTe
                            05.08.2019 18:15

                            Но я в хаскеле не объявил функцию, а вызвал её.


                            Что эквивалентно невызову функции.

                            Вызов функции в хаскеле эквивалентен невызову в си? Окей, но мне сложно назвать это "похожими вещами", не говоря про "Одно и то же".


                            1. 0xd34df00d
                              05.08.2019 18:19

                              Но я в хаскеле не объявил функцию, а вызвал её.

                              Нет, не вызвали. Вы объявили байндинг без имени (потому что _), который ссылается на функцию putStrLn. Чтобы вызвать, надо вот эту вот стрелочку налево писать, которая <-. Ну или что-то аналогичное, если без сахара do-нотации (но формализация этого в рамках данной дискуссии несущественна).


                              Вызов функции в хаскеле эквивалентен невызову в си?

                              Создание байндинга, который в итоге нигде не участвует в цепочке IO.


                      1. Yuuri
                        05.08.2019 18:28

                        Как провести изоморфизм, если хаскель разделяет a и IO a, а си – нет?


                        1. 0xd34df00d
                          05.08.2019 18:33
                          -1

                          Да, правильнее говорить о биекции, тем более, что какой-то структуры на множестве программ мы не определили.


                          Достаточно того, что для каждой программы на хаскеле найдётся эквивалентная программа на С (это вообще очевидно, учитывая, что ghc умеет компилировать в С), и для каждой программы на С найдётся эквивалентная программа на хаскеле (с точностью до не очень релевантных операций с байтиками, на случай апелляции к которым я заготовил подмену сишечки сейф-подмножеством джавы или сишарпа, например).


                  1. leon_nikitin
                    05.08.2019 17:53

                    Если строго говорить, то чистая. И возвращает она последовательность действий «1) прочитать строку; 2) вывести строку, используя результат первого действия»

                    Собственно main :: IO () в haskell — это просто выражение, которое описывает определенные действия из N возможных. Описание это формируется при помощи композиции (монадической композиции, скажем так), ряда функций вида a -> IO b.

                    Вот и все. Далее, на основании этого описания компилятор, собственно, генерирует машинный код (или код ассемблера, или код на промежуточном языке, таким, как C).


                    1. 0xd34df00d
                      05.08.2019 18:00

                      Ну так и код на С просто описывает определённые действия из N возможных. И при одном и том же исходном состоянии машины он тоже совершит одни и те же действия.


                      Любую функцию на С вида T foo(Arg1, ..., ArgN) вы можете представить себе как foo :: Arg1 -> ... -> ArgN -> IO T и не потерять вообще ничего.


                      1. leon_nikitin
                        05.08.2019 18:13

                        Речь ведь не про состояние машины, а про вызове функций с одними и теми же параметрами.

                        В C можно написать функцию

                        T foo (Arg1, ..., Arg N),
                        которую, если вызывать с одними и теми же значениями Arg1 и ArgN, то она будет всегда возвращать разные значения. Это и есть нарушение чистоты функции. Про «состояние машины» речь не идет совсем.


                        1. 0xd34df00d
                          05.08.2019 18:16

                          На хаскеле тоже можно написать функцию foo :: Arg1 -> ... -> ArgN -> IO T, которая при вызове с одними и теми же параметрами будет возвращать разный результат.


                          Тут, конечно, дъявол кроется в деталях и, как обычно, в определениях: что именно значит «возвращать результат»?


                          Когда пытаются примирить чистоту хаскеля с наличием IO, обычно говорят, что функция возвращает не T, а IO-экшн, вычисление которого в рантайме даст значение типа T (ну или экзепшн). Но кто вам мешает совершенно аналогично интерпретировать функции на С? Когда вы пишете ту сишную функцию, вы точно так же описываете действия, которые в рантайме дадут вам значение типа T.


              1. poxvuibr
                05.08.2019 17:13

                Дело даже не в IO, а в том, что в Си можно вызвать функцию, передав туда указатель на структуру, потом изменить поле структуры, переданной ранее в функцию, потом вызвать ту же функцию, передав туда тот же указатель и получить другой результат.


                1. 0xd34df00d
                  05.08.2019 17:17

                  Ну, собственно, аналогичный вопрос. Как насчёт хаскель-функции, живущей в MonadState, чистая ли она?


            1. PsyHaSTe
              05.08.2019 17:08

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

              Можно, но ФП парадигма не в том, чтобы можно было писать чистые функции, а в том, что она требует писать только чистые функции. А что до возможности их писать в других парадигмах… Ложка дегтя портит всю бочку мёда. Чтобы был мед, дёгтя не должно быть вообще.


              В ФП центровым понятием является функция, функция должна быть чистой. Если функции не чистые, то уже не ФП, а элементы ФП

              Объекты это частные случаи функций, и наоборот. Объект А всегда можно представить как функцию () -> A.


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

              foo<T> — это полиморфизм без использования парадигмы ООП.


              1. 0xd34df00d
                05.08.2019 17:19
                +1

                Объект А всегда можно представить как функцию () -> A.

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


                1. PsyHaSTe
                  05.08.2019 17:35

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


                  1. 0xd34df00d
                    05.08.2019 17:45

                    Широко известный в узких кругах Types and Programming Languages. Вроде даже pdf'ка свободно циркулирующая была (но если не найдёте — стукнитесь в личку).


                    Особо много матана там нет, самый хардкор там, ИМХО, в главе про метатеорию рекурсивных типов, но она для понимания ООП не обязательна.


              1. poxvuibr
                05.08.2019 18:07

                foo — это полиморфизм без использования парадигмы ООП.

                Если вы имеете в виду дженерики или шаблоны, то это не полиморфизм, потому что там нет вызовов полиморфных методов.


                1. PsyHaSTe
                  05.08.2019 18:10

                  Посмотрите определение. Параметрический и подтиповой полиморфизм — и то и то виды полиморфизма, причем второй частный случай первого.


                  1. poxvuibr
                    05.08.2019 19:31

                    Я думал, что мы говорим про тот полиморфизм, который используется в ООП. То есть получается про подтиповой. Когда начинаете им пользвоаться — начинаете использовать ООП. Параметрический полиморфизм такой неотъемлемой частью ООП не является.


                    1. PsyHaSTe
                      05.08.2019 20:31

                      Полиморфизм в ООП подтиповой, можно реализовать через параметрический, в той же статье это написано.


                      То есть это просто более богатый языковой концепт, как паттерн матчинг против if. В итоге мы можем выразить всё то же самое используя его.


                      1. Druu
                        06.08.2019 05:43
                        +1

                        Полиморфизм в ООП подтиповой, можно реализовать через параметрический, в той же статье это написано.

                        В общем случае нельзя, для этого нужна ограниченная квантификация и экзистенциальные типы.


        1. VolCh
          06.08.2019 13:46
          +1

          Инкапсуляция (не в смысле сокрытия) данных и алгоритмов их обрабатывающих с привязкой их друг к другу by design. Как по мне, то именно это стало основным шагом от структур типа сишных с указателями/ссылками на функции к ООП. Не token = auth.login(auth, username, password), а token = auth.login( username, password).


          1. PsyHaSTe
            06.08.2019 14:30
            -2

            Ну возьмите Раст, ООП там нет, но там тоже будет auth.login(...), ФП никак этому не противоречит.


            1. mayorovp
              06.08.2019 14:37

              В Rust нет наследования (привычного), а не ООП.


              1. PsyHaSTe
                06.08.2019 15:07
                -1

                А в чем еще ООП состоит? Я ниже спросил, никто так и не ответил.


                В моем понимании единственным значимым отличие именно наследование и есть.


                А в расте ни привычного, ни непривычного наследования структур нет.


                1. mayorovp
                  06.08.2019 15:16
                  -1

                  Ответ вам дали в этой ветке 4 комментариями выше. Вы ходите кругами.


                  В моем понимании единственным значимым отличие именно наследование и есть.

                  А в моём — инкапсуляция и полиморфизм.


                  А в расте ни привычного, ни непривычного наследования структур нет.

                  Есть наследование интерфейсов-трейтов, и его более чем достаточно.


                  1. defuz
                    06.08.2019 16:26

                    В Haskell есть инкапсуляция и полиморфизм. Выходит, Haskell — это тоже ООП?


                    1. mayorovp
                      06.08.2019 16:39

                      В Haskell, насколько я знаю, инкапсуляция только уровня модулей. Напомню с чего началась ветка:


                      Инкапсуляция (не в смысле сокрытия) данных и алгоритмов их обрабатывающих с привязкой их друг к другу by design.

                      Если вам не нравится "инкапсуляция" — читайте это слово как "использование значения пользовательского типа как пространства имен для возможных операций, определенных над этим типом".


                      1. defuz
                        06.08.2019 17:04

                        Я поддерживаю ваше начинание дать определения buzzword и разобраться по сути, но давайте сделаем тоже самое и с полиморфизмом. Вы писали:

                        ООП потеряется только если убрать полиморфизм. Наследование там не главное
                        Есть два основных вида полиморфизма: подтиповый и параметрический. Haskell и Rust на полную катушку используют параметрический полиморфизм, при этом по крайней мере Haskell точно не является ООП-языком. Подтиповый полиморфизм невозможен без поддержки наследования на уровне языка. В Delphi/Object Pascal долгое время был только подтиповый полиморфизм, что врочем не мешало этим языкам считаться «ООП».

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


                        1. mayorovp
                          06.08.2019 17:24

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


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


                          1. defuz
                            06.08.2019 17:47
                            +1

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

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


                            1. mayorovp
                              06.08.2019 18:17

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

                              Ну да, так и есть. Это удобный механизм, а не необходимая часть ООП.


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

                              Так ведь в чистом ФП все эти "паттерны проектирования" оказываются слабо применимы, а те, что применимы — реализуются совсем другим образом.


                              Не вижу причин не называть их паттернами ООП.


                        1. poxvuibr
                          06.08.2019 18:56

                          Получается, что с точки зрения полиморфизма ООП от не-ООП отличает как раз присутствием подтипового полиморфизма

                          Получается да


                          который в свою очередь невозможен без наследования.

                          Да нет, наследование не нужно. Нужно позаботиться, чтобы объект реализовал интерфейс и всё.


                        1. vintage
                          06.08.2019 19:15

                          Ребята, вы забываете, что ООП — это Объектно Ориентированное Программирование. Ничто не мешает реализовать объекты в ФП. Так же как и чистые функции в ИП. Разница лишь в том, на какой тип использования язык ориентирован.


                      1. defuz
                        06.08.2019 17:18
                        +1

                        А на счет инкапсуляции как «использования значения как пространства имен» – это ведь не более чем синтаксический сахар над Foo::method(foo). То есть явно не дотягивает до ключевой особенности, определяющей парадигму.


                        1. mayorovp
                          06.08.2019 17:31
                          -1

                          Сахар сахаром, но это именно что ключевая особенность!


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


                          Если же синтаксический сахар не учитывать, то надо записывать Си без плюсов как поддерживающий ООП язык, ведь ядро Linux и оконная подсистема WINAPI написаны именно в парадигме ООП!


                          1. PsyHaSTe
                            06.08.2019 17:50

                            Ну да, по вашему определению получается Си это ООП язык, и вы правы, это не так, но не потому, что там нет сахара вызова методов через точку.


                          1. defuz
                            06.08.2019 17:52

                            Ну это скорее ваше собственное определение, чем общепринятое. Я его понимаю и принимаю. В таком смысле действительно Rust и Go – вполне себе ООП языки.

                            Тем не менее, такой подход вряд ли удовлетворит обывателей, которые жалуются что даже «ООП не смогли нормально завезти»: в первом нет наследования, а во втором – параметрического полиморфизма.

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


                            1. mayorovp
                              06.08.2019 18:27

                              Ну, я тоже считаю, что в Rust «ООП не смогли нормально завезти»: вот как наконец-то появится делегирование реализации, так это мнение и переменится.


                              Тем не менее, "ненормальное" ООП — это вовсе не отсутствие ООП.


                              1. defuz
                                06.08.2019 18:43

                                Можно пример, чем использование отдельных типажей или Deref/AsRef не угодили?


                                1. PsyHaSTe
                                  06.08.2019 19:26

                                  Эмуляция через них ООП это официальный антипаттерн.


                                  1. defuz
                                    06.08.2019 19:36

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


                                    1. mayorovp
                                      06.08.2019 19:52

                                      Ну вот есть типаж о 10 методах (конкретный кейс придумывать лень, но не вижу причин ему не существовать). Нужно сделать реализацию B, которая делает всё как реализация A — но 1 метод из 10 отличается.


                                      На том же C# я бы использовал либо наследование, либо композицию с наследованием — а в Rust я вынужден писать тривиальную реализацию для каждого из 9 методов.


                                      1. PsyHaSTe
                                        06.08.2019 21:31

                                        Обычно это заменяется выносом 9 общих методов в один тип, и потом использование его из А и В.


                                        1. mayorovp
                                          06.08.2019 22:19

                                          Обычно это заменяется выносом 9 общих методов в один тип, и потом использование его из А и В.

                                          Вот только для того, чтобы его можно было использовать именно что из A и B, а не раскрывать его существование всем потребителям этих классов — как раз и нужно наследование либо делегирование.


                                          Иначе получается, что у нас есть тип с 9 "общими" методами — и еще 18 написанных вручную методов-делегатов.


                                          1. PsyHaSTe
                                            06.08.2019 22:24

                                            Вот только для того, чтобы его можно было использовать именно что из A и B, а не раскрывать его существование всем потребителям этих классов — как раз и нужно наследование либо делегирование.

                                            Можно просто закрыть его от наследования, оставив публичным на использование. Да, в расте есть паттерн, который позволяет так делать. Правда, я не вижу проблем сделать сущность публичной.


                                            Если у вас 9 общих методов, значит надо выделять абстракцию. А то потом появляются треугольники, унаследованные от линий.


                                      1. defuz
                                        07.08.2019 00:35

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

                                        Во-первых, вызывает вопросы существование типажа с десятью не default методами. Я понимаю что это утрированный пример, но это плохой пример. Типаж – это неделимая единица абстракции, из которой нельзя выкинуть ни один метод, не нарушив контракт. Я сомневаюсь что у вас есть настолько сложная абстракция. Как правило типажи ограничиваются 1-4 методами. Так что задумайтесь как поделить типаж на 2-3 отдельных.

                                        Теперь по поводу вашего подхода. На каком основании вы приняли решение что B наследуется от A? Потому что A существовал в вашей кодой базе раньше чем B? Почему не наоборот? Если я преобразую экземпляр B в тип его предка A, то как должен выполняться тот самый десятый метод? Что если реализация десятого метода для A находится в другой библиотеке и он не объявлен виртуальным? Если мне понадобится еще один тип C, который переопределяет третий метод, а десятый реализует как B, я должен наследовать его от B? А если потом окажется что нужен еще и D, который реализует третий метод как С, но десятый как A, от кого будем его наследовать?

                                        Своими вопросами я хочу показать, что пока вы рассматриваете инструменты абстракции как споcоб писать поменьше кода, а не как способ реализовывать корректные контракты, вы будете получать дырявые абстракции. Они будут работать только на тех примерах, которые был у вас в голове когда вы их реализовывали.


                                        1. mayorovp
                                          07.08.2019 06:01

                                          Теперь по поводу вашего подхода. На каком основании вы приняли решение что B наследуется от A?

                                          На том основании, что B сложнее A и расширяет его функциональность.


                                          Если я преобразую экземпляр B в тип его предка A, то как должен выполняться тот самый десятый метод?

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


                                          Что если реализация десятого метода для A находится в другой библиотеке и он не объявлен виртуальным?

                                          А как давно на Rust появились виртуальные методы?


                                          Если мне понадобится еще один тип C, который переопределяет третий метод, а десятый реализует как B, я должен наследовать его от B?

                                          Такой вариант возможен.


                                          А если потом окажется что нужен еще и D, который реализует третий метод как С, но десятый как A, от кого будем его наследовать?

                                          Ну, это будет означать что пора программу рефакторить.


                                          пока вы рассматриваете инструменты абстракции как споcоб писать поменьше кода, а не как способ реализовывать корректные контракты, вы будете получать дырявые абстракции

                                          А вы рассматриваете контракты как что-то, зависящее от реализации, хотя должно быть наоборот.


                                          Если так получилось, что в типаже 10 методов — в языке должен быть способ нормально с таким типажом работать. А не разбивать его на 10 типажей по 1 методу просто из-за деталей реализации.


                                          1. PsyHaSTe
                                            07.08.2019 12:07

                                            А как давно на Rust появились виртуальные методы?

                                            Всегда были


                                            А вы рассматриваете контракты как что-то, зависящее от реализации, хотя должно быть наоборот.

                                            Да нет, это ваш случай. Вы смотрите на содержимое методов, и такой "ба, да тут можно отнаследовать всё это. Пофиг, что наследуем треугольник от линии, у них обоих есть метод Draw!".


                                            1. mayorovp
                                              07.08.2019 12:29

                                              Всегда были

                                              А мне почему-то казалось, что в Rust нет наследования, как следствие полиморфизма подтипов — а значит, и виртуальных методов.


                                              Если же вы намекаете на dyn Trait — то покажите мне в таком случае невиртуальный метод типажа, который ну никак нельзя переопределить. Мне кажется, это оксюморон.


                                              1. PsyHaSTe
                                                07.08.2019 12:36

                                                Так же, как и в ООП, делаете структуру Derived, определяете для неё трейт Foo, и засовываете в Vec<Box<dyn Foo>>


                                                1. mayorovp
                                                  07.08.2019 13:04

                                                  Это вы сейчас на какой вообще вопрос ответили?


                                                  На всякий случай процитирую ветку целиком:


                                                  Нужно сделать реализацию B, которая делает всё как реализация A — но 1 метод из 10 отличается.

                                                  Что если реализация десятого метода для A находится в другой библиотеке и он не объявлен виртуальным?

                                                  покажите мне в таком случае невиртуальный метод типажа, который ну никак нельзя переопределить

                                                  Так же, как и в ООП, делаете структуру Derived, определяете для неё трейт Foo, и засовываете в Vec<Box<dyn Foo>>

                                                  Извините, но я не вижу в этом диалоге вообще никакого смысла.


                                                  1. PsyHaSTe
                                                    07.08.2019 13:59

                                                    Я Ответил про то как работает вызов виртуальных методов в расте.


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


                                                    1. mayorovp
                                                      07.08.2019 14:13

                                                      А я десять раз уже сказал, что меня более чем устраивает композиция вместо наследования.


                                          1. defuz
                                            07.08.2019 12:07
                                            +1

                                            На том основании, что B сложнее A и расширяет его функциональность.
                                            Не расширяют. Они оба реализуют один и тот же типаж/интерфейс, и все. Их функциональность идентична. Как вы решили что именно B расширяет А, а не наоборот? Потому что в B больше строчек кода? Расширение функционала – это добавление нового метода или реализация нового интерфейса.
                                            Никак, потому что вы не сможете преобразовать экземпляр B в тип его предка A.
                                            Разве? dotnetfiddle.net/m9gFeB
                                            А как давно на Rust появились виртуальные методы?
                                            Не появились, чтобы таких проблем не возникало. Появилась специализация, но это про другое.
                                            Ну, это будет означать что пора программу рефакторить.
                                            Потому что она была построена на изначально неверных предпосылках. Если бы ваш трейт/интерфейс был разбит на отдельные независимые интерфейсы и вы использовали композицию, этой проблемы можно было бы избежать.

                                            А вы рассматриваете контракты как что-то, зависящее от реализации, хотя должно быть наоборот.
                                            Как раз наоборот. Я утверждаю что A и B – сущности одного порядка, и между ними не может быть отношения наследования. Это выплывает из контрактов, которые они выполняют, а не из реализации. Если реализация скрыта от пользователя она может быть любой – A является оберткой над B или B является оберткой над А, или они оба композируют себя третью базовую сущность. Интефейсы вашей программы от этого не изменятся. В Вашем случае в интерфейсы вашего кода протекает ненужная абстракция того что B наследуется от A. Когда вы реализуете два класа, и один из них является наследником другого, вы тем самым утверждаете, что у них общий тип – тип предка. И приведение экземпляров наследника к типу предка – это осмысленная операция. Что она будет означать в вашем случае? Вы не отвечаете на этот вопрос, потому что решение наследовать A от B вы приняли мотивируя это схожестью реализации. Расширение функционала – это не «добавить еще строчку логирования» в метод.


                                            1. mayorovp
                                              07.08.2019 12:46

                                              Ну вот возьмём пример, который за ночь придумался-вспомнился. Абстрактное хранилище некоторых конкретных структур. Это типаж, у которого есть методы load, save, add, delete, и ещё куча методов-запросов, которые не сводятся друг к другу (точнее, свести-то их можно, но только с неизбежной просадкой производительности — из-за чего лучше разрешить хранилищу их все переопределять). Таких методов в сложных случаях может хоть два десятка набраться.


                                              А дальше нужно сделать хранилище с локальным кешем, чтобы последовательные вызовы load с одним и тем же ключом не тратили лишнее время. Оно должно наследовать (или делегировать — это вообще не важно) поведение базовой реализации, но с дополнительной логикой в этих самых load, save, add, delete. В запросах никакая новая логика не нужна.


                                              Если разделять каждый такой типаж на два — это удвоит число зависимостей в вышележащем слое. А их и так много.


                                              Ситуации, в которой в очередной реализации мне придется сначала изменить один из запросов, но с обязательным кешированием — а потом изменить другой запрос, сохранив изменения в первом, но убрав кеширование, я придумать не могу.


                                              Разве? dotnetfiddle.net/m9gFeB

                                              Я вообще-то про Rust писал, а не про C#.


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

                                              Ну так если его нет, то откуда вообще ваш исходный вопрос про невиртуальный метод?


                                              Потому что она была построена на изначально неверных предпосылках. Если бы ваш трейт/интерфейс был разбит на отдельные независимые интерфейсы и вы использовали композицию, этой проблемы можно было бы избежать.

                                              Но если предположить, что она построена на изначально верных предпосылках — то и рефакторинг тоже не понадобится.


                                              Если реализация скрыта от пользователя она может быть любой – A является оберткой над B или B является оберткой над А, или они оба композируют себя третью базовую сущность.

                                              Совершенно верно. Более того, меня устроит любой из этих трёх вариантов! Но уж извините, выбирать между ними, я будут именно что с позиций "где меньше кода писать" — именно потому что абстракциям пофиг на эти детали реализации, пока абстракция не порушена — писать я могу как угодно.


                                              Вы не отвечаете на этот вопрос, потому что решение наследовать A от B вы приняли мотивируя это схожестью реализации.

                                              Нет, это вы приписали мне это утверждение. Изначально я утверждал, что одному классу нужна часть поведения другого. Будет ли для этого использоваться наследование, делегирование или вообще копи-паст — с точки зрения постановки задачи не важно, это делать реализации.


                                              1. defuz
                                                07.08.2019 13:26
                                                +1

                                                ещё куча методов-запросов, которые не сводятся друг к другу (точнее, свести-то их можно, но только с неизбежной просадкой производительности)
                                                Можно подробнее про просадку производительности, я не очень понимаю откуда ей взятся. Например в Iterator несколько десятков предопределенных методов, и все они сводятся к next() и size_hint(), которые как раз и должен определить реализатор итератора, и никаких просадок производительности нет.

                                                Ситуации, в которой в очередной реализации мне придется сначала изменить один из запросов, но с обязательным кешированием — а потом изменить другой запрос, сохранив изменения в первом, но убрав кеширование, я придумать не могу.
                                                Зато я могу придумать вам другой пример: вы решили добавить еще один слой кеширования (Redis). Или решили добавить профилирование выполнения запросов. А потом решили выполнить профилирование без кеширования. Или реализовать еще одно хранилище, сохранив логику кеширования и профилирования. Вот тут вы отгребете за наследование и пойдете рефакторить. Пример который вы описали – это прямо типичный случай, когда нужно использовать композицию. Ваша кеширующия/профилирующия логика – это обертка над базовой реализацией (которая должна быть абстрактна в том смысле, что мы знаем лишь ее интерфейс). Наследование не позволяет вам это сделать.

                                                Более того, меня устроит любой из этих трёх вариантов! Но уж извините, выбирать между ними, я будут именно что с позиций «где меньше кода писать» — именно потому что абстракциям пофиг на эти детали реализации, пока абстракция не порушена — писать я могу как угодно.
                                                Так выбирайте, все 3 подхода доступны вам в Rust. Обертка != наследование.

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


                                                1. mayorovp
                                                  07.08.2019 13:43

                                                  Можно подробнее про просадку производительности, я не очень понимаю откуда ей взятся. Например в Iterator несколько десятков предопределенных методов, и все они сводятся к next() и size_hint(), которые как раз и должен определить реализатор итератора, и никаких просадок производительности нет.

                                                  Что быстрее: SELECT с условием WHERE на стороне СУБД — или SELECT без такого условия, с последующей фильтрацией, сводящейся к вызовам next?


                                                  1. PsyHaSTe
                                                    07.08.2019 14:01

                                                    Для такой штуки есть паттерн QueryBuilder, который используется например в том же дотнете с IQueryable. Никакой просадки у вас не будет в итоге.


                                                    1. mayorovp
                                                      07.08.2019 14:09

                                                      Увы, у него свои недостатки: формирование запросов к БД размазывается по всему коду. Это потом аукается на этапе оптимизации БД или, не дай бог, замены хранилища.


                                                      1. PsyHaSTe
                                                        07.08.2019 14:21

                                                        Это не "размазывание формирования запроса", а разделение ответственности. Кто-то определяет что достаем, какие-нибудь стратегии определяют фильтрацию, какой-нибудь валидатор фильтрует по правам юзера, и так далее. Это SRP называется, а не размазыванием.


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


                                                        А вот как раз-таки при использовании вендор-специфичного чего-нибудь вместо стандартизованного интерфейса можно словить сюрпризов.


                                                        1. mayorovp
                                                          07.08.2019 14:29

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

                                                          И всё это прекрасно живёт за фасадом репозитория.


                                                          А вот как раз-таки при использовании вендор-специфичного чего-нибудь вместо стандартизованного интерфейса можно словить сюрпризов.

                                                          Вот потому это самое вендор-специфичное и нужно собирать в одном месте, чтобы было на виду.


                                                        1. Druu
                                                          07.08.2019 16:24

                                                          Это не "размазывание формирования запроса", а разделение ответственности. Кто-то определяет что достаем, какие-нибудь стратегии определяют фильтрацию, какой-нибудь валидатор фильтрует по правам юзера, и так далее. Это SRP называется, а не размазыванием.

                                                          Штука в том, что иногда такой запрос будет тормозной. В EF, например, до сих пор кривейший провайдер, который часто генерит полную дичь. Так что приходится писать запрос руками, добиться вменяемого запроса нативными средствами ОРМ просто невозможно.


                                                  1. defuz
                                                    07.08.2019 14:15

                                                    Если честно, не понимаю к чему это. У вас есть наиболее общий метод select с WHERE, который нужно реализовать. И есть частные методы: без условия, с выбором одного элемента по primary key и так далее. Все они вызывают базовый метод.


                                                    1. mayorovp
                                                      07.08.2019 14:21

                                                      Это помешает заниматься оптимизацией отдельных запросов.


                                                    1. PsyHaSTe
                                                      07.08.2019 14:22

                                                      Он про то, что если у вас есть репозиторий FilterByDate() и FilterByName() вы не можете при реализации FilterByDateAndName() их использовать, придется писать отдельный метод.


                                                      Впрочем, как я выше написал, весь смысл в самом антипаттерне репозитория, без него таких проблем нет.


                                                      1. mayorovp
                                                        07.08.2019 14:37

                                                        Если бы там только FilterByDate и FilterByName — я бы согласился.


                                                        Но ведь я совсем недавно (4 месяца назад) обнаружил ошибку, что запросы всех видимых отчётов, всех "своих" отчётов, и всех видимых, но чужих отчётов не были согласованы друг с другом.


                                                        И общих спецификаций использовать там не получается — при попытках поиграться в комбинирование стратегий как минимум один из трёх запросов оказывается неоптимальным.


                                                        Вот были бы они все в одном месте — ошибка была бы заметна сразу же, а не на проде...


                                                1. mayorovp
                                                  07.08.2019 14:07
                                                  +1

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

                                                  Вы принципиально не читаете того, что я вам пишу?


                                                  Так выбирайте, все 3 подхода доступны вам в Rust. Обертка != наследование.

                                                  Но все три требуют писать вручную 9 делегирующих методов.


                                                  Важно, до тех пор пока предок A у вас торчит наружу

                                                  А где я писал, что он торчит наружу?


                                                  1. defuz
                                                    07.08.2019 14:21

                                                    Если A вообще нигде планируется использовать, то задача не имеет смысла – просто измените поведение A и не вводите новых сущностей.

                                                    Но все три требуют писать вручную 9 делегирующих методов.
                                                    Именно. И я вам пытаюсь объяснить почему это скорее всего правильно. Лучше архитектура не там где меньше кода.


                                                    1. mayorovp
                                                      07.08.2019 14:48

                                                      Если A вообще нигде планируется использовать, то задача не имеет смысла – просто измените поведение A и не вводите новых сущностей.

                                                      Во-первых, а если все-таки планируется?


                                                      Во-вторых, а как же SRP?


                                                      Лучше архитектура не там где меньше кода.

                                                      А причём тут вообще архитектура, если я сравниваю два разных языка (Rust и гипотетических Rust с делегированием) при неизменной архитектуре?


                                                  1. PsyHaSTe
                                                    07.08.2019 14:24

                                                    Но все три требуют писать вручную 9 делегирующих методов.

                                                    Если вам лень писать 9 методов и поэтому вы ломаете архитектуру лучше повесить атрибут [DelegateCallsTo] чтобы он нагенерировал вам эти прокси.


                                                    Вопрос ведь в том, как сделать правильно и расширяемо, а не просто решить задачу наименьшим диффом в гите.


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


                                                    1. mayorovp
                                                      07.08.2019 14:49

                                                      вы ломаете архитектуру

                                                      В какой момент?


                                                      лучше повесить атрибут [DelegateCallsTo]

                                                      Не нашел такого аттрибута, ни в Rust, ни в C#


                                                      1. PsyHaSTe
                                                        07.08.2019 15:00
                                                        -1

                                                        В какой момент?

                                                        В момент когда вы наследуете классы на основании того, что код в теле методов похожий, а не потому, что они по логике отвечают отношению "является".


                                                        Не нашел такого аттрибута, ни в Rust, ни в C#

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


                                    1. PsyHaSTe
                                      06.08.2019 21:30
                                      +1

                                      Ну реалистичный пример — это GUI. Классический гуй где есть абстрактный класс контрола и вот это все отлично работает, а в расте как сделать нормально до сих пор не придумали.


                                      Других примеров, правда, придумать не получается.


                                      1. defuz
                                        07.08.2019 00:44
                                        -1

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

                                        Вообще GUI это монументальная задача с точки зрения архитектуры. Я реализовывал TUI фреймворк на Rust. В принципе тоже самое что GUI но попроще. Пока реализовывал выучил Rust. Паттерн Visitor который распространяет события и выполняет прорисовку спускаясь по иерархии виджетов вполне справляется.


                                        1. snamef
                                          07.08.2019 03:12

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


                                          1. defuz
                                            07.08.2019 04:37

                                            Я это и имел ввиду: рекурсивный вызов функции draw от предка до потомка по иерархии вложенности, если я правильно понял. Тоже самое и с распространением событий. Не тот визитор, который класс с кучей методов типа visit_foo.


                                        1. PsyHaSTe
                                          07.08.2019 12:10

                                          Ну расскажите о своем опыте, потому что то что я видел (gtk-rs/azul/yew...) пока только экспериментирую, и никаких устоявшихся паттерном пока не изобрели.


                                          С другой стороны иерархичный GUI всегда хорошо работал, возьмите хоть WPF, хоть Qt.


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


                                          Я просто привел пример задачи, на которую вам сами авторвы гуй фреймворков скажут "да, это проблема".


                                          1. defuz
                                            07.08.2019 14:24
                                            -1

                                            Я не эксперт по GUI чтобы рассказывать как правильно – наверняка найдутся ситуации которые я не учел и которые вызовут проблемы.

                                            Подозреваю что проблемы с проектами типа gtk-rs/Qt связаны в первую очередь с тем что они делались под С++, и в итоге при их использовании приходится писать С++ на Rust, что мало кому понравится.


                                            1. PsyHaSTe
                                              07.08.2019 15:02

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


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


                                        1. vintage
                                          07.08.2019 17:19

                                          Это тот классический GUI в котором у каждого контрола сотня полей, две трети из которых не имеют смысла для каждого отдельно взятого типа контрола?

                                          Вполне себе имеют и регулируют различные аспекты поведения контрола. А этих аспектов очень много. Большая часть поведения единообразная. Но у каждого контрола свои особенности в разных аспектах.


              1. snamef
                06.08.2019 15:16
                -1

                В Rust нет классического наследования, но есть возможность изобразить его с помощью типажей.

                В Rust есть полиморфизм подтипов и реализуется он через типажи и типажи-объекты.

                а кстати это серъёзный минус Раста. Что он не делает как все а как то через задницу


      1. nuclight
        05.08.2019 14:17
        +1

        Если вы уберете наследование из ООП вы потеряете всё ООП.
        [...]
        Инкапсуляция
        [...]
        Полиморфизм


        “I invented the term object oriented, and I can tell you that C++ wasn't what I had in mind.” —Alan Kay

        Все эти 3 распиаренных слова не являются ключевыми для ООП.


        1. poxvuibr
          05.08.2019 14:21
          -1

          Все эти 3 распиаренных слова не являются ключевыми для ООП.

          А что для ООП ключевое? Не того, которое Алан Кей придумал, а для того, которое в Go, Java, C++, Javascript, С# и так далее.


          1. playermet
            06.08.2019 17:48
            -1

            Полиморфизм. Не считая его, все ООП-принципы можно использовать в чистом C с минимальными усилиями. Об этом можно почитать у автора принципов SOLID, Роберта Мартина, в его книге Clean Architecture. Именно на полиморфизме строится профит от подавляющей части паттернов и техник ООП.


        1. PsyHaSTe
          05.08.2019 14:54

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


          1. AN3333
            05.08.2019 17:49

            Не согласен не nuclight, а автор ООП которого nuclight процитировал.

            Гипотезы здесь не уместны, поскольку есть первоисточники.


            1. poxvuibr
              05.08.2019 17:57

              В первоисточнике кибернетика — наука об управлении государством, но сегодня это определение никто не использует. Точно так же и ООП, которым мы пользуемся сегодня это не то, что имел в виду Алан Кей, поэтому ссылка на Алана Кея тут неуместна.


              1. Yuuri
                06.08.2019 08:14

                Раньше был в этом вопросе на стороне Кея, но после ваших доводов задумался.
                Может быть, чтобы не было путаницы и холиворов, стоит различить «объектное» (в стиле Кея) и «объектно-ориентированное» (в мейнстримном понимании) программирование? В конце концов, мы же не говорим «функционально-ориентированное» или «процедурно-ориентированное».


                1. akryukov
                  06.08.2019 08:26
                  +1

                  Программирование в стиле Алана Кея сейчас называется моделью акторов.


          1. Source
            05.08.2019 18:57

            Единственное, что отличает ООП от ФП, — это угол зрения:



            Можно применять ООП в функциональных языках и ФП в объектно-ориентированных, ничто этому не препятствует. Разница лишь в акцентах, а не в чём-то фундаментальном.


            1. PsyHaSTe
              05.08.2019 19:18
              -2

              Вы не можете применять ФП в языках, где функции не чистые. Сам рантайм ФП языков предполагает, что функции чистые, и когда у вас компилятор заинлайнит функцию log(x); return x, и в эластик перестанут сыпаться логи, вы не обрадуетесь.


              1. mayorovp
                05.08.2019 19:54
                +1

                В том же Хаскеле есть seq для тех случаев, когда монад почему-то не хватает.


              1. 0xd34df00d
                05.08.2019 19:59

                Какой тип у log? Для всех разумных типов ничего никто никуда не заинлайнит, на каждое вычисление по-прежнему будет по вызову. Даже если у вас будет код типа


                getVal :: IO Ty
                getVal = do
                  log "foo"
                  pure val
                
                useVal :: IO ()
                useVal = do
                  v1 <- getVal
                  v2 <- getVal
                  ...

                Семантику языка всё-таки не изверги придумывали.


                1. PsyHaSTe
                  05.08.2019 20:40

                  log :: Int = \x =>
                    unsafePerformIo myLog
                    x


                  1. 0xd34df00d
                    05.08.2019 20:42

                    А, ну да. Не надо делать unsafe для такого.


                    1. PsyHaSTe
                      05.08.2019 21:33

                      Не надо, но бывает. Для примера сойдет.


                      Предлагаю закончить на этом.


              1. Source
                05.08.2019 23:49

                Функциональное проектирование вы можете применять вообще где угодно, даже далеко за пределами программирования.


          1. inew-iborn
            06.08.2019 22:29

            Я не силен во всех этих теориях, по этому также можно сказать задам вопрос, если что меня поправьте…

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

            Я не сильно силен в ФП, от сюда и вопрос, а разве нет подобия наследования в ФП, только оно вполне возможно выглядит иначе?


            1. Source
              08.08.2019 00:30

              Наследования (реализации) как такового в ФП нет, но те задачи, которые решаются
              наследованием можно решить другими путями.


              важна инкапсуляция, тоесть мы забываем про иммутабельность

              Какая связь то? Посмотрите модель акторов (на примере Erlang/Elixir)… Инкапсуляция такая, что мейнстрим языкам даже в самых влажных мечтах не снилось, но при этом абсолютно все данные иммутабельны.


            1. VolCh
              08.08.2019 13:16

              Инкапсуляция к мутабельности отношения не имеет вроде как. У нас может быть иммутабельный объект с сильной инкапсуляцией и защитой.


      1. netricks
        05.08.2019 15:49

        Не будем убирать ничего. Наследование никак не нарушает чистоты функций и наоборот.


        1. PsyHaSTe
          05.08.2019 16:04

          Он просто усложняет вывод типов, без чего в ФП жить становится немного неуютно. Пример Scala в этом плане показателен. Качество IDE и подсказок компилятора, что ни говори, важно.


        1. Yuuri
          06.08.2019 08:17
          +1

          А ещё оно, в случае мутабельности, ломает вариантность и принцип Лисков.


  1. CheY
    05.08.2019 13:55

    Чудесный вброс. Я почти поверил, пока не прочитал внимательнее :)


  1. apxi
    05.08.2019 14:48

    Интересно, а может кто нибудь сделать пример расчета больничного листа со всеми нюансами, если так удобно на ФП писать бизнес логику.


    1. 0xd34df00d
      05.08.2019 15:17

      Если вы опишете алгоритм расчёта со всеми нюансами, то почему бы и нет.


      1. apxi
        06.08.2019 08:13

        Алгоритм расчета со всеми, ну или почти со всеми нюансами есть в 1С ЗУП.


        1. 0xd34df00d
          06.08.2019 15:05

          Но 1С ЗУП нет, например, у меня.


  1. Dron007
    05.08.2019 15:21
    +1

    Какой-то не очень удачный пример:

    const getFruitPrice = type => fruits =>
      fruits
      |> filterByType(type)
      |> first
      |> get('price');


    Для того, чтобы получить цену первого фрукта заданного типа мы отфильтровали весь массив. А если там миллиард элементов?


    1. PsyHaSTe
      05.08.2019 15:46

      Фильтрация ленивая ведь. Мы остановимся как только встретим первый подходящий фрукт


      https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8ac1a1c6de3f950f408b323492b5d063


      Ну или если лайфтаймы и линейные типы напрягают, на скале: https://scastie.scala-lang.org/w6JXitIoRimV6zyfTGRzzg


      Кстати, забавно отметить, что я вот изначально не подумал что функция возвращает Option, ведь в массиве такого фрукта может и не оказаться.


      1. Dron007
        05.08.2019 16:13

        Мы точно про JS говорим? Там никакой магии нет, как я понимаю. Пока filter() не пройдётся по всему массиву, следующие функции не начнут работать.


        1. PsyHaSTe
          05.08.2019 16:15
          +1

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


          В гугле первой ссылкой находится вот такая библиотека: https://fitzgen.github.io/wu.js/


          На первый взгляд выглядит достойно.


          1. Dron007
            05.08.2019 16:56
            +1

            Да, действительно работает. Не фильтрует лишнее. codepen.io/Dron007/pen/rXYPRm


  1. PavelBryz
    05.08.2019 15:23

    По стилистике напоминает Fizz Buzz Enterprise Edition


    1. rboots
      05.08.2019 16:44

      deleted


  1. Andrey_Epifantsev
    05.08.2019 16:30

    «Если ты такой умный, то почему такой бедный?»
    Если функциональное программирование гораздо лучше чем ООП, то почему он не используется более широко чем ООП?


    1. PsyHaSTe
      05.08.2019 17:14

      1. Потому что в универах обычно его не преподают (по инерции, тянущейся еще с семидесятых, паскалей и си)
      2. Потому что когда человека отучили от математического определения функции и заменили императивной "процедурой", обратный переход неприятен
      3. Потому что до недавних пор компиляторы ФП не всегда хорошо оптимизировали код, а приложениям нужна была производительность
      4. Потому что ООП в 70х тоже было распространено меньше процедурного стиля, это не значит, что ООП хуже него
      5. ...


      1. dasFlug
        05.08.2019 23:10
        +1

        Смотря в каких универах. Во многих чтонибудь Lisp-образное — первый язык программирования. А ООП и тем более C уже потом. Потому что, как вы правильно заметили, императивная парадигма требует меньше усилий для освоения и освоить декларативную потом тяжело, гораздо тяжелее чем в обратной последовательности.


        1. PsyHaSTe
          05.08.2019 23:36
          -1

          Нет, я этого не говорил. Наоборот, есть исследования западных универов, которые давали сначала ФП, после чего людям тяжело было понять императивное. Как я уже сказал, людям тяжело воспринимать ФП не потому, что оно сложнее понимается, а потому что у них первым обычно идет паскаль/си (особенно в снг). Если давать первым ФП, то людям будет выносить мозг запись i = i + 1. Я таких реально видел. Могли написать на комбинаторах полезную программку, а эту запись не понимали.


          1. snamef
            06.08.2019 00:23
            +1

            ты не подменяешь понятия ФП и декларативное, потому что распостранённое ФП вообще то императивное


          1. dasFlug
            06.08.2019 06:20
            +1

            Честно говоря мне трудно представить такое. Не понимали синтаксис или не понимали концепцию переменной (тоесть вобще не имели представления о том как физически устроен компьютер)? По моему второе достаточно просто для понимания.


            1. Yuuri
              06.08.2019 08:24
              +1

              А как устроен компьютер? Ну, вычисляет чего-то. Вводит данные, выводит данные.
              А если в итоге нужно объяснять, что такое память, байты, переменные, присваивание, то почему бы взамен не взять концепцию, где это можно не объяснять? ;)


          1. michael_vostrikov
            06.08.2019 10:00
            +2

            Если давать первым ФП, то людям будет выносить мозг запись i = i + 1

            Так это не от ФП зависит, а от математики. У нас в школе не было ФП, но эта запись для меня тоже поначалу выглядела непонятно. А все потому что математика не работает с состоянием, любое состояние раскладывается по оси времени на ряд независимых точек, существующих как бы одновременно. Самое близкое это границы суммы (?), но обычно не уточняется как i переходит из одного значения в другое.


            1. PsyHaSTe
              06.08.2019 12:11

              Ну я к этому и веду. ФП математично, поэтому после 10 лет математики его понять и правда проще, чем объяснять концепцию переменных, триггеров, регистров и вот этого всего. Не говоря про то, что человек плохо в голове держит состояние, одна из причин, по которым goto был признан неудачным, и намного лучше справляется с причинно-следственными цепочками "если-то".


              1. Druu
                06.08.2019 12:54

                Ну я к этому и веду. ФП математично, поэтому после 10 лет математики его понять и правда проще, чем объяснять концепцию переменных, триггеров, регистров и вот этого всего.

                Это все ровно до тех пор, пока у нас простенькие алгоритмы вроде получили — преобразовали — вернули. А как только нужны сайдэффекты и прочее ИО — происходит беда, так как корректное "математичное" описание этих вещей — штука далеко не тривиальная, и в итоге императивная модель оказывается значительно проще.


                1. PsyHaSTe
                  06.08.2019 14:31
                  -1

                  А как только нужны DI и прочие солиды — происходит беда, т.к. простое "оопэшное" описание этих вещей — штука далеко нетривиальная.


                  1. poxvuibr
                    06.08.2019 14:43

                    В чём проблема с DI?


                    1. 0xd34df00d
                      06.08.2019 16:53

                      Как его описать в терминах ООП так, чтобы компилятор гарантировал, что вы все ваши D дергаёте через I, а не напрямую?


                      1. mayorovp
                        06.08.2019 17:04

                        Так это вы и в ФП не сможете.


                        1. 0xd34df00d
                          06.08.2019 17:09

                          Ну почему, каждая dependency выражается соответствующей монадой. А дальше включаем {-# LANGUAGE Safe #-} и копируем нашу дискуссию из соседней ветки.


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


                          1. mayorovp
                            06.08.2019 17:39

                            Ну почему, каждая dependency выражается соответствующей монадой.

                            А как вы заставите выражать её соответствующей монадой, а не использовать напрямую? :-)


                            1. 0xd34df00d
                              06.08.2019 17:44

                              А как вы заставите выражать её соответствующей монадой, а не использовать напрямую? :-)

                              А как вы напрямую будете писать в лог, дёргать сервер или заниматься чем-то подобным?


                              Это ж эффекты всё.


                              1. mayorovp
                                06.08.2019 18:03

                                Напишу весь код в монаде IO, и компилятор мне не помешает. Даже подскажет, где я ещё забыл IO добавить!


                                Но, вообще говоря, D в DI — это не обязательно эффекты. Вот совсем недавно я писал функцию преобразования типа данных DbRequestForRegistration -> RequestForRegistrationInfo, которая почти сводилась к копированию полей 1 к 1, за некоторыми исключениями. Эта функция, исходя из соображений архитектуры (к которой у меня есть претензии, но не я главный на проекте), внедряется в виде интерфейса (=тайпкласса) в конструктор.


                                И если бы я писал на Haskell — то у меня было бы написано как-то так:


                                data DbFoo = ...;
                                data FooInfo = ...;
                                
                                class IFooMapper a where
                                    mapFoo :: a -> DbFoo -> FooInfo
                                
                                data FooMapper = FooMapperInstance;
                                
                                instance IFooMapper FooMapper where
                                    mapFoo FooMapperInstance entity = ...;

                                Так вот, в переводе на Haskell ваш вопрос будет звучать так:


                                как описать в терминах ФП, чтобы компилятор гарантировал, что ни одна функция bar за пределами Composition Root не использует FooMapperInstance напрямую, вместо этого имея сигнатуру вида IFooMapper m => m -> ...?

                                Да никак не описать! Просто от программиста нужна сила воли, чтобы либо следовать архитектуре, либо рефакторить её к чертям, но никак не обходить её.


                                PS на тот случай если вам кажется что код в примере слишком плохой для примера, можно было сделать лучше и вообще проблемы мы сами себе придумали:


                                Да, так и есть. Но это не отменяет того факта, что выделение и внедрение зависимостей довольно косвенно связано с контролем сайд-эффектов.


                                1. 0xd34df00d
                                  06.08.2019 18:54

                                  Я, наверное, не очень понял ваш пример, но кто мешает просто не экспортировать FooMapperInstance из вашего модуля?


                                  1. mayorovp
                                    06.08.2019 19:55

                                    Мешает тот факт, что вам его все равно нужно как-то прокинуть до Composition Root.


                                    1. 0xd34df00d
                                      06.08.2019 20:08

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


                                      1. mayorovp
                                        06.08.2019 20:29

                                        А кода выше вам не достаточно?


                                        Ну вот ещё:


                                        testData = ...
                                        
                                        -- изображаем запрос к БД
                                        good :: IFooMapper m => m -> Int -> IO FooInfo
                                        good mapper id = return $ mapFoo mapper testData;
                                        
                                        bad ::Int ->  IO FooInfo
                                        bad id = return $ mapFoo FooMapperInstance testData
                                        
                                        --  Composition Root
                                        main = do 
                                            let query = good FooMapperInstance
                                            info <- query 12
                                            print info


                          1. mayorovp
                            06.08.2019 17:42

                            Так вот, как бы вы доказывали доступными первокурснику методами, что структура векторного пространства над полем рациональных чисел определяется единственным образом?

                            А что мешает ввести понятие базиса, доказать что все базисы векторного пространства имеют одну и ту же размерность, после чего уже ввести систему координат?


                            Если что, мне это всё в 8м классе рассказывали, значит и первокурсник поймет. Ежели мотивация понимать будет. Вот без мотивации всё плохо...


                            1. 0xd34df00d
                              06.08.2019 17:47

                              А что мешает ввести понятие базиса, доказать что все базисы векторного пространства имеют одну и ту же размерность, после чего уже ввести систему координат?

                              Я, наверное, туплю, но как это поможет доказать, что если у вас есть две операции • и 0 умножения вектора (из какого-нибудь V) на скаляр, и если скаляры лежат в ?, то ? ?: ?, v: V. a • v = a 0 v?


                              1. Druu
                                07.08.2019 05:52
                                +1

                                >Я, наверное, туплю, но как это поможет доказать, что если у вас есть две операции • и 0 умножения вектора (из какого-нибудь V) на скаляр, и если скаляры лежат в ?, то ? ?: ?, v: V. a • v = a 0 v?

                                Не поможет, но чего там вообще доказывать? 1 @ v = 1 ^ v для любых умножений @/^, тогда (1+..+1)@v = (1+...+1)^v по дистрибутивности (т.о. для целых чисел все выполняется). Далее, если a@v = a^v => 1/a @ v = 1/a ^ v, т.к. (a * 1/a) @ v = (a * 1/a ) ^ v => a @ (1/a @ v) = a ^ (1/a ^ v) и для целых а мы уже доказали.

                                Т.о. выполняется для целых и обратных целым, а любое умножение на рациональное число можно представить как последовательное умножение на некоторое целое и обратное некоторому целому.


                                1. 0xd34df00d
                                  07.08.2019 06:13

                                  А мне через то, что ? начальный объект в Ring, морфизм ? > ? — эпиморфизм в Ring, а векторное пространство над ? — это просто морфизм в соответствующее кольцо эндоморфизмов, как-то очевиднее. То есть, если задешугарить все эти определения, то получится то же самое (особенно если посмотреть на доказательство того, что это эпиморфизм), но мы как-то сразу построили удачные абстракции, и потом надо их просто комбинировать.


                                  1. Druu
                                    07.08.2019 07:06

                                    А мне через то, что ? начальный объект в Ring

                                    Ну вы ж спрашивали про "элементарными методами для первокурсника" — вот тут не сильно сложнее.
                                    Здесь показательнее пример с теоремой про существование и единственность решения задачи Коши, которая доказывается дефолтно долго-долго, а через теорему о неподвижной точке — в три-четыре строки (+ 1 лекция чтобы доказать саму теорему о неподвижной точке...)


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


                                    а векторное пространство над ? — это просто морфизм в соответствующее кольцо эндоморфизмов, как-то очевиднее

                                    Дык, а почему морфизм-то этот — единственный? :)
                                    Типа, на эпиморфизм сократили?


                                    1. 0xd34df00d
                                      07.08.2019 07:18

                                      Дык, а почему морфизм-то этот — единственный? :)
                                      Типа, на эпиморфизм сократили?

                                      Да.


                      1. poxvuibr
                        06.08.2019 18:59

                        Передавать зависимости через конструктор, в чём проблема то?


                  1. Druu
                    06.08.2019 16:47
                    +1

                    А как только нужны DI и прочие солиды — происходит беда, т.к. простое "оопэшное" описание этих вещей — штука далеко нетривиальная.

                    Тривиальная, почему нет? Собственно, проще триггерорв/регистров и всего вот этого. И заведомо проще "математичного" ИО.


      1. A114n
        06.08.2019 12:24

        Потому что ООП интуитивно понятен человеку, как бы ни иронизировал над этим автор статьи.


        1. PsyHaSTe
          06.08.2019 14:32
          -1

          Я боюсь человеку не будет интуитивно понятно, что такое "фабрика блокнотов", откуда у блокнота метод "записать", и зачем тут какой-то "менеджер".


    1. Voronar
      05.08.2019 17:18

      F# довольно популярен среди .NET-разработчиков.
      Там извлекают самое лучшее из ООП и заворачивают это в ФП.
      fsharpforfunandprofit.com/posts/fsharp-is-the-best-enterprise-language


      1. PsyHaSTe
        05.08.2019 17:37

        Есть мнение, что ФП без HKT невозможно. По крайней мере я не знаю, как писать в ФП стиле без монад.


        1. Neftedollar
          05.08.2019 19:16
          +2

          в Ocaml HKT нет, но если кто скажет что это не фп язык, то даже не знаю какой он.


        1. Yuuri
          06.08.2019 08:28
          -1

          В F# вычислительные выражения в качестве и.о. монад есть, хоть и определены ad-hoc без HKT.


    1. sshikov
      05.08.2019 20:10

      Насчет широко не знаю, но вот Spark например…


    1. defuz
      05.08.2019 22:35
      +1

      По той же причине, по которой PHP, Javascript и Go набрали популярность. Популярным становится не то что лучше, а то что проще объяснить.

      В ООП, как и в реальном мире, все сущности – это объекты, у каждого объекта есть класс (кошечки и собачки). Классы могут наследоваться (и кошечки, и собачки – это животные). Можно инкапсулировать один объект в другой (как двигатель в машину). Можно часть объекта сделать приватной (как переписка в телеге с Машей), а часть – публичной (как профиль в инстаграмме). Еще можно делать фабрики объектов – это как настоящие фабрики, только любых объектов. Ну и так далее.

      Поздравляю, если вы поняли то что я написал выше, значит вы усвоили принципы ООП. Теперь вы – энтерпрайз программист.


  1. muhaa
    05.08.2019 16:36

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


  1. rboots
    05.08.2019 16:48
    +2

    Вы сравниваете средний код ФП с плохим кодом ООП. Так будет выглядеть более-менее приличное решение с ООП. По вашему это плохо?

    class FruitFinder {
      fruits = new Map([
        ['apple', { type: 'apple', price: 1.99 }],
        ['orange', { type: 'orange', price: 2.99 }],
        ['grape', { type: 'grape', price: 44.95 }], 
      ]);
    
      getFruit (name) {
        return this.fruits.get(name);
      }
    
      getPrice (name) {
        return this.getFruit(name).price;
      }
    }
    
    const finder = new FruitFinder();
    
    console.log('apple price', finder.getPrice('apple'));
    

    Ноль зависимостей. Мемоизация в Map в конструкторе. Разнесены медоды получения цены и фрукта. Возможность расширения. Возможность инкапсуляции. Решение на ФП против этого выглядит как вермишель.


    1. 0xd34df00d
      05.08.2019 17:13
      +1

      По вашему это плохо?

      Да. Я смотрю на сигнатуру функции getFruit (предполагая, что она бы была) и не понимаю, что она делает, если такого фрукта нет. Кидает экзепшон? Возвращает null? Возвращает созданный по умолчанию фрукт? Идёт на сервер и обновляет список фруктов?


      От чего зависит getFruit? Если завтра в вашем классе появится список, не знаю, типов фруктов, какое эта функция будет иметь к нему отношение? А он появится, практика показывает.


      1. mayorovp
        05.08.2019 17:49

        Да. Я смотрю на сигнатуру функции getFruit (предполагая, что она бы была) и не понимаю, что она делает, если такого фрукта нет.

        Простите, а что непонятного в сигнатуре (this: FruitFinder, name: string) -> { type: string, price: number } | null? :-)


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


        От чего зависит getFruit?

        От FruitFinder. Детальнее знать и не надо, A — Абстракция.


        1. PsyHaSTe
          05.08.2019 18:09
          -1

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

          в ФП сигнатура отсекает такую возможность, в чем её суть и состоит. Если функция возвращает null, то эксепшон она не кинет (иначе возвращала бы Either).


          1. mayorovp
            05.08.2019 19:47

            В ФП, в некоторых языках, бывает и ?. И даже unsafePerfornIO, так что даже запрос к серверу формально сигнатурой не может быть исключен.


            Не делать дичи — вопрос соглашений и желания программиста им следовать.


            1. 0xd34df00d
              05.08.2019 19:53
              -1

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

              Добавляете {-# LANGUAGE Safe #-} и исключаете всю эту ерунду.


              1. mayorovp
                05.08.2019 19:58
                +1

                Полностью исключить ерунду невозможно, пока есть модули, FFI, позднее связывание бинарников в рантайме и левые драйвера в ядре ОС. Есть лишь уровни уверенности в отсутствии этой ерунды.


                1. 0xd34df00d
                  05.08.2019 20:00
                  -1

                  пока есть модули

                  Из Safe вы можете импортировать только Safe.


                  FFI

                  В Safe всё FFI должно иметь тип IO.


                  позднее связывание бинарников в рантайме

                  Ну это либо тоже IO, либо таки на уровне драйверов ОС (но обсуждать это перебор, ИМХО).


                  1. mayorovp
                    05.08.2019 20:15
                    -1

                    Из Safe вы можете импортировать только Safe.

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


                    Ну ладно, отладку заменят типизация и внимательный взгляд, но что делать с счётчиками производительности?


                    1. 0xd34df00d
                      05.08.2019 20:24

                      Заодно избавились от удобного языкового средства трассировки, теперь если что не так — только отладчиком...

                      Вы про Debug.Trace для отладки бага вот прям сейчас при разработке? Если да, то снимаете прагму, дебажите, навешиваете обратно.


                      Если вы про нормальное логгирование, то и не надо его было так делать, надо заворачиваться в MonadWriter/etc.


                      Ну ладно, отладку заменят типизация и внимательный взгляд, но что делать с счётчиками производительности?

                      Тоже заворачиваться в монаду. Вот, например, на днях ваш покорный слуга наваял.


                      1. mayorovp
                        05.08.2019 21:38

                        Вы про Debug.Trace для отладки бага вот прям сейчас при разработке? Если да, то снимаете прагму...

                        … со все цепочки модулей. Так себе занятие...


                        Тоже заворачиваться в монаду.

                        Это если они постоянные.


                        1. 0xd34df00d
                          05.08.2019 21:59

                          … со все цепочки модулей. Так себе занятие...

                          Ну, тут есть несколько вариантов, по мере возрастания сложности и реюзабельности:


                          1. Вы же пишете тесты? В тестах Safe всё равно не нужен, так что снимаете только с тестируемого модуля, и всё.
                          2. Делаете отдельный Trustworthy-пакет с Debug.Trace и прочими нужными вам функциями, указываете при сборке своего кода -trust чётотам или тому подобное (точно не помню, у меня никогда такой нужды не возникало).
                          3. Добавляете в ghc флаг, заставляющий его игнорировать safe-аннотации при сборке.

                          Это если они постоянные.

                          Не понял. В смысле?


                          1. mayorovp
                            06.08.2019 08:25

                            указываете при сборке своего кода -trust чётотам или тому подобное

                            О, отлично, значит возможность сделать запрос к серверу из чистой функции снова есть :-)


                            1. 0xd34df00d
                              06.08.2019 15:06

                              Нет, вы же не добавили в свой trusted-пакет соответствующую функцию ;)


                              1. mayorovp
                                06.08.2019 15:17

                                Но я и не один в команде работаю. И вообще это может быть legacy-код от индусов заказчика.


                                1. 0xd34df00d
                                  06.08.2019 15:52

                                  В любом случае, это opt-in, а не opt-out, и скорее уже организационные проблемы. Язык даёт вам средства очень точного контроля за лазейками, а как вы ими распорядитесь — вопрос десятый.


      1. Druu
        05.08.2019 18:28
        -1

        Да. Я смотрю на сигнатуру функции getFruit (предполагая, что она бы была) и не понимаю, что она делает, если такого фрукта нет. Кидает экзепшон? Возвращает null? Возвращает созданный по умолчанию фрукт? Идёт на сервер и обновляет список фруктов?

        Ну, типа, если бы вы не поленились и посмотрели сигнатуру, а не рассуждали, то:


        interface Map<K, V> {
            clear(): void;
            delete(key: K): boolean;
            forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
            get(key: K): V | undefined;
            has(key: K): boolean;
            set(key: K, value: V): this;
            readonly size: number;
        }

        и, с-но, сигнатура getFruit будет:


        (method) FruitFinder.getFruit(name: string): {
            type: string;
            price: number;
        } | undefined

        Теперь, посмотрев на реальную сигнатуру, вы же сможете ответить на свой вопрос? :)


        ЗЫ: и, да, этот код тайпчек не пройдет на this.getFruit(name).price
        ЗЫЫ: и непонятно, при чем тут ФП-то


        1. 0xd34df00d
          05.08.2019 18:38
          +2

          Ну, типа, если бы вы не поленились и посмотрели сигнатуру, а не рассуждали, то:

          А я не знаю, куда смотреть-то, даже если не лениться. Но это так.


          Теперь, посмотрев на реальную сигнатуру, вы же сможете ответить на свой вопрос? :)

          Я всё ещё не знаю, не лезет ли эта функция на сервер.


          И да, я не знаю достаточно хорошо правил языка, на котором записано описание Map<K, V>, чтобы через параметричность вывести, возвращает мне эта функция на самом деле undefined, если она ничего не нашла, или же какой-то по умолчанию сконструированный фрукт.


          1. Druu
            05.08.2019 19:49

            Я всё ещё не знаю, не лезет ли эта функция на сервер.

            Ну, вы ни в каком языке не знаете, у вас может быть только определенная уверенность (чуть большая или чуть меньшая). Вот в тс, если ф-я не возвращает void или промис — значит на сервер, скорее всего, не лезет и сайд-эффекты не совершает.


            возвращает мне эта функция на самом деле undefined, если она ничего не нашла, или же какой-то по умолчанию сконструированный фрукт.

            Это вы зависимые типы хотите уже. Хотя, опять-таки, непонятно при чем тут ФП — типы-то ему ортогональны. ФП не про типы и не про чистоту, ФП — про нетривиальную композицию функциональных объектов. Когда вы redux-saga юзаете, вы вполне себе занимаетесь полноценным фп, хотя это может быть даже не ts, а js, т.е. без типов вообще (хотя генераторы и в тс нормально не типизируются). Т.к. это свободная монада прикрученная к эквивалентному do-нотации синтаксису. С другой стороны, если вы на идрисе пишите и при этом у вас все чисто, да и вообще каким-нибудь point-free обмазались, это не значит, что у вас ФП (хотя и одни функции кругом). Ну точно так же как если вы в ф-ю reduce вместо IAbstractReducer какого-нибудь передали просто лямбду — ФП не появилось от этого, вопрос ведь в ментальной модели.


            1. 0xd34df00d
              05.08.2019 19:57
              +1

              Это вы зависимые типы хотите уже.

              Не, конкретно в этом случае это не обязательно, я ж не зря parametricity упомянул. Если у вас функция lookup :: Ord k => M.Map k v -> v, то вы знаете, что построить объект она не может, потому что она ничего не знает про v (а было бы, скажем, Default v — знала бы). Поэтому вы заодно знаете, что эта функция заведомо не тотальная.


              Хотя, опять-таки, непонятно при чем тут ФП — типы-то ему ортогональны. ФП не про типы и не про чистоту, ФП — про нетривиальную композицию функциональных объектов.

              Это, конечно, субъективщина и философия, но тем не менее: ИМХО цены для рассуждений о коде в этой композиции не так уж много. Типы важнее. Толку вам с того, что у вас есть какая-то функция с именем map и другая функция с именем filter, и вы что-то там как-то куда-то им передаёте?


              1. Druu
                06.08.2019 09:00
                +2

                Если у вас функция lookup :: Ord k => M.Map k v -> v, то вы знаете, что построить объект она не может

                Эм, так в get(key: K): V | undefined; тоже нет ничего про V. И вы видите, что она возвращает undefined, если ключа нет. А в вашем случае если ключа нет, то что происходит? Как это определить из сигнатуры? И, кстати, из этой сигнатуры отсутствие тотальности как раз не следует никак. Для того, чтобы сделать заключение о тотальности, вам нужно знать семантику ф-и (то, что мапа, и что вы по ключу что-то достаете, и что ключа может не быть, и вот если его нет...).


                ИМХО цены для рассуждений о коде в этой композиции не так уж много.

                А при чем тут рассуждения о коде? Какое отношение рассуждения о коде имеют к ФП?


                Толку вам с того, что у вас есть какая-то функция с именем map и другая функция с именем filter, и вы что-то там как-то куда-то им передаёте?

                Так я об этом и говорю. Совершенно без толку. Важно, не какие у вас filter и map, или какие типы, а какая у вас ментальная модель программы.
                Тот факт, что вы на идрисе что-то написали и доказали и вот это вот все — какое отношение имеет к ФП? Да никакого, ведь вы вполне могли написать все это в рамках императивной модели. Про которую тоже можно доказывать не хуже чем про ФП.
                Вот и получается, что типы — не являются ни достаточным ни необходимым условием для ФП, раз уж у нас на практике существует ФП без типов и не-ФП с типами.
                Я вам даже больше скажу — ФП вполне может быть без лямбд :)


                1. 0xd34df00d
                  06.08.2019 15:12
                  +1

                  А в вашем случае если ключа нет, то что происходит?

                  ? происходит.


                  Как это определить из сигнатуры?

                  Если пытаться формализовать хаскель, то там как раз любой возвращаемый тип на самом деле объединяется с ?.


                  И, кстати, из этой сигнатуры отсутствие тотальности как раз не следует никак. Для того, чтобы сделать заключение о тотальности, вам нужно знать семантику ф-и (то, что мапа, и что вы по ключу что-то достаете, и что ключа может не быть, и вот если его нет...).

                  Безусловно, семантику (или денотацию) знать надо (или хотя бы надо знать. что такое Map). Но разве это не подразумевается предметом нашего обсуждения?


                  Да никакого, ведь вы вполне могли написать все это в рамках императивной модели. Про которую тоже можно доказывать не хуже чем про ФП.

                  Только метатеория (пока?) не очень хорошо проработана, но то такое.


                  Тот факт, что вы на идрисе что-то написали и доказали и вот это вот все — какое отношение имеет к ФП?

                  Да на самом деле никакого. Но, раз уж мы об этом заговорили, то в чём смысл и ценность ФП?


                  1. mayorovp
                    06.08.2019 15:28

                    Если пытаться формализовать хаскель, то там как раз любой возвращаемый тип на самом деле объединяется с ?.

                    Это понятно, но остаётся открытым вопрос: как понять из одной только сигнатуры, что произойдет когда нужного ключа нет?


                    Если что, возможности тут следующие помимо вашей:


                    1. функция может вернуть значение по умолчанию, только не через класса Default, а просто сохраненное в самом контейнере;


                    2. функция может сформировать значение по умолчанию исходя их ключа (используя сохраненную в самом контейнере фабрику);


                    3. функция может вернуть значение по ближайшему ключу снизу или сверху (Ord же!).



                    Безусловно, семантику (или денотацию) знать надо (или хотя бы надо знать. что такое Map). Но разве это не подразумевается предметом нашего обсуждения?

                    Так весь фокус в том, что если мы знаем что такое MapFruitFinder — то нам сигнатура уже и не нужна. А если не знаем — то сигнатура, конечно же, ответит нам что функция нам вернет — но без завтипов не ответит в каких случаях.


                    1. 0xd34df00d
                      06.08.2019 15:55

                      Это понятно, но остаётся открытым вопрос: как понять из одной только сигнатуры, что произойдет когда нужного ключа нет?

                      Достаточно знать, что можно создать пустую мапу (или, строже, что есть функция empty :: Map k v).


                      1. mayorovp
                        06.08.2019 16:06

                        А вот и нет, вариант номер 3 никак при этом не отсекается.


                        Если подумать, то и варианты 1 и 2 тоже не отсекаются — просто в мапе может быть одним из полей Maybe v и поведение lookup при создании мапы разными конструкторами может отличаться.


                        1. 0xd34df00d
                          06.08.2019 16:22

                          А вот и нет, вариант номер 3 никак при этом не отсекается.

                          Как, если мапа пустая?


                          Если подумать, то и варианты 1 и 2 тоже не отсекаются — просто в мапе может быть одним из полей Maybe v

                          Который будет Nothing, потому что откуда взяться значению типа v при создании мапы, если мы про v ничего не знаем?


                          Или, обобщая, тип lookup 'a' empty?v:*. v. А мы с вами же знаем, что таких термов быть не может в sound-теории.


                          1. mayorovp
                            06.08.2019 16:28

                            Как, если мапа пустая?

                            Если совсем-совсем пустая — можно и ? вернуть. А если чем-то наполнена — то ? возвращать не обязательно.


                            Который будет Nothing, потому что откуда взяться значению типа v при создании мапы, если мы про v ничего не знаем?

                            Повторюсь: поведение lookup при создании мапы разными конструкторами может отличаться. Вам недостаточно знать, что есть функция empty :: Map k v, вам надо видеть полный список возможных конструкторов и мутаторов.


                            1. 0xd34df00d
                              06.08.2019 16:33

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


                              1. mayorovp
                                06.08.2019 16:45

                                Если смотреть самое начало — то там вы утверждали, что по сигнатуре getFruit непонятно что функция делает. Так вот, по сигнатуре lookup точно так же непонятно что она делает. Разве что понятно что на сервер она не будет ломиться, если только её создатель вообще понимает зачем вообще в языке ограничения. Но то, что она для отсутствующего ключа гарантированно вернет ? — уже из сигнатуры не видно.


                                А насчет тотальности — да, согласен. Из двух функций (empty и lookup) хотя бы одна обязана не быть тотальной, и скорее всего это не первая.


                                1. PsyHaSTe
                                  06.08.2019 17:55

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


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


                                  1. mayorovp
                                    06.08.2019 18:32

                                    В данном случае, ? — это не зависание, а паника.


                  1. Druu
                    06.08.2019 17:50
                    +1

                    ? происходит.

                    Ага, но жопа неявно в любом типе есть, то есть это как если бы с get в ts всегда в любом типе был null/undefined. Оно так и есть в non-strict режиме, но в non-strict никто не пишет, он нужен только при портировании.
                    При этом нас компилятор никак не форсит эту жопу обработать — а обработку undefined форсит. Ну т.е. сигнатура в тс заведомо информативнее, безопаснее, и вообще круче :)


                    Если пытаться формализовать хаскель, то там как раз любой возвращаемый тип на самом деле объединяется с ?.

                    Ну вы ж понимаете, что это как "у нас в любом типа может быть налл" :)


                    Но разве это не подразумевается предметом нашего обсуждения?

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


                    Только метатеория (пока?) не очень хорошо проработана, но то такое.

                    Вообще, она была исчерпывающе проработана еще в те времена, когда ФП не было даже в проекте.


                    Но, раз уж мы об этом заговорили, то в чём смысл и ценность ФП?

                    Ну свою точку зрения я выше уже указывал — для меня ФП начинается тогда, когда мы сами определяем средства (де)композиции. В чем ценность? Ну, с-но, в том, что мы можем выбрать (или создать) тие средства, которые хотим и которые наиболее удобны в данном конкретном случае.


                    (или, строже, что есть функция empty :: Map k v)

                    Ну пусть empty возвращает жопу, с-но пустых мап нет :)


                    1. 0xd34df00d
                      06.08.2019 18:02
                      +1

                      При этом нас компилятор никак не форсит эту жопу обработать — а обработку undefined форсит. Ну т.е. сигнатура в тс заведомо информативнее, безопаснее, и вообще круче :)

                      В этом вашем тайпскрипте нет исключений, например? :)


                      Тамошний undefined, судя по описанию, больше похож на Nothing, а T | undefined — на Maybe T.


                      Вообще, она была исчерпывающе проработана еще в те времена, когда ФП не было даже в проекте.

                      И что там про доказывание? Я из таких языков только ATS знаю слышал, но там с доказательствами не оч.


                      В чем ценность? Ну, с-но, в том, что мы можем выбрать (или создать) тие средства, которые хотим и которые наиболее удобны в данном конкретном случае.

                      Прям лиспом запахло.


      1. fogone
        06.08.2019 18:15

        Так нетипизированный язык же, в том же котлине будет очевидно:

        fun main() {
            val fruitRepository = FruitRepository(
                    Fruit(type = "apple", price = 1.99.toBigDecimal()),
                    Fruit(type = "orange", price = 2.99.toBigDecimal()),
                    Fruit(type = "grape", price = 44.95.toBigDecimal())
            )
        
            val price = fruitRepository.findFruitPriceByType("apple")
        
            println(price ?: "priceless")
        }
        
        data class Fruit(val type: String, val price: BigDecimal)
        
        class FruitRepository(vararg fruits: Fruit) {
        
            private val index = fruits.map { it.type to it }.toMap()
        
            fun findFruitByType(type: String): Fruit? = index[type]
        
            fun findFruitPriceByType(type: String): BigDecimal? = findFruitByType(type)?.price
        }
        


    1. muhaa
      05.08.2019 17:55
      +3

      Хорошо, у нас есть FruitFinder и теперь можно его использовать. Допустим, теперь вам надо написать написать алгоритм, в котором к списку фруктов будет много раз случайно пробно добавляться по несколько фруктов и потом этот пробный список будет каждый раз передаваться в функцию, которая оценивает для этого списка некую целевую функцию и таким образом ищет наилучший вариант дополнения фруктами. Причем, эта функция на входе должна получить список и объекты (или функции) для получения нужной информации из этого списка.
      Что мы имеем:
      1. Мы можем передать этой функции FruitFinder, но ей может быть нужен, скажем getPrice, но не нужен какой-нибудь getByWeight, который может быть в FruitFinder. Кроме того, ей и только ей может быть нужен какой-нибудь getByColor, которого в FruitFinder не предусмотрели. Что делать? Наследовать много FruitFinder-ов для каждого такого алгоритма или делать один универсальный монструозный FruitFinder?
      2. В FruitFinder пока нельзя добавлять фрукты. Создавать FruitFinder с расширенным списком каждый раз накладно. Допустим, наследуем FruitFinder с возможностью добавления. Тогда чтобы привести список в исходное состояние (для следующей итерации) придется использовать функцию удаления последних добавленных фруктов.
      3. Учитывая мутабельность списка в предыдущем пункте, один и тот же список фруктов в FruitFinder нельзя использовать в разных потоках (при параллельной обработке разных запросов). Для обработки каждого запроса нужно копировать свой экземпляр списка.
      4. Новый программист пытается использовать FruitFinder. Он видит иерархию FruitFinder-ов или один FruitFinder с множеством методов. Что происходит внутри FruitFinder — нужно разбираться. Если не разбираться, могут быть сложные последствия для скорости работы, памяти и прочего. Но программисту возможно нужна только одна очень простая функция…

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


      1. mayorovp
        05.08.2019 20:09

        На самом деле, в ФП всё так же возникает проблема номер 2 (вариация "Создавать FruitFinder с расширенным списком каждый раз накладно"). Если его и правда накладно создавать — его и в ФП будет накладно на каждой итерации создавать, и вы тут ничего не измените. А решения этой проблемы, используемые в ФП, и для ООП сгодятся.


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

        Любой метод этого объекта соответствует функции из ФП. Чистый метод — чистой функции, нечистый — функции использующей какую-то из монад.


        Единственное место, где может "просесть" производительность — в конструкторе, ведь метод всегда принимает объект целиком и не может принять его часть. Но тут тоже всё просто: надо держать конструктор простым, а класс — в рамках SRP.


        1. muhaa
          05.08.2019 21:29

          На самом деле, в ФП всё так же возникает проблема номер 2 (вариация «Создавать FruitFinder с расширенным списком каждый раз накладно»). Если его и правда накладно создавать — его и в ФП будет накладно на каждой итерации создавать, и вы тут ничего не измените. А решения этой проблемы, используемые в ФП, и для ООП сгодятся.
          Простейшее решение добавления элемента для ФП — создавать новый список, содержащий еще один элемент и указатель на исходный список. Таким образом можно получить сколько угодно вариантов расширенных списков одновременно и все они будут использовать один и тот же исходный список. Это возможно потому, что в ФП у нас все «кишки наружу» и список доступен непосредственно. Как это просто сделать в объектном стиле, если FruitFinder скрывает список и не позволяет никому пользоваться его элементами?
          Любой метод этого объекта соответствует функции из ФП. Чистый метод — чистой функции, нечистый — функции использующей какую-то из монад.
          Но речь же не о математической эквивалентности. Основная проблема программирования — борьба со сложностью. Есть много задач которые отлично декомпозируются в стиле ФП и для которых чистых функций достаточно, а если их не достаточно, можно чуть отклониться в обычный процедурный стиль и все равно все останется простым и понятным.


          1. mayorovp
            05.08.2019 21:41

            Как это просто сделать в объектном стиле, если FruitFinder скрывает список и не позволяет никому пользоваться его элементами?

            Добавить метод в FruitFinder, которому все внутренности будут доступны.


            При нарушении SRP — вынести сам список в отдельный класс. Ну или взять готовый класс из подходящей библиотеки.


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

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


            1. muhaa
              06.08.2019 10:18

              Добавить метод в FruitFinder, которому все внутренности будут доступны.
              При нарушении SRP — вынести сам список в отдельный класс. Ну или взять готовый класс из подходящей библиотеки.
              В итоге мы рискуем получить большую и сложную для понимания систему для решения довольно тривиальной по своей сути задачи.
              Вероятно та же проблема возникла бы с ФП, если задача не сводится к одному сложному алгоритму с явным входом и выходом.
              … а есть много задач, которые отлично декомпозируются в объекты, и для которых методов достаточно. Иногда это даже те же самые задачи.
              Это конечно же правда. Пример с FruitFinder был хорош тем, что этот класс сам по себе уже довольно искусственная абстракция и возможно здесь ФП стиль предпочтительнее.


              1. mayorovp
                06.08.2019 10:26

                Вероятно та же проблема возникла бы с ФП, если задача не сводится к одному сложному алгоритму с явным входом и выходом.

                Именно так и есть. Более того, в ООП только в таком случае сложные системы и возникают (если не играться с архитектурой ради архитектуры).


          1. rboots
            05.08.2019 21:46

            Это возможно потому, что в ФП у нас все «кишки наружу»

            Основная проблема программирования — борьба со сложностью.

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


            1. muhaa
              07.08.2019 10:20
              +2

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


      1. rboots
        05.08.2019 20:17

        Естественно можно придумать ситуации, в которых ФП или ООП будет работать лучше другого, поэтому оба эти подхода и до сих пор активно используются. Плюсы ФП я признаю и расписал в свой статье Хватит спорить про функциональное программирование и ООП. Просто в данной конкретной статье приведён очень плохой код ООП, я его исправил. Вы написали новое ТЗ, которое содержит чуть ли не больше слов, чем сам уже написанный код, и которое по определению предполагает иммутабельность. Я не отрицаю, что на ФП этот частный случай может иметь элегантное решение, но:
        — очень неблагодарное дело перезакладываться на все возможные случаи изменений требований
        — конкретно это требование (случайно пробно добавляться по несколько фруктов...), в реальных проектах будет возникать сравнительно редко
        — в ООП такие вещи тоже не так сложно решаются, созданием метода клонирования объекта, сериализации или просто вынесением фруктов в отдельную сущность и добавлением её к экземпляру класса агрегацией.
        Мой тезис в том, что, к сожалению, очень многие из тех, кто активно ратует за ФП, не освоили в должной мере ООП, пишут плохой код на ООП (взять даже эту статью) и жалуются, что парадигма плохая. Парадигма замечательная, просто для хорошего кода не достаточно знать функторы и монады, хорошо бы знать наблюдатель, фабрику, стратегию, зачем нужна слабая связанность, когда ей можно пожертвовать, когда и зачем использовать инкапсуляцию и т.п… Только это долго изучать, ФП кажется коротким путём, но это только кажется, почти весь промышленный софт всё таки по ООП написан.


        1. muhaa
          06.08.2019 08:42

          — конкретно это требование (случайно пробно добавляться по несколько фруктов...), в реальных проектах будет возникать сравнительно редко
          Для моих проектов это самые обычные требования. Мне кажется главная проблема книг и статей по ООП и ФП в том, что они никак не классифицируют задачи и делают вид будто описываемая технология одинаково эффективно и просто декомпозирует любого рода задачи («просто» — в смысле легко понять как это работает и легко переделать или расширить). Когда автор описывает некие патерны, он подразумевает решение неких привычных для него задач. Но объяснять он это может на жирафах и слонах, из любви к искусству. Если программист, который читает книгу не сталкивался с теми же задачами, как автор, возникает куча недоразумений и недопонимания (это мой случай).


  1. dss_kalika
    05.08.2019 17:05

    Не могу понять, это сарказм или очень эмоциональный текст…


  1. gmixo
    05.08.2019 17:15

    Открывал в статью с готовностью защищать ФП), получилось что в целом согласен с мнением автора(хотя и так тщательно скрытым). Сам на данный момент использую по максимуму ФП, при этом щепотка ООП обычно бывает полезной. Статья конечно провокационная, приятно удивила)


  1. snamef
    05.08.2019 18:25
    +1

    В следующем фрагменте кода продемонстрированы проблемы,

    вы продемонстрировали проблемы непонимания основ структурирования кода потому что вы делаете хеш таблицу
    prices = { 'apple': [1.99 , 1.59 ..?] , 'orange': [2.99] , 'grape' : [44.95]}
    там просто должно быть несколько цен иначе код вообще бессмысленнен и вы хотите выбрать первую цену которая у яблок, это
    lambda type_name : prices.get(type_name , [0])[0]
    а функциональное программирование уменьшает флудокод в 200500 раз, делает его более чистым и отлично сочетается в OOP в нормальных руках

    for (const u in users)

    это вообще не о функциональном программировании.
    а вот за ненужные циклы вместо хеширования надо сразу увольнять с занесением в бд дураков. На практике видел следующий проэкт -> человек делает через JDBC запрос выбрать все то есть select что то там * и потом В ПРОГРАММЕ ЦИКЛОМ ФИЛЬТРУЕТ, причём каждый раз при выборе по имени.
    Так что там или селект или хеш таблица {userSelectionStrategy: user_name}

    Для того чтобы достичь достойного уровня в сфере ООП нужно 20-30 лет.
    надо прослушать нормальный курс софтваредизайна и поучаствовать в нескольких проэктов чистого кода, тогда будет видно что весь код описанный в этом посте г, и например если флудить фабрику, то пусть хотя бы одна для всех фруктов, а чтобы не искать, каждый тип заносится в отдельный лист. И это не вопрос функционального программирования.
    class Fruit {
    constructor(type, price) {
    this.type = type;
    this.price = price;
    }
    }

    class FruitRepository {
    constructor() {
    this.fruitListPerTypes = {};
    }

    class FruitFactory {
    constructor(FruitRepository f) {
    this.fruitRepository = f;
    }

    Fruit newFruit(type, price)
    a = Fruit (type, price)
    this.fruitListPerTypes.get(type,[]).append(a)
    return a

    Fruit newApple()
    return this.newFruit('apple', price)

    ...
    getCheapestFruit = lambda type : this.fruitRepository.get(type, [null]).sort()[0]




  1. 1c80
    05.08.2019 18:27
    +6

    Да ладно, ФП совсем не плох, при должной сноровке можно написать код так, что никто кроме Вас в этом не разберется, что является несомненным плюсом в реалиях нашей жизни.


  1. OlegGelezcov
    05.08.2019 21:05

    Автору нужно обьяснять почему интерактивные девайсы никто не пишет и не будет писать на фп? Я про самолеты, корабли, автомобили и т.д.
    Огромный круг инженерных задач, с которыми фп адекватно не справится.


    Возможно в анализе данных и комбинаторных задачах оно хорошо, но это тонкий слой в промышленной разработке.


    1. defuz
      05.08.2019 23:37

      Автор сорее всего не узнает о вашем объяснении, поскольку это перевод, но вы можете объяснить мне. Мне казалось что FRP позволяет решать такие задачи и даже помогает не допускать многих ошибок, свойственных императивному подходу.

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


  1. NIKOSV
    06.08.2019 02:05

    Чистое ФП зло, так же само как чистое ООП зло, автор обильно приводит его чтобы показать превосходство ФП. Нужно искать средину и брать лучшее из двух миров. Тот же пример с фруктами на C# будет так:

    var fruits = new[]
    {
        new { Type = "apple", Price = 1.99 },
        new { Type = "orange", Price = 2.99 },
        new { Type = "grape", Price = 44.84 },
    };
    
    var applePrice = fruits.FirstOrDefault(f => f.Type == "apple")?.Price;
    
    Console.WriteLine($"apple price: {applePrice}");


    В 2 раза короче варианта на ФП (который, к слову, тоже можно было укоротить). То же самое с другими примерами.


    1. Yuuri
      06.08.2019 05:37

      А если кто-то обновит прайс, а там не будет яблок (или опечатка), прилетит apple price: null? Или NRE?


      1. mayorovp
        06.08.2019 06:37

        Так ведь потому-то и написано fruits.FirstOrDefault(f => f.Type == "apple")?.Price


        1. Yuuri
          06.08.2019 08:05

          Так я догадываюсь, что в результате applePrice = null получится. И уточняю, что увидит пользователь.


    1. PsyHaSTe
      06.08.2019 12:18

      А где тут ООП (да и ФП)? Вызвали метод для массива структур и достали одно поле. На Си будет то же самое, с разницей на то что там нет ? оператора и экстеншн метод будет записан буквально Enumerable.FirstOrDefault(fruits)


      1. mayorovp
        06.08.2019 12:29

        А где тут ООП (да и ФП)?

        А их в такой простой задаче нигде и не должно быть явно видно.


      1. NIKOSV
        06.08.2019 12:32

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


  1. Zoolander
    06.08.2019 07:55
    +2

    справедливости ради, первый пример с функциональным кодом тоже под вопросом

    делать геттер цены через прогон массива это очень плохое инженерное решение. Почему
    — у вас может не быть яблок (но цена на них нужна, для расчетов ситуаций, когда яблоки будут)
    — у вас может быть разная цена на яблоки (но функция вернет только первую)
    — этот геттер однажды может быть поставлен в функцию в другом геттере, и через N таких относительно дешевых и незаметных изменений вы получите функцию со сложностью n в степени M

    Короче, как быстрый костыль функция работает, но код надо рефакторить — слишком опасный костыль, который однажды может вылететь и выбить глаз в продакшене

    Суть ФП была в том, чтобы сделать функции безопасными, а не примкнуть к злу. Тут функция как бы чистая, но прогон массива в функции — это потенциальная засада, которую надо как-то обезвреживать — хотя бы неймингом. Из названия функции не понятно, что она гоняет массив, она выглядит как обычный геттер, а геттеры обычно дешевы и просты. Тут это не так — функция и не дешева на больших массивах, и не проста


  1. Hivemaster
    06.08.2019 08:01

    Статье явно нужен тег «троллинг» и, желательно, ещё пометку в заголовке, а то многие восприняли стёб серьёзно.


    1. PsyHaSTe
      06.08.2019 12:21

      Это очень грустно, что для такой толстый стеб кто-то физически может воспринять серьезно.


  1. Bassist067
    06.08.2019 08:49

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

    Так как ФП в продакшне применяется исчезающе мало, соответственно, никто его и не учит — получается замкнутый круг. Всё нужное и ФП-подобное мы имеем в LINQ, а остальное не прошло проверку временем и индусиками.


  1. cubit
    06.08.2019 11:58

    По-моему перегнули…


    1. PsyHaSTe
      06.08.2019 12:21

      Видимо недогнули, как чуть выше написано, некоторые серьезно восприняли. Не знаю, как, но факт.


  1. alexxisr
    06.08.2019 12:53

    Подскажите лучше — как вы дебажите программы на том же хаскеле?
    Есть ли дебаггеры и как можно расставить точки останова в цепочке функций?


    1. 0xd34df00d
      06.08.2019 15:15

      Есть ghc'шный дебаггер, но им приходится пользоваться очень изредка и только для того, чтобы понять, где в какой из библиотек вылетает экзепшон.


      А вообще, ну, не приходится дебаггера желать. Функции мелкие, цена создания функций мала, цена их запуска в REPL мала.


      1. alexxisr
        06.08.2019 15:25

        Пока мои попытки в ФП разбивались о «ghc считает что вы идиот». Вроде пишу одно, а в итоге выполняется непонятно что. И если в С можно было пройтись пошагово и понять что происходит, то тут сложна.


        1. 0xd34df00d
          06.08.2019 15:56
          +1

          Было бы любопытно взглянуть на примеры.


        1. Yuuri
          07.08.2019 14:54

          Возможно, вы по привычке пытаетесь оформлять алгоритмы очень императивно, с явными последовательностями и шагами. Считается и эмпирически демонстрируется, что чем ближе к чисто-функциональному дизайну (особенно если присыпать правильными типами), тем чаще заветное «скомпилировалось – работает». Хотя, конечно, в произвольно взятом случае приближаться так можно произвольно долго или дорого.
          Я, кстати, использовал разок GHC-шный отладчик – в 2012 в Leksah, где он был довольно неплохо прикручен. И как раз на алгоритме, наспех переписанном с императивного.



  1. zogxray
    06.08.2019 22:07

    Отличная драма, хороший сарказм.


  1. taujavarob
    08.08.2019 21:23

    ООП умрёт.

    О нём будут вспоминать как о каком-то казусе. Почему?

    Потому что за ООП не стоит никакой… математики.

    ООП есть порождение (во всяком случае восхваление и возвеличивание) банды четырёх.

    ООП основан на «здравом смысле» в наблюдение и размышлении над природой.
    ООП не основан на… математике.
    За ООП не стоит никакая математическая теория.

    «Здравый смысл» подвёл ООП.

    Даже убогий и смешной SQL, созданный НЕ для программистов, а для конечных юзеров (бухгалтеров, библиотекарей, архивистов, кладовщиков, экономистов и торговцев), к которому лет 20 назад Oracle хотел «присобачить» объекты и развить до ООП языка (попытка эта тихо провалилась — почему тихо, а не с треском? — потому что лет 20 назад ООП гордо лез во все языки и дыры и громко его «выпинывать» никто не решился), даже SQL имеет за собой… математику (реляционную алгебру)!

    ФП имеет за собой… математику. Много математики. Много.
    Как протянуть много математики в… язык? — Это проблема. Верно.

    Вспомним как понятие "множество" входило в среднюю школу. Как ввели в школьные учебники понятие «конгруэнтность», которое всё же выбросили из школьных учебников через
    несколько лет, ибо даже учителя математики НЕ понимали в чём разница между «треугольники конгруэнтны между собой» и «треугольники равны между собой».

    Язык ФП труден хотя бы тем что функция отдельно и данные отдельно.
    И программист НЕ видит места в программе где данные «поступают» в функцию.

    Он может поставить точку останова только в функции, а ему бы хотелось поставить точку останова «на входе» в функцию — а это трудновато сделать в ФП, где программисту приходится писать программу «жонглируя» функциями и только потом запускать её (программу), давая на вход (высоко высоко) начальные данные, ожидая где-то внизу (низенько низенько) конечных данных.

    Логирование, конечно, можно вставить в каждую функцию, но программист привык ставить точку останова МЕЖДУ функциями, а в ФП это затруднительно. — Там «река данных» течёт от одной функции к другой часто довольно извилистым и непрозрачным для программиста путём, что… напрягает.

    Но, наверняка, привыкнуть можно. Человек… пластичен и привыкает ко многому. ;-)

    Математика победит здравый смысл. (С)

    А ООП умрёт как недоразумение. (вангую)


    1. worldaround
      09.08.2019 01:39

      Боюсь даже спрашивать, а нет ли какой-то статистики, что 95% программистов пользуются ООП?