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

Согласование использования объективов с доступом к базе данных

Я недавно общался с объективами и считаю их очень приятными для их предполагаемого использования - врываясь в сложные структуры данных. Но одна из областей, в которых я их наиболее ценю, - это доступ к базе данных (в частности, sqlite, но я думаю, что мой вопрос обобщается для большинства БД), и все же я не вижу возможности писать линзы, которые не сильно жертвуют производительности или детализации.

Если я пишу объектив (или, я думаю, вероятно, Prism, в свете полей NULLable?) из БД в таблицу, из таблицы в строку и из строки в столбец, каждый шаг этого происходит доступ к БД, означающий, что должен быть один доступ, как минимум 4.

С другой стороны, если я намереваюсь сопоставить доступ БД 1:1 с использованием объектива/призмы, я получаю большие линзы, которые не могут быть разбиты на более мелкие кусочки, когда я хочу просто увидеть какие столбцы находятся в таблице и т.д.

Имеет ли смысл вообще использовать объективы с БД, и если я не вижу очевидного способа избежать дублирования работы, чтобы избежать ненужного доступа к БД?

4b9b3361

Ответ 1

Мне кажется, что вы хотите использовать объектив таким же образом, как linq IQueryable в С#.

Например, если у вас есть типы:

data Project = Project {
  _projectId :: Int
  , _projectPriority :: Int
  , _projectName :: String
  , _projectTasks :: [Task]
   } deriving (Show)

data Task = Task {
  _taskId :: Int
  , _taskName :: String
  , _taskEstimate :: Int
  } deriving (Show)


makeLenses ''Project
makeLenses ''Task

И база данных:

create table projects ( id, name, priority);
create table tasks (id, name, estimate, projectId);

insert into projects values (1, 'proj', 1), (2, 'another proj', 2);

insert into tasks values (1, 'task1', 30, 1), (2, 'another', 40, 1),
                        (3, 'task3', 20, 2), (4, 'more', 80, 2);

Если вы хотите получить список имен задач из проектов с приоритетом более 1, было бы неплохо, если бы вы могли использовать:

highPriorityTasks :: IO [String]
highPriorityTasks = db ^.. projects . filtered (\p -> p ^. projectPriority > 1 )
                    . projectTasks . traverse . taskName

И запросите эту базу данных с помощью запроса:

select t.name from projects as p 
inner join tasks as t on t.projectId = p.id 
where p.priority > 1;

К сожалению, это невозможно в библиотеке. В принципе, чтобы быть эффективной базой данных, вы (обычно) должны делать все в одном запросе. Было бы неприемлемо сделать это:

select * from projects where priority > 1;
for each project:
   select name from tasks where projectId = <project>.id    

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

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


Связанный с этим вопрос - если это вообще возможно. Для этого нам нужно создать подъязык для числовых и строковых операторов, а композиция будет отслеживать, что сделано. Это возможно. Например, вы можете создать тип Num, который записывает все, что с ним делается:

data TrackedNum = TrackedNum :-: TrackedNum
                | TrackedNum :+: TrackedNum
                | TrackedNum :*: TrackedNum
                | Abs TrackedNum
                | Signum TrackedNum
                | Value Integer
  deriving (Show)

instance Num TrackedNum where
  a + b = a :+: b
  a * b = a :*: b
  a - b = a :-: b
  abs a = Abs a
  signum a = Signum a
  fromInteger = Value

t :: TrackedNum
t = 3 + 4 * 2 - abs (-34)

> t 
(Value 3 :+: (Value 4 :*: Value 2)) :-: Abs (Value 0 :-: Value 34)

Повторите процесс для логических операторов (для этого вам понадобится новый класс типа), операторов списка и состава функций (т.е. класс Category), и вы должны иметь возможность сделать функцию "белого ящика", которая затем можно использовать для создания эффективного SQL-запроса. Это не тривиальное начинание, хотя!