Подтвердить что ты не робот

Достижение полиморфизма в функциональном программировании

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

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

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

В FP я немного застрял между:

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

Каковы рекомендуемые функциональные подходы для такого рода ситуаций? Существуют ли другие хорошие альтернативы?

4b9b3361

Ответ 1

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

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

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

Полиморфизм в ООП во многом похож на "экзистенциальную квантификацию" в логике - полиморфное значение имеет НЕКОТОРЫЙ тип времени выполнения, но вы не знаете, что это такое. Во многих языках функционального программирования полиморфизм больше похож на "универсальную квантификацию" - полиморфное значение может быть создано для ЛЮБЫХ совместимых типов, которые хочет пользователь. Они - две стороны одной и той же монеты (в частности, они меняются местами в зависимости от того, смотрите ли вы на функцию из "внутри" или "снаружи" ), но она оказывается чрезвычайно сложной при разработке язык, чтобы "сделать монету справедливой", особенно при наличии других языковых особенностей, таких как подтипирование или более высокий тип полиморфизма (полиморфизм над полиморфными типами).

Если это поможет, вы можете подумать о полиморфизме в функциональных языках как нечто очень похожее на "generics" на С# или на Java, потому что именно такой тип полиморфизма, который, например, ML и Haskell, благоприятствует.

Ответ 2

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

data Foo = Bar | Baz

instance Show Foo where
    show Bar = 'bar'
    show Baz = 'baz'

main = putStrLn $ show Bar

Функция show :: (Show a) => a -> String определяется для каждого типа данных, в котором экземпляр typeclass Show. Компилятор найдет для вас правильную функцию, в зависимости от типа.

Это позволяет более подробно определять функции, например:

compare a b = a < b

будет работать с любым типом класса Ord. Это не совсем похоже на OOP, но вы даже можете наследовать такие классные классы:

class (Show a) => Combinator a where
    combine :: a -> a -> String

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

Это не полно, и, насколько я знаю, многие языки FP не содержат классов типов. OCaml не делает этого, он подталкивает его к своей части ООП. И у Схемы нет никаких типов. Но в Haskell это мощный способ добиться своего рода полиморфизма в пределах.

Чтобы идти еще дальше, более новые расширения стандарта 2010 допускают типы семейств типов и т.д.

Надеюсь, это немного помогло вам.

Ответ 3

Кто сказал

определение чистых функций отдельно от данных

- лучшая практика?

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

И полиморфные объекты строятся путем замены некоторых из этих функций объекта различными функциями с одной и той же сигнатурой.

Если вы хотите узнать больше о том, как реализовать объекты на функциональном языке (например, Scheme), ознакомьтесь с этой книгой:

Абельсон/Суссман: "Структура и взаимодействие компьютерных программ"

Ответ 4

Майк, оба ваши подходы вполне приемлемы, и все плюсы и минусы каждого из них обсуждаются, как говорит Док Браун, в главе 2 SICP. Первый страдает от наличия таблицы большого типа, которая должна поддерживаться. Вторая - это просто традиционные таблицы однофакторной полиморфизма/виртуальных функций.

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

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

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

В clojure у вас действительно есть предварительно построенная система объектов/диспетчеризации, встроенная в multimethods, и одним из ее преимуществ перед традиционным подходом является то, что он может отправлять типы всех аргументов. Вы можете (по-видимому) также использовать систему heirarchy, чтобы дать вам наследования, как функции, хотя я никогда не использовал его, поэтому вы должны взять этот cum grano salis.

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

Это то, что вы предлагаете выше.

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

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

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