Функциональное программирование: как обрабатывать сложные данные без раздутых функций? - PullRequest
0 голосов
/ 24 февраля 2019

Допустим, в вашей программе вы определили сложный объект автомобиля.Этот объект содержит очень длинный список предопределенных пар ключ-значение (wheels, engine, color, lights, amountDoors и т. Д.), Каждая из которых является либо номером детали, либо списком номера детали, либоконкретное значение.

//** PSEUDO CODE:
var inputCar = { 
  "engine": "engine-123", 
  "lights": ["light-type-a", "light-type-b"], 
  "amountDoors": 6,
  etc ... lets assume a lot more properties
}

Предположим также, что этот объект уже настолько прост, насколько это возможно, и его нельзя уменьшить.

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

var settingsEngine = [
  { "id": "engine-123", weight: 400, price: 11000, numberScrews: 120, etc ... },
  { "id": "engine-124" etc ... }
]

Со всеми настройками, связанными в главном объекте настроек

settings = { settingsEngine, settingsWheel, settingsLight ... }

Теперь у нас есть различные функции, которые должны принимать Car и возвращает определенные значения об этом, такие как вес, цена или количество винтов.

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

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

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

Одна реализация может выглядеть так:

var detailedCar = getDetailedCar(inputCar, settings);

var priceCar = getPriceCar(detailedCar);
var weightCar = getWeightCar(detailedCar);

Таким образом, часть работыдолжно быть сделано только один раз.Но в этом примере detailedCar будет еще более сложным объектом, чем исходный объект ввода, и поэтому будет параметром getPriceCar, что также затрудняет его тестирование, потому что нам всегда нужен полный объект car длякаждый контрольный пример.Поэтому я не уверен, что это хороший подход.

Вопрос

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

Как результат может быть легко тестируемым модулем при сложном, взаимозависимом вводе?

Ответы [ 2 ]

0 голосов
/ 26 февраля 2019

Я бы предложил здесь немного другой подход.

Поскольку ваш вопрос касается чисто функционального программирования, я бы сказал, что вам нужна функция более высокого порядка, отвечающая за осветление необходимых фрагментов сложной структуры данных и затенение ненужных: readComplexDataStructure :: (ComplexDataStructure -> a) -> (a -> b) -> ComplexDataStructure -> b, где a представляет данные, которые необходимо извлечь из некоторого экземпляра ComplexDataStructure, а b - результат вычисления.

Обратите внимание, насколько она близка к монаде Reader, хотя я не рекомендовал бы использовать ее прямо сейчас, если сложность кода не оправдывает такое решение.

PS Это весы.Вам просто нужна функция для получения n-uple из (ComplexDataStructure -> a) проекций.В качестве примера рассмотрим следующую подпись: double :: (ComplextDataStructure -> a) -> (ComplexDataStructure -> b) -> ( (a, b) -> c) -> ComplexDataStructure -> c.Ваш код не станет «раздутым», если вы поддерживаете только соответствующие прогнозы, а все остальное достаточно сочувственно и информативно.

0 голосов
/ 26 февраля 2019

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

В вашем примере вам нужна «винтовая проекция», которая берет данные, которые описываютавтомобиль и подходит с винтами, которые необходимы.Следовательно, мы определяем функцию:

screwProjection(vehicle, settings) -> [(screwType, screwCount)]

, которая принимает транспортное средство и настройки, описывающие компоненты, и предлагает винты, которые составляют транспортное средство.Вы также можете иметь дополнительную проекцию, которая просто суммирует второй элемент в кортеже, если вас не волнует screwType.

Теперь, чтобы разложить screwProjection(), вам понадобится что-то, что повторяется по каждому компонентутранспортного средства, и ломает его далее по мере необходимости.Например, первый шаг в вашем примере - получить engine и найти настройки, соответствующие двигателям, и отфильтровать по типу двигателя, а затем отфильтровать полученный результат по полю для винтов:

partProjection(part, settings) -> [(partType, partCount)]

Итак, screwProjection() выглядит следующим образом:

vehicle.parts
  .flatMap( part -> partProjection( part, settings ) ) // note 1
  .filter( (partType, partCount) -> partType == 'screw' )
  .map( (partType, partCount) -> partCount )
  .sum()

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

Этот общий подход перечисление => проекция => фильтр => уменьшение лежит в основе многих парадигм функционального программирования.

...