Mar*_*urg 8 theory haskell types relational-database hindley-milner
我试图在Haskell中编写关系问题的代码,当时我必须发现以类型安全的方式执行此操作远非显而易见.比如谦虚
select 1,a,b, from T
Run Code Online (Sandbox Code Playgroud)
已经提出了一些问题:
1,a,b?一般来说投影的类型是什么?我相信即使是Oracle的PL/SQL语言也没有这么做.虽然invald投影主要在编译时检测到,但是大量的类型错误仅在运行时显示.大多数其他绑定到RDBMS(例如Java的jdbc和perl的DBI)使用字符串中包含的SQL,因此完全放弃了类型安全性.
进一步的研究表明,有一些Haskell库(HList,乙烯基和TRex),它们提供类型安全的可扩展记录等等.但是这些库都需要Haskell扩展,如DataKinds,FlexibleContexts等等.此外,这些库不易使用,并且有一种诡计的气味,至少对于像我这样的未初始化的观察者来说.
这表明,类型安全的关系操作不能很好地适应功能范例,至少不像在Haskell中实现的那样.
我的问题如下:
让我们定义一个在某些列上索引的表作为具有两个类型参数的类型:
data IndexedTable k v = ???
groupBy :: (v -> k) -> IndexedTable k v
-- A table without an index just has an empty key
type Table = IndexedTable ()
Run Code Online (Sandbox Code Playgroud)
k将是表索引的所有列的(可能是嵌套的)元组。 v将是表未索引的所有列的(可能是嵌套的)元组。
例如,如果我们有下表
| Id | First Name | Last Name |
|----|------------|-----------|
| 0 | Gabriel | Gonzalez |
| 1 | Oscar | Boykin |
| 2 | Edgar | Codd |
Run Code Online (Sandbox Code Playgroud)
...并且它在第一列上建立索引,那么类型将是:
type Id = Int
type FirstName = String
type LastName = String
IndexedTable Int (FirstName, LastName)
Run Code Online (Sandbox Code Playgroud)
但是,如果它在第一列和第二列上建立索引,则类型将为:
IndexedTable (Int, Firstname) LastName
Run Code Online (Sandbox Code Playgroud)
Table将实现Functor、Applicative和Alternative类型类。换句话说:
instance Functor (IndexedTable k)
instance Applicative (IndexedTable k)
instance Alternative (IndexedTable k)
Run Code Online (Sandbox Code Playgroud)
因此连接将实现为:
join :: IndexedTable k v1 -> IndexedTable k v2 -> IndexedTable k (v1, v2)
join t1 t2 = liftA2 (,) t1 t2
leftJoin :: IndexedTable k v1 -> IndexedTable k v2 -> IndexedTable k (v1, Maybe v2)
leftJoin t1 t2 = liftA2 (,) t1 (optional t2)
rightJoin :: IndexedTable k v1 -> IndexedTable k v2 -> IndexedTable k (Maybe v1, v2)
rightJoin t1 t2 = liftA2 (,) (optional t1) t2
Run Code Online (Sandbox Code Playgroud)
然后您将拥有一个单独的类型,我们将其称为Select. 该类型还将有两个类型参数:
data Select v r = ???
Run Code Online (Sandbox Code Playgroud)
A会消耗表中的Select一堆类型的行并产生类型为 的结果。换句话说,我们应该有一个类型的函数:vr
selectIndexed :: Indexed k v -> Select v r -> r
Run Code Online (Sandbox Code Playgroud)
我们可以定义的一些例子Select是:
count :: Select v Integer
sum :: Num a => Select a a
product :: Num a => Select a a
max :: Ord a => Select a a
Run Code Online (Sandbox Code Playgroud)
这种Select类型将实现Applicative接口,因此我们可以将多个Selects 组合成一个Select。例如:
liftA2 (,) count sum :: Select Integer (Integer, Integer)
Run Code Online (Sandbox Code Playgroud)
这类似于以下 SQL:
SELECT COUNT(*), SUM(*)
Run Code Online (Sandbox Code Playgroud)
然而,我们的表通常会有多个列,因此我们需要一种方法将焦点集中Select到单个列上。我们称这个函数为Focus:
focus :: Lens' a b -> Select b r -> Select a r
Run Code Online (Sandbox Code Playgroud)
这样我们就可以写出如下内容:
liftA3 (,,) (focus _1 sum) (focus _2 product) (focus _3 max)
:: (Num a, Num b, Ord c)
=> Select (a, b, c) (a, b, c)
Run Code Online (Sandbox Code Playgroud)
所以如果我们想写这样的东西:
SELECT COUNT(*), MAX(firstName) FROM t
Run Code Online (Sandbox Code Playgroud)
这相当于这个 Haskell 代码:
firstName :: Lens' Row String
table :: Table Row
select table (liftA2 (,) count (focus firstName max)) :: (Integer, String)
Run Code Online (Sandbox Code Playgroud)
因此您可能想知道如何实现Select和Table。
Table我在这篇文章中描述了如何实现:
http://www.haskellforall.com/2014/12/a-very-general-api-for-relational-joins.html
...你可以Select这样实现:
type Select = Control.Foldl.Fold
type focus = Control.Foldl.pretraverse
-- Assuming you define a `Foldable` instance for `IndexedTable`
select t s = Control.Foldl.fold s t
Run Code Online (Sandbox Code Playgroud)
另外,请记住,这些并不是实现Table和的唯一方法Select。它们只是帮助您入门的简单实现,您可以根据需要概括它们。
从表中选择列怎么样?好吧,你可以定义:
column :: Select a (Table a)
column = Control.Foldl.list
Run Code Online (Sandbox Code Playgroud)
所以如果你想做:
SELECT col FROM t
Run Code Online (Sandbox Code Playgroud)
...你会写:
field :: Lens' Row Field
table :: Table Row
select (focus field column) table :: [Field]
Run Code Online (Sandbox Code Playgroud)
重要的一点是,您可以在 Haskell 中很好地实现关系 API,而无需任何花哨的类型系统扩展。
| 归档时间: |
|
| 查看次数: |
244 次 |
| 最近记录: |