Объектно-ориентированное программирование в чисто функциональном контексте программирования? - PullRequest
42 голосов
/ 06 ноября 2010

Есть ли какие-либо преимущества использования объектно-ориентированного программирования (ООП) в контексте функционального программирования (ФП)?

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

Это не исключает необходимости использования пространств имен некоторой формы, которая ортогональна ООП. Также не рекомендуется использование структур данных. Фактически, реальное использование языков FP сильно зависит от структур данных. Если вы посмотрите на стек F #, реализованный в F Sharp Программирование / Расширенные структуры данных , вы обнаружите, что он не объектно-ориентирован.

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

Практическая причина может заключаться в возможности взаимодействия с кодом ООП, почти так же, как F # работает с .NET . Однако, кроме этого, есть ли причины? И каков опыт в мире Haskell, где программирование - это более чистый FP?

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

Ответы [ 3 ]

53 голосов
/ 06 ноября 2010

Отключение, которое вы видите, не FP или OOP. В основном речь идет об неизменяемости и математических формализмах против изменчивости и неформальных подходов.

Во-первых, давайте избавимся от проблемы изменчивости: у вас может быть FP с изменяемостью и ООП с неизменяемостью. Еще более функциональный, чем ты, Haskell позволяет вам играть с изменяемыми данными все, что вы хотите, вам просто нужно четко указать, что является изменчивым и порядок, в котором все происходит; и кроме соображений эффективности, почти любой изменяемый объект может создавать и возвращать новый, «обновленный» экземпляр вместо изменения своего собственного внутреннего состояния.

Большей проблемой здесь являются математические формализмы, в частности интенсивное использование алгебраических типов данных на языке, мало удаленном от лямбда-исчисления. Вы пометили это с помощью Haskell и F #, но понимаете, что это только половина вселенной функционального программирования; Семейство Lisp имеет совершенно другой, гораздо более свободный характер по сравнению с языками в стиле ML. Большинство широко используемых сегодня ОО-систем носят очень неформальный характер - формализмы существуют для ОО, но они явно не вызываются так, как формализмы FP в языках стиля ML.

Многие из очевидных конфликтов просто исчезают, если вы удалите несоответствие формализма. Хотите построить гибкую, динамическую, специальную ОО-систему поверх Lisp? Давай, это будет работать просто отлично. Хотите добавить формализованную неизменяемую ОО-систему к языку в стиле ML? Нет проблем, просто не ожидайте, что он будет хорошо работать с .NET или Java.


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

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

Причина, по которой я говорю, что ООП является более функционально-ориентированной, заключается в том, что сами функции представляют собой форму поведенческой абстракции. Фактически, вы можете смоделировать структуру в стиле OO в чем-то наподобие Haskell, используя записи, содержащие набор функций в качестве объектов, позволяя типу записи быть своего рода «интерфейс» или «абстрактный базовый класс» и заменяя функции, создающие записи. конструкторы классов. Таким образом, в этом смысле ОО-языки используют функции высшего порядка гораздо чаще, чем, скажем, Хаскелл.

В качестве примера такого дизайна, который на самом деле очень хорошо используется в Haskell, прочитайте исходный код пакета graphics-drawingcombinators , в частности, о том, как он использует непрозрачный тип записи, содержащий функционирует и объединяет вещи только с точки зрения их поведения.


РЕДАКТИРОВАТЬ: Несколько последних вещей, которые я забыл упомянуть выше.

Если ОО действительно широко использует функции более высокого порядка, на первый взгляд может показаться, что он вполне естественно вписывается в функциональный язык, такой как Haskell. К сожалению, это не совсем так. Это правда, что объекты , как я их описал (см. Статью, упомянутую в ссылке на LtU), подходят просто отлично. на самом деле результатом является более чистый стиль ОО, чем у большинства языков ОО, поскольку «закрытые члены» представлены значениями, скрытыми замыканием, используемым для создания «объекта», и недоступны ни для чего, кроме одного конкретного экземпляра. Вы не становитесь намного более приватным, чем это!

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

Кроме того, большинство ОО-языков со статическими системами типов также создают полный хэш подтипов, поскольку они слишком слабы с подстановкой и не обеспечивают надлежащей поддержки дисперсии в сигнатурах методов. На самом деле, я думаю, что единственный полноценный ОО-язык, который не испортил его полностью, по крайней мере, я знаю, это Scala (F #, казалось, слишком много уступал .NET, хотя, по крайней мере, я не думаю, что он делает любые новые ошибки). У меня ограниченный опыт работы со многими такими языками, поэтому я определенно могу ошибаться.

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

8 голосов
/ 06 ноября 2010

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

Инкапсуляция или «скрытие данных» часто выполняется посредством замыканий функций или экзистенциальных типов, а не частных членов,Например, вот тип данных генератора случайных чисел с инкапсулированным состоянием.ГСЧ содержит метод для генерации значений и начальное значение.Поскольку тип 'seed' инкапсулирован, единственное, что вы можете с ним сделать, это передать его методу.

data RNG a where RNG :: (seed -> (a, seed)) -> seed -> RNG a

Динамическая диспетчеризация метода в контексте параметрического полиморфизма или "общего программирования" обеспечиваетсяклассы типов (которые не являются классами OO).Класс типов похож на таблицу виртуальных методов класса OO.Тем не менее, нет данных, скрывающих.Классы типов не «принадлежат» к типу данных, как методы класса.

data Coordinate = C Int Int

instance Eq Coordinate where C a b == C d e = a == b && d == e

Динамическая диспетчеризация методов в контексте полиморфизма подтипов или «подклассов» - это почти перевод шаблона класса в Haskellиспользуя записи и функции.

-- An "abstract base class" with two "virtual methods"
data Object =
  Object
  { draw :: Image -> IO ()
  , translate :: Coord -> Object
  }

-- A "subclass constructor"
circle center radius = Object draw_circle translate_circle
  where
    -- the "subclass methods"
    translate_circle center radius offset = circle (center + offset) radius
    draw_circle center radius image = ...
6 голосов
/ 06 ноября 2010

Я думаю, что есть несколько способов понять, что означает ООП.Для меня это не инкапсуляция изменяемого состояния , а больше об организации и структурировании программ.Этот аспект ООП может прекрасно использоваться в сочетании с концепциями FP.

Я считаю, что смешивание двух концепций в F # - очень полезный подход - вы можете связать состояние immutable с работающими операциямив этом состоянии.Вы получите приятные возможности «точечного» завершения для идентификаторов, возможность простого использования кода F # из C # и т. Д., Но вы все равно сможете сделать свой код совершенно функциональным.Например, вы можете написать что-то вроде:

type GameWorld(characters) = 
  let calculateSomething character = 
    // ...
  member x.Tick() = 
    let newCharacters = characters |> Seq.map calculateSomething
    GameWorld(newCharacters)

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

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