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

Rob*_*ham 13 haskell lenses haskell-lens

我最近一直在玩镜头,发现它们非常适合它们的预期用途 - 挖掘复杂的数据结构.但我最欣赏他们的一个领域是数据库访问(特别是sqlite,但我认为我的问题推广到大多数数据库),但我看不出任何方法来编写不会牺牲很多钱的镜头性能或粒度.

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

在另一方面,如果我的目标是地图DB访问1:1与透镜/棱镜的用途,我得到大做,一切镜头不能被分解成更小的碎片时,我希望只看到是什么列在表格中,等等.

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

Dav*_*ani 4

在我看来,您想以类似于IQueryablec# 中的 linq 的方式使用lens。

例如,如果您有以下类型:

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
Run Code Online (Sandbox Code Playgroud)

和一个数据库:

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
Run Code Online (Sandbox Code Playgroud)

如果您想从优先级大于 1 的项目中获取任务名称列表,最好可以使用:

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

并使用以下查询查询数据库:

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);
Run Code Online (Sandbox Code Playgroud)

不幸的是,图书馆不可能做到这一点。基本上,为了提高数据库效率,您(通常)必须在一个查询中完成所有操作。这样做是不可接受的:

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

不幸的是,不可能分解函数来了解它们是由什么组成的。除了类型之外,如果不运行函数,您就无法找到有关该函数的任何信息。因此无法从函数中提取数据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)
Run Code Online (Sandbox Code Playgroud)

对布尔运算符(您将需要一个新的类型类)、列表运算符和函数组合(即 Category 类)重复该过程,您应该能够创建一个“白盒”函数,然后可以将其用于创建高效的 sql 查询。但这并不是一件小事!

  • @RobertHarvey,David 的建议表明这**可以**在 Haskell 中完成,只是还没有完成。微软在 .Net 开发上投入了数百万美元,但在 Haskell 上投入的不多(GHC 是由 MS Research 开发的)。我确信西蒙·佩顿·琼斯会喜欢改变这一点。;) (3认同)