Является ли функциональное программирование надмножеством объектно-ориентированного?

26

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

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

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

Я знаю, что Haskell может удовлетворить требование «интерфейсов», но опять же не уверен, является ли его метод функциональным? Я предполагаю, что тот факт, что у функторов есть математическая основа, можно сказать, что это определенное встроенное ожидание функционала, возможно?

Пожалуйста, опишите, как вы считаете, функционал выполняет или не выполняет 4 принципа ООП.

Редактировать: Я прекрасно понимаю разницу между функциональной парадигмой и объектно-ориентированной парадигмой и понимаю, что в наши дни существует множество языков с множественной парадигмой, которые могут выполнять обе задачи. Я на самом деле просто ищу определения того, как прямой fp (думаю, пурист, как haskell) может выполнить любую из 4 перечисленных вещей, или почему он не может сделать ни одну из них. то есть «Инкапсуляция может быть сделана с помощью замыканий» (или, если я ошибаюсь в этом убеждении, пожалуйста, укажите причину).

Джимми Хоффа
источник
7
Эти 4 принципа не "делают" ООП. ООП просто «решает» их, используя классы, классы hiearchy и их экземпляры. Но я тоже хотел бы получить ответ, если есть способы достичь этого в функциональном программировании.
Эйфорическая
2
@Euphoric В зависимости от определения, он делает ООП.
Конрад Рудольф
2
@KonradRudolph Я знаю, что многие люди утверждают, что эти вещи и преимущества, которые они приносят как уникальные свойства ООП. Предполагая, что «полиморфизм» означает «полиморфизм подтипа», я могу согласиться с тем, что последние два являются неотъемлемой частью ООП. Но мне еще предстоит встретить полезное определение инкапсуляции и абстракции, которое исключает явно не ООП подходы. Вы можете просто скрыть детали и ограничить доступ к ним даже в Haskell. И у Haskell также есть специальный полиморфизм, только не полиморфизм подтипа - вопрос, имеет ли значение бит подтипа?
1
@KonradRudolph Это не делает его более приемлемым. Во всяком случае, это стимул сделать шаг вперед и дать тем, кто распространяет его, повод пересмотреть его.
1
Абстракция присуща любому программированию, по крайней мере, любому программированию за пределами исходного машинного кода. Инкапсуляция существовала задолго до ООП и является неотъемлемой частью функционального программирования. Функциональный язык не обязан включать явный синтаксис для наследования или полиморфизма. Я предполагаю, что это добавляет «нет».
Sdenham

Ответы:

44

Функциональное программирование не на уровень выше ООП; это совершенно другая парадигма. Можно сделать ООП в функциональном стиле (F # был написан именно для этой цели), а на другом конце спектра у вас есть такие вещи, как Haskell, который явно отвергает принципы ориентации объекта.

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

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

Мейсон Уилер
источник
1
Ну, наследование (в тех исключительно редких случаях, когда это необходимо) достижимо по составу, и оно чище, чем наследование на уровне типов. Полиморфизм является естественным, особенно при наличии полиморфных типов. Но, конечно, я согласен, что FP не имеет ничего общего с ООП и его принципами.
SK-logic
Его всегда можно подделать - вы можете создавать объекты на любом языке, который вы выберете. Я согласен со всем остальным, хотя :)
Элиот Болл
5
Я не думаю, что термин «побочные эффекты» был придуман (или в основном используется) функциональными программистами.
sepp2k
4
@ sepp2k: Он не сказал, что они изобрели этот термин, просто они используют его примерно в том же тоне, который обычно используют для обозначения детей, которые отказываются выходить из своего газона.
Aaronaught
16
@ Aaronaught Меня беспокоят не дети на моей лужайке, а их кровавые побочные эффекты! Если бы они просто перестали мутировать по всей моей лужайке, я бы не стал возражать против них.
Джимми Хоффа
10

Я считаю следующую интуицию полезной для сравнения ООП и ФП.

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

  1. Некоторая операция, которая выполняется,
  2. некоторые входные аргументы для операции,
  3. некоторые фиксированные данные / параметры, которые могут влиять на определение операции,
  4. какое-то значение результата и
  5. возможно побочный эффект.

В ООП это фиксируется

  1. Метод, который выполняется,
  2. входные аргументы метода,
  3. объект, для которого вызывается метод, содержащий некоторые локальные данные в форме переменных-членов,
  4. возвращаемое значение метода (возможно, void),
  5. побочные эффекты метода.

В FP это фиксируется

  1. Закрытие, которое выполняется,
  2. входные аргументы замыкания,
  3. захваченные переменные замыкания,
  4. возвращаемое значение замыкания,
  5. возможные побочные эффекты замыкания (в чистых языках, таких как Haskell, это происходит очень контролируемым образом).

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

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

