2010-11-06 1 views
41

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

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

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

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

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

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

ответ

52

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

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

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

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


Теперь, вы можете быть интересно, что является соответствующий формализм для объектно-ориентированного программирования? Ну, вот линия удара: Во многом это более функционально, чем ML-стиль FP! Я вернусь к one of my favorite papers за то, что, по-видимому, является ключевым отличием: структурированные данные, такие как алгебраические типы данных в языках стиля ML, обеспечивают конкретное представление данных и возможность определять на нем операции; объекты обеспечивают абстракцию черного ящика над поведением и возможностью легкой замены компонентов.

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

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

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


EDIT: Несколько заключительных вещей, которые я забыл упомянуть выше.

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

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

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

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

+4

+1, отличный ответ! – missingfaktor

+0

Спасибо за впечатляющий ответ (и ссылки). У меня, вероятно, последующие вопросы после того, как я прочитаю материал. –

6

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

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

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

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

  • Есть еще чисто функциональным (состояние представляет собой список символов и не мутирует)
  • Это объектно-ориентированный - единственная необычная вещь, что все методы возвращают новый экземпляр «мир»
+2

Я не очень уверен в этом, поэтому позвольте мне сделать несколько замечаний. Организация и структурирование программ могут выполняться пространствами имен или модулями в F #. Преимущество завершения точки встречается с помощью 'Seq.map' вместо' fun x -> x.map' в конвейерах. Вопрос о том, должна ли функция, возвращающая новый экземпляр, быть частью класса, а не просто «пространство имен» модуля. Разумеется, совместимость с C# /. Net - хорошая причина. –

8

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

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

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 = ... 
+0

Я не знаю Haskell, так что вы можете объяснить большую разницу между диспетчером динамических методов и ООП на основе стандартного класса? –

+0

«Динамическая отправка метода» Я имею в виду стратегию выбора правильного метода для вызова во время выполнения. Из двух стратегий, показанных здесь, стратегия подтипирования похожа на то, как виртуальные методы работают под капотом в ООП (с таблицами виртуальных методов). Стратегия параметрического полиморфизма не является объектно-ориентированной и более часто используется в Haskell. – Heatsink

+3

Маловероятная терминология: «параметрический полиморфизм» означает универсальную количественную оценку по непрозрачным типам, что языки OO часто называют дженериками, и это то, что по умолчанию имеет Haskell. Перегрузка по типу, либо время компиляции, либо время выполнения, называется «ad-hoc polymorphism» и является классом классов Haskell. –

Смежные вопросы