为什么类型安全的关系操作如此困难?

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中实现的那样.

我的问题如下:

  • 以类型安全的方式对关系操作建模这种困难的根本原因是什么.Hindley-Milner在哪里落空?或者问题是否源于已键入的lambda演算?
  • 是否有范式,关系操作是一等公民?如果是这样,是否有真实的实施?

Gab*_*lez 4

让我们定义一个在某些列上索引的表作为具有两个类型参数的类型:

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将实现FunctorApplicativeAlternative类型类。换句话说:

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)

因此您可能想知道如何实现SelectTable

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,而无需任何花哨的类型系统扩展。