2015-05-02 17 views
9

Ich habe versucht, ein relationales Problem in Haskell zu programmieren, als ich herausfinden musste, dass dies in einer Art sicheren Art und Weise zu tun ist, ist alles andere als offensichtlich. Z.B. ein bescheidenesWarum sind typsichere relationale Operationen so schwierig?

select 1,a,b, from T 

wirft bereits eine Reihe von Fragen:

  • was ist die Art dieser Funktion?
  • Was ist der Typ der Projektion 1,a,b? Was ist der Typ von eine Projektion im Allgemeinen?
  • Was ist der Ergebnistyp und wie drücke ich die Beziehung zwischen dem Ergebnistyp und der Projektion aus?
  • Was ist der Typ einer solchen Funktion, die eine gültige Projektion akzeptiert?
  • Wie kann ich beim Kompilieren ungültige Projektionen erkennen?
  • Wie würde ich eine Spalte zu einer Tabelle oder zu einer Projektion hinzufügen?

Ich glaube, sogar Oracle PL/SQL-Sprache wird nicht ganz richtig. Während Ungültige Projektionen meist zur Kompilierzeit erkannt werden, gibt es eine große Anzahl von Typfehlern, die nur zur Laufzeit angezeigt werden. Die meisten anderen Bindungen zu RDBMS (z. B. Java's jdbc und Perls DBI) verwenden SQL, das in Strings enthalten ist, und geben somit die Typensicherheit vollständig auf.

Weitere Untersuchungen zeigten, dass es einige Haskell-Bibliotheken (HList, vinyl und TREX), die und etwas mehr typsichere erweiterbare Aufzeichnungen zur Verfügung stellen. Aber diese Bibliotheken benötigen alle Haskell-Erweiterungen wie DataKinds, FlexibleContexts und viele mehr. Außerdem sind diese Bibliotheken nicht einfach zu bedienen und riechen nach Tricks, zumindest für nicht initialisierte Beobachter wie mich.

Dies deutet darauf hin, dass typsichere relationale Operationen nicht gut in das funktionale Paradigma passen, zumindest nicht so, wie es in Haskell implementiert ist.

Meine Fragen sind:

  • Was die grundlegenden Ursachen für diese Schwierigkeiten sind relationale Operationen in einer Art sichere Art und Weise zu modellieren. Wo steht Hindley-Milner? Oder liegt das Problem bereits am typisierten Lambda-Kalkül?
  • Gibt es ein Paradigma, in dem relationale Operationen erstklassige Bürger sind? Und wenn ja, gibt es eine Implementierung in der realen Welt?
+1

Haben Sie sich die Bibliotheken angeschaut, die einen typsicheren DB-Zugriff in Haskell erlauben? Z.B. https://hackage.haskell.org/package/esqueleto oder http://www.yesodweb.com/book/persistent? – chi

+0

Noch nicht. Der Datenbankzugriff ist nicht meine Hauptsorge. Aber diese Pakete müssen meine Probleme auf die eine oder andere Weise gelöst haben, also könnte ich einen Blick darauf werfen. Vielen Dank. –

Antwort

4

Lassen Sie uns einen Tisch auf einige Spalten als Typ mit zwei Typparametern indiziert definieren:

data IndexedTable k v = ??? 

groupBy :: (v -> k) -> IndexedTable k v 

-- A table without an index just has an empty key 
type Table = IndexedTable() 

k eine (möglicherweise verschachtelte) Tupel aller Spalten, die in der Tabelle auf indiziert ist. v ist ein (möglicherweise verschachteltes) Tupel aller Spalten, für die die Tabelle nicht indiziert ist.

So zum Beispiel, wenn wir haben die folgende Tabelle

| Id | First Name | Last Name | 
|----|------------|-----------| 
| 0 | Gabriel | Gonzalez | 
| 1 | Oscar  | Boykin | 
| 2 | Edgar  | Codd  | 

...und es auf der ersten Spalte indiziert wurden, dann wäre der Typ sein:

type Id = Int 
type FirstName = String 
type LastName = String 

IndexedTable Int (FirstName, LastName) 

Allerdings, wenn es auf der ersten und zweiten Spalte indiziert wurden, dann wäre der Typ sein:

IndexedTable (Int, Firstname) LastName 

Table würde Implementieren Sie die Klassen Functor, Applicative und Alternative. Mit anderen Worten:

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 

Dann würden Sie einen separaten Typ haben, dass wir eine Select nennen:

instance Functor (IndexedTable k) 

instance Applicative (IndexedTable k) 

instance Alternative (IndexedTable k) 

So würde verbindet als implementiert werden. Dieser Typ wird auch zwei Typparameter:

data Select v r = ??? 

A Select ein paar Zeilen des Typs v aus der Tabelle verbrauchen würde und ein Ergebnis vom Typ r erzeugen. Mit anderen Worten, sollten wir eine Funktion vom Typ haben:

selectIndexed :: Indexed k v -> Select v r -> r 

Einige Beispiele Select s, die wir würden vielleicht definieren:

count :: Select v Integer 
sum  :: Num a => Select a a 
product :: Num a => Select a a 
max  :: Ord a => Select a a 

Diese Select Art der Applicative Schnittstelle implementieren würde, so konnten wir kombinieren mehrere Select s in eine einzige Select. Zum Beispiel:

liftA2 (,) count sum :: Select Integer (Integer, Integer) 

, die zu dieser SQL analog wäre:

SELECT COUNT(*), SUM(*) 

Doch oft werden unsere Tabelle mehrere Spalten haben, so müssen wir einen Weg ein Select auf eine einzige Säule zu konzentrieren. Lassen Sie uns diese Funktion aufrufen Focus:

focus :: Lens' a b -> Select b r -> Select a r 

Damit wir Dinge wie schreiben:

SELECT COUNT(*), MAX(firstName) FROM t 

Das wäre gleichbedeutend dazu:

liftA3 (,,) (focus _1 sum) (focus _2 product) (focus _3 max) 
    :: (Num a, Num b, Ord c) 
    => Select (a, b, c) (a, b, c) 

Also, wenn wir so etwas wie schreiben wollte Haskell-Code:

Sie könnten sich also fragen, wie man Select und Table implementieren könnte.

ich beschreiben, wie Table in diesem Beitrag zu implementieren:

http://www.haskellforall.com/2014/12/a-very-general-api-for-relational-joins.html

...und Sie können Select wie einfach implementieren:

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 

Auch bedenken Sie, dass diese nicht die einzigen Möglichkeiten zur Umsetzung Table und Select. Sie sind nur eine einfache Implementierung, um Sie zu starten und Sie können sie bei Bedarf verallgemeinern.

Wie wäre es mit der Auswahl von Spalten aus einer Tabelle? Nun können Sie definieren:

column :: Select a (Table a) 
column = Control.Foldl.list 

Also, wenn Sie tun wollte:

SELECT col FROM t 

... würden Sie schreiben:

field :: Lens' Row Field 

table :: Table Row 

select (focus field column) table :: [Field] 

Der wichtige Mitnehmen ist, dass Sie ein implementieren relationale API in Haskell gut ohne jede Art von System-Erweiterungen.

+1

Ich finde zwei Dinge verwirrend: (1) in Ihrer Argumentation spielen die _keys_ eine sehr prominente Rolle. Ich glaube, dass Schlüssel für bestimmte Garantien erforderlich sind, aber nicht für die relationalen Operationen als solche. (2) Sollte der Ergebnistyp von _select_ nicht erneut eine Tabelle sein? –

+0

Ich habe meinen Beitrag aktualisiert. (1) Für eine Tabelle, in der Sie sich nicht um den Schlüssel kümmern, müssen Sie nur den Typ des Schlüssels auf '()' setzen. (2) Sie können eine Tabelle abrufen, indem Sie eine entsprechende Auswahl anwenden, die eine Tabelle zurückgibt. Ein 'Select' kann eine Liste/Tabelle von Ergebnissen zurückgeben. –