Джорджио
источник
8

Это зависит от того, кого вы просите дать определение ООП. Спросите пять человек, и вы, вероятно, получите шесть определений. Википедия говорит :

Попытки найти согласованное определение или теорию, лежащую в основе объектов, оказались не очень успешными

Поэтому, когда кто-то дает очень точный ответ, принимайте его с недоверием.

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

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

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(Конечно, JavaScript допускает изменение значений, что недопустимо в чисто функциональном программировании, но при этом не требуется в строгом определении ООП.)

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

Конрад Рудольф
источник
1
Я чувствую, что может иметь смысл продумывать, где должна быть проведена линия, когда я переключаю парадигмы. Если говорят, что в fp нет способа достичь субтипального полиморфизма, то я не буду пытаться использовать fp при моделировании чего-то, что хорошо ему подходит. Однако, если это возможно, я могу потратить время на то, чтобы найти хороший способ сделать это (хотя хороший способ не может быть возможен), работая интенсивно в пространстве fp, но желая субтипального полиморфизма в нескольких нишевых пространствах. Очевидно, что если большая часть системы подойдет для этого, лучше использовать ООП.
Джимми Хоффа
6

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

  • функциональное программирование - это программирование с функциями первого класса

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

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

FP и OO - два инструмента построения абстракций. У каждого из них есть свои сильные и слабые стороны (например, у них разные предпочтительные направления расширения в задаче выражения), но ни один из них не является более мощным, чем другой. Вы можете построить OO-систему на ядре FP (CLOS - одна из таких систем). Вы можете использовать OO-фреймворк для получения функций первого класса (например, смотрите, как лямбда-функции определены в C ++ 11).

AProgrammer
источник
Я думаю, что вы имеете в виду «функции первого класса», а не «функции первого порядка».
dan_waterworth
Errr ... C ++ 11 лямбды - едва ли первоклассные функции: каждая лямбда имеет свой собственный специальный тип (для всех практических целей, анонимная структура), несовместимый с собственным типом указателя на функцию. И std::function, к которому могут быть назначены как указатели функций, так и лямбда-выражения, является явно общим, а не объектно-ориентированным. Это неудивительно, потому что ограниченная марка полиморфизма объектной ориентации (полиморфизм подтипа) строго менее мощна, чем параметрический полиморфизм (даже Хиндли-Милнер, не говоря уже о полной Системе F-омега).
Пион
У меня нет большого опыта работы с пуристическими функциональными языками, но если вы можете определить классы с одним статическим методом внутри замыканий и передать их в различные контексты, я бы сказал, что вы (возможно, неловко) по крайней мере на полпути там на опциях функционального стиля. Есть много способов обойти строгие параметры в большинстве языков.
Эрик Реппен
2

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

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

m3th0dman
источник
3
Неизменяемые классы не создают функций более высокого порядка, не выполняют списки и не замыкаются. Fp не является подмножеством.
Джимми Хоффа
1
@Jimmy Hoffa: Вы можете легко смоделировать более высокую функцию oreder, создав класс, который имеет единственный метод, который принимает или несколько объектов аналогичного типа, а также возвращает объект такого же типа (тип, у которого есть метод и нет полей) , Сравнение списков не относится к языку программирования, а не к парадигме (Smalltalk поддерживает его и является ООП). Замыкания присутствуют в C # и также будут вставлены в Java.
m3th0dman
да, у C # есть замыкания, но это потому, что это мультипарадигма, замыкания были добавлены вместе с другими частями fp в C # (за что я вечно благодарен), но их присутствие в oop-языке не делает их упертыми. Хорошая мысль о функции более высокого порядка, хотя инкапсуляция метода в классе допускает такое же поведение.
Джимми Хоффа
2
Да, но если вы используете замыкания для изменения состояния, вы все равно будете программировать в функциональной парадигме? Дело в том, что функциональная парадигма - это отсутствие состояния, а не функции высокого порядка, рекурсия или замыкания.
m3th0dman
Интересная мысль по поводу определения fp. Мне нужно больше об этом подумать, спасибо, что поделились своими наблюдениями.
Джимми Хоффа
2

Ответ:

В Википедии есть отличная статья о функциональном программировании с некоторыми примерами, которые вы просите. @ Конрад Рудольф уже предоставил ссылку на статью ООП .

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

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

Все более Тангенциальный Рамблинг:

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

Java является ОО-языком, но в версии 7 добавлена ​​функция «попробуй с ресурсами», которая может использоваться для имитации своего рода замыкания. Здесь он имитирует обновление локальной переменной «а» в середине другой функции, не делая ее видимой для этой функции. В этом случае первая половина другой функции - это конструктор ClosureTry (), а вторая половина - метод close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Выход:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

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

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

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

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

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

GlenPeterson
источник