在我看来,您想以类似于IQueryable
c# 中的 linq 的方式使用镜头。
例如,如果您有以下类型:
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
函数中提取数据来帮助构建查询。也不可能从完整的表达式中提取子透镜。所以这是不可能使用镜头库的。
目前你能得到的最好的方法是使用一组函数查询数据库,并使用lens查询结果数据。有关此示例,请参阅有关 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 查询。不过,这可不是一件小事!