Ответ 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-запроса. Это не тривиальное начинание, хотя!