将镜头使用与数据库访问协调

时间:2014-06-27 23:23:45

标签: haskell lens lenses

我最近一直在玩镜头,发现它们的用途非常令人愉快 - 挖掘复杂的数据结构。但我最欣赏的一个领域是数据库访问(特别是sqlite,但我认为我的问题可以推广到大多数数据库),但是我无法找到任何方法来编写不需要的镜头# 39;严重牺牲性能或粒度。

如果我从DB到表,从表到行,从行到列写镜头(或者我认为可能是Prism,根据NULLable字段?),那么每一步都会发生数据库访问,意味着应该访问的是至少4个。

另一方面,如果我的目标是使用镜头/棱镜来对数据库访问进行1:1的映射,那么当我时,我会得到一些无法将所有镜头分解成小块的镜头。 想要查看表中的列,依此类推。

使用带有DB的镜头是否有意义?如果是这样,我是否错过了一种明显的方法来避免重复工作以避免不必要的DB访问?

1 个答案:

答案 0 :(得分:4)

听起来我想要在c#中使用与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函数中提取数据以帮助构建查询。也不可能从完整表达中提取子镜头。所以使用镜头库是不可能的。

目前最好的方法是使用一组函数查询数据库,并使用镜头查询结果数据。有关此示例,请参阅此blog post about 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类),你应该能够创建一个&#34;白盒&# 34;函数,然后可用于创建有效的SQL查询。尽管如此,这不是一项微不足道的事情!