2011-12-16 4 views
4

Setup:Обновление элементов нескольких коллекций с динамическими функциями

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

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

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

Вопросы:

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

Какой комбинатор я использую для создания такой транзакции?

Идеи:

  1. Поместите все коллекции в одну огромную структуру и передает эту структуру вокруг.
  2. Используйте состояние монады для выполнения в основном одно и то же
  3. Используйте IORef (или один из его более мощных кузенов как MVar) и создать действие ввода-вывода
  4. Использование функционального Реактивное программирует Framework

1 и 2, похоже, что они несут много багажа, особенно если я предполагаю, что в конце концов некоторые коллекции будут внесены в базу данных. (Darn IO Monad)

3, похоже, хорошо работает, но начинает много напоминать воссоздание ООП. Я также не уверен, на каком уровне использовать IORef. (Например IORef (Collection Obj) или Collection (IORef Obj) или data Obj {field::IORef(Type)})

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


Пример

У меня есть веб-витрину. Я держу коллекцию продуктов с (помимо прочего) количеством на складе и ценой. У меня также есть коллекция пользователей, имеющих кредит в магазине.

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

Это означает, что я получаю следующее:

checkout :: Cart -> ProductsCol -> UserCol -> (ProductsCol, UserCol) 

Но тогда жизнь становится более сложным, и мне нужно, чтобы иметь дело с налогами:

checkout :: Cart -> ProductsCol -> UserCol -> TaxCol 
      -> (ProductsCol, UserCol, TaxCol) 

И тогда я должен быть уверен, чтобы добавить заказ в очереди доставки:

checkout :: Cart 
     -> ProductsCol 
     -> UserCol 
     -> TaxCol 
     -> ShipList 
     -> (ProductsCol, UserCol, TaxCol, ShipList) 

И так далее ...

Что я хотел бы написать что-то вроде

checkout = updateStockAmount <*> applyUserCredit <*> payTaxes <*> shipProducts 
applyUserCredit = debitUser <*> creditBalanceSheet 

но тип-шашка бы пойти апоплексический на меня. Как структурировать это хранилище таким образом, чтобы функции checkout или applyUserCredit оставались модульными и абстрактными? Я не могу быть единственным, у кого есть эта проблема, не так ли?

+1

Просто для того, чтобы быть ясным: у вас есть множество функций с типами, похожими на 'A -> A', для разных типов' A'; вы хотите объединить их в составные действия, обновив какое-то общее состояние; и вы не хотите, чтобы каждая функция знала все остальное, что находится в общем состоянии? –

+0

C.A.McCann: Правильно; хотя некоторые из них, возможно, должны быть получены из чего-то типа «B -> A -> A» путем их каррирования. –

ответ

6

Хорошо, давайте сломаем это.

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

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

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

Мы можем вывести несколько необходимой особенность простой конструкции:

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

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

  • Единственными операциями, необходимыми для общего состояния в целом, являются взаимодействие с промежуточным слоем и все, что может потребоваться для поддержания внесенных изменений.

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

В частности, это, по существу, объединяет ваши идеи 2 и 3. Здесь присутствует присущий монадический контекст, и интерфейс класса типа предложил позволит несколько подходов, такие как:

  • Сделайте общегосударственный тип записи, хранить его в State монаде, и использовать линзы, чтобы обеспечить уровень интерфейса.

  • Сделать общий доступ к типу записи, содержащему что-то вроде STRef для каждой части, и объединить селекторы полей с действиями обновления monad ST для обеспечения уровня интерфейса.

  • Сделать общий доступ к коллективу TChan с отдельными потоками для их чтения/записи в соответствующих случаях для асинхронного общения с внешним хранилищем данных.

Или любое количество других вариантов.

3

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

{-# LANGUAGE TemplateHaskell #-} 
import Data.Lens.Template 
import Data.Lens.Common 
import Data.List (foldl') 
import Data.Map ((!), Map, adjust, fromList) 

type User = String 
type Item = String 
type Money = Int -- money in pennies 

type Prices = Map Item Money 
type Cart = (User, [(Item,Int)]) 
type ProductsCol = Map Item Int 
type UserCol = Map User Money 

data StoreState = Store { _stock :: ProductsCol 
         , _users :: UserCol 
         , msrp :: Prices } 
        deriving Show 
makeLens ''StoreState 

updateProducts :: Cart -> ProductsCol -> ProductsCol 
updateProducts (_,c) = flip (foldl' destock) c 
    where destock p' (item,count) = adjust (subtract count) item p' 

updateUsers :: Cart -> Prices -> UserCol -> UserCol 
updateUsers (name,c) p = adjust (subtract (sum prices)) name 
    where prices = map (\(itemName, itemCount) -> (p ! itemName) * itemCount) c 


checkout :: Cart -> StoreState -> StoreState 
checkout c s = (users ^%= updateUsers c (msrp s)) 
      . (stock ^%= updateProducts c) 
      $ s 

test = checkout cart store 
    where cart = ("Bob", [("Apples", 2), ("Bananas", 6)]) 
     store = Store initialStock initialUsers prices 
     initialStock = fromList 
         [("Apples", 20), ("Bananas", 10), ("Lambdas", 1000)] 
     initialUsers = fromList [("Bob", 20000), ("Mary", 40000)] 
     prices = fromList [("Apples", 100), ("Bananas", 50), ("Lambdas", 0)] 
Смежные вопросы