Обработка возрастающих Изменений Моделирования данных в Функциональном программировании

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

Если я удачлив, что не должен даже программно добавлять новый код "поведения" (функции, методы). Вместо этого я могу описание добавлять проверку и даже опции UI путем аннотирования свойства (Java).

В Функциональном программировании кажется, что добавление новых свойств данных требует большого количества изменений кода из-за сопоставления с образцом и конструкторов данных (Haskell, ML).

Как я минимизирую эту проблему?

Это, кажется, распознанная проблема, как Xavier Leroy заявляет приятно на странице 24 "Объектов и Классов по сравнению с Модулями" - Для суммирования для тех, которые не имеют средства просмотра PostScript, она в основном говорит, что языки FP лучше, чем языки ООП для добавления нового поведения по объектам данных, но языкам ООП лучше для добавления новых объектов/свойств данных.

Там какой-либо шаблон разработки используется на языках FP, чтобы помочь смягчить эту проблему?

Я прочитал рекомендацию Phillip Wadler использования Монад для помощи этой проблеме модульного принципа, но я не уверен, что понимаю как?

17
задан Adam Gent 11 May 2010 в 02:03
поделиться

4 ответа

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

Во-первых, краткий обзор «проблемы выражения».Рассмотрим следующий алгебраический тип данных:

data Expr a = Lit a | Sum (Expr a) (Expr a)

exprEval (Lit x) = x
exprEval (Sum x y) = exprEval x + exprEval y

exprShow (Lit x) = show x
exprShow (Sum x y) = unwords ["(", exprShow x, " + ", exprShow y, ")"]

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

exprMap f (Lit x) = Lit (f x)
exprMap f (Sum x y) = Sum (exprMap f x) (exprMap f y)

Легко! Мы можем писать функции весь день, не беспокоясь! Алгебраические типы данных - это здорово!

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

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

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

data Actions a = Actions {
    actEval :: a,
    actMap  :: (a -> a) -> Actions a }

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

mkLit x = Actions x (\f -> mkLit (f x))

mkSum x y = Actions 
    (actEval x + actEval y) 
    (\f -> mkSum (actMap x f) (actMap y f))

Можем ли мы теперь проще добавить умножение? Конечно может!

mkProd x y = Actions 
    (actEval x * actEval y) 
    (\f -> mkProd (actMap x f) (actMap y f))

Ой, но подождите - мы забыли добавить действие actShow ранее, давайте добавим это, мы просто ... эээ, хорошо.

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

expr1plus1 = Sum (Lit 1) (Lit 1)
action1plus1 = mkSum (mkLit 1) (mkLit 1)
action1times1 = mkProd (mkLit 1) (mkLit 1)

Практически одинаково, если вы не расширяете их.

Интересное примечание:учтите, что в стиле "действия" фактические значения в выражении полностью скрыты - поле actEval только обещает дать нам что-то правильного типа, как оно это обеспечивает это его собственный бизнес. Благодаря ленивому вычислению содержимое поля может быть даже сложным вычислением, выполняемым только по запросу. Действия значение полностью непрозрачно для внешнего контроля, представляя внешнему миру только определенные действия.

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

22
ответ дан 30 November 2019 в 12:00
поделиться

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

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

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

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

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

6
ответ дан 30 November 2019 в 12:00
поделиться

Этот компромисс известен в литературе по теории языков программирования как проблема выражения :

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

Решения были предложены, но я их не изучал. (Много обсуждений на Lambda The Ultimate .)

4
ответ дан 30 November 2019 в 12:00
поделиться

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

3
ответ дан 30 November 2019 в 12:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: