HaskRel 0.1.0.0 → 0.1.0.1
raw patch · 18 files changed
+864/−307 lines, 18 filesdep −lens
Dependencies removed: lens
Files
- HaskRel.cabal +113/−56
- README.md +139/−19
- examples/MiniDB.hs +24/−0
- examples/SuppliersPartsDB/Default.hs +25/−6
- examples/SuppliersPartsDB/Definition.hs +7/−2
- examples/SuppliersPartsDB/README.md +1/−1
- examples/SuppliersPartsExample.hs +55/−29
- src/Database/HaskRel/FWTabulation.hs +21/−7
- src/Database/HaskRel/HFWTabulation.hs +23/−7
- src/Database/HaskRel/Order.hs +5/−1
- src/Database/HaskRel/RDBMS.hs +12/−4
- src/Database/HaskRel/Relational/Algebra.hs +121/−53
- src/Database/HaskRel/Relational/Assignment.hs +33/−14
- src/Database/HaskRel/Relational/Definition.hs +58/−22
- src/Database/HaskRel/Relational/Expression.hs +181/−68
- src/Database/HaskRel/Relational/Unicode.hs +27/−11
- src/Database/HaskRel/Relational/Variable.hs +4/−2
- src/Database/HaskRel/Support.hs +15/−5
HaskRel.cabal view
@@ -1,59 +1,100 @@ name: HaskRel -version: 0.1.0.0+version: 0.1.0.1 -synopsis: HaskRel, Haskell as a DBMS with support for the relational algebra+synopsis: HaskRel, Haskell as a DBMS with support for the relational+ algebra -description: HaskRel aims to define the elements of the relational theory of database management as first-class Haskell objects, thus enabling Haskell in its own right as a DBMS with first-class support for the relational algebra. It does not qualify as a proper RDBMS since it as-is only defines the relational algebra, relational variables and relational assignment. It does not define the relational calculus, views, constraints and transactions (beyond the fundamental requirement that the tuples of relations are to be unique), certain operators like relation valued aggregate operators, nor a few minor or even deprecated operators such as DIVIDE. The implemented parts are decently complete even if there are major implementation shortcomings that prevent this from being practically usable as an actual DBMS.- .- I refer to it as "/first-class/" since the types of the relational model are first-class types to Haskell, and the Haskell type system is able to induce the type resulting of relational expressions (for instance that a natural join of two relations results in a relation with a heading that is the setwise union of the headings of the original relations).- .- == Model- .- HaskRel is based on the relational model of database management, as defined by Chris Date and Hugh Darwen today. Is is not based on SQL in any way. SQL and Relational Theory, 2nd ed. [1] has been the principal guide in this endeavor (I have plenty of SQL in my background so a book on SQL and the relational model has been a good introduction to the latter). See also The Third Manifesto [2], and the additional material on http://www.thethirdmanifesto.com/ (this might be better to start with for those mathematically inclined but not steeped in SQL).- .- In addition to naming certain other aspects of Tutorial D has been adopted, such as argument order. Such aspects may be changed in the future if HaskRel is found to be solid enough to stand on its own, particularly if there are ways to express this that are more idiomatic vis-a-vis Haskell (Tutorial D is, after all, not a definition of the relational model, but vehicle for understanding it).- .- == Implementation- .- HaskRel is developed and tested with GHC 7.10.2. It builds upon HList (at least version 0.4.0.0). Separately from this library there is also an optional variant that builds on TIPs instead of records, which additionally relies on Lens (tested with version 4.12.3).- .- = Examples- .- The examples in this documentation are based on "the old warhorse" that is the suppliers-parts database (see [1] for more). This gives a body of relational expressions with known results to base examples upon. See also examples\/SuppliersPartsExamples.hs (not visible from this documentation) for Haskell versions of a selection of the Tutorial D expressions given as examples in chapters 6 and 7 of [1]. These can be run by starting examples\/suppliersPartsDB.sh and then running @snrt2ndExamples@. While most Tutorial D expressions translate fairly verbatim to Haskell there are a few where one must be a bit more explicit. While most Tutorial D expressions translate fairly verbatim to Haskell there are a few where Haskell is stricter than Tutorial D and one must be a bit more explicit.- .- $ is always used after `p`\/`rPrint` or `pt`\/`rPrintTyped` in the examples to keep them uniform (and so it kinda looks like a prompt), even when not required. The short forms `p` and `pt` are used whenever there isn't a conflict with other identifiers, whereas for the SuppliersPartsExample, which has a relvar "@p@", `rPrint` is used instead of `p` for presentation of relational objects without type information.- .- = Terminology- .- Since this builds on both Haskell and relational theory this documentation uses terms as they have been established in material related to either. Several terms of Haskell and HList have been redefined in terms of relational theory in this library, mostly to illustrate how terms and concepts have been mapped from the latter to the former. (I'm trying to keep this open to change later if it turns out to be an unhelpful crutch.)- .- The following table gives a quick overview of either terms or concepts as found in Haskell, the relational model (as presented in [1]), HaskRel and SQL, and how they are mapped from the second to the first:- .- @- ┌───────────────────────────┬────────────────────┬────────────┬────────────────────────────────────────────────┐- │ haskell │ relModel │ haskRel │ sql │- ╞═══════════════════════════╪════════════════════╪════════════╪════════════════════════════════════════════════╡- │ <https://hackage.haskell.org/package/tagged/docs/Data-Tagged.html Data.Tagged.Tagged> │ attribute │ Attr │ field, column │- │ <https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html Data.HList.Record.Record> │ tuple │ RTuple │ row │- │ ( <https://hackage.haskell.org/package/containers/docs/Data-Set.html Set> (<https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html Record> a) ) │ relation │ Relation a │ table │- │ <https://hackage.haskell.org/package/filepath/docs/System-FilePath.html FilePath> (<https://hackage.haskell.org/package/containers/docs/Data-Set.html Set> (<https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html Record> a)) │ relvar │ Relvar a │ table │- │ <https://hackage.haskell.org/package/HList/docs/Data-HList-FakePrelude.html#t:Label Data.HList.Record.Label> │ attribute name │ Label │ field name, column name │- │ <https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html#t:Labels Data.HList.Record.Labels> │ attribute name set │ Labels │ list of field/column names │- │ function, operator │ operator │ function │ operator, function, procedure, routine, method │- └───────────────────────────┴────────────────────┴────────────┴────────────────────────────────────────────────┘- @- .- Found in "example\/Terminology.hs". Note that this is just an overview, study of [1] or [2], Haskell itself, HList and HaskRel is required to see how terms and concepts correlate.- .- The term \"RTuple\", or "r-tuple", is chosen to simultaneously distinguish the concept from Haskell tuples while relating it to tuples of the relational model. For the type of either "Record a" or "Set ( Record a )" in Haskell the term "heading" is used in relational theory, and "row type" or "composite type" in SQL. In relational theory the term "scalar" is used to refer to data types that are neither tuples nor relations, which corresponds to everything but "Record a" or "Set ( Record a )" in Haskell. Note also that HaskRel /does/ use the term "table", but then only in the sense of "presentation of a relation value" (see above).- .- == The HaskRel library- .- Not all modules of this library are relevant to gain an understanding of how it functions, the next part to go to at this point is "Database.HaskRel.RDBMS", and the modules it reexports.- .- [1] <http://shop.oreilly.com/product/0636920022879.do SQL and Relational Theory, 2nd ed. (2011), C.J. Date>- [2] <http://www.dcs.warwick.ac.uk/~hugh/TTM/TTM-2013-02-07.pdf The Third Manifesto, C. J. Date and Hugh Darwen, February 7th, 2013>+description:+ HaskRel aims to define those elements of the relational theory of database+ management that Haskell can accommodate, thus enabling Haskell (or more+ precisely GHC) in its own right as a DBMS with first-class support for those+ parts of the relational model. It does not qualify as a proper RDBMS since it+ as-is only defines the relational algebra, relational variables and relational+ assignment. It does not define the relational calculus, views, constraints and+ transactions (beyond the fundamental requirement that the tuples of relations+ are to be unique), certain operators like relation valued aggregate operators,+ nor a few minor or even deprecated operators such as DIVIDE. The implemented+ parts are decently complete even if there are major implementation+ shortcomings that prevent this from being practically usable as an actual+ DBMS.+ .+ I refer to it as "/first-class/" since the types of the relational model are+ first-class types to Haskell, and the Haskell type system is able to induce+ the type resulting of relational expressions (for instance that a natural join+ of two relations results in a relation with a heading that is the setwise+ union of the headings of the original relations).+ .+ = Examples+ .+ The examples in this documentation are based on "the old warhorse" that is the+ suppliers-parts database (see [1] for more). This gives a body of relational+ expressions with known results to base examples upon. See also+ examples\/SuppliersPartsExamples.hs (not visible from this documentation) for+ Haskell versions of a selection of the Tutorial D expressions given as+ examples in chapters 6 and 7 of [1]. These can be run by starting+ examples\/suppliersPartsDB.sh and then running @snrt2ndExamples@. While most+ Tutorial D expressions translate fairly verbatim to Haskell there are a few+ where one must be a bit more explicit. While most Tutorial D expressions+ translate fairly verbatim to Haskell there are a few where Haskell is stricter+ than Tutorial D and one must be a bit more explicit.+ .+ $ is always used after `p`\/`rPrint` or `pt`\/`rPrintTyped` in the examples to+ keep them uniform (and so it kinda looks like a prompt), even when not+ required. The short forms `p` and `pt` are used whenever there isn't a+ conflict with other identifiers, whereas for the SuppliersPartsExample, which+ has a relvar "@p@", `rPrint` is used instead of `p` for presentation of+ relational objects without type information.+ .+ = Terminology+ .+ Since this builds on both Haskell and relational theory this documentation+ uses terms as they have been established in material related to+ either. Several terms of Haskell and HList have been redefined in terms of+ relational theory in this library, mostly to illustrate how terms and concepts+ have been mapped from the latter to the former. (I'm trying to keep this open+ to change later if it turns out to be an unhelpful crutch.)+ .+ The following table gives a quick overview of either terms or concepts as+ found in Haskell, the relational model (as presented in [1]), HaskRel and SQL,+ and how they are mapped from the second to the first:+ .+ @+ ┌───────────────────────────┬────────────────────┬────────────┬────────────────────────────────────────────────┐+ │ haskell │ relModel │ haskRel │ sql │+ ╞═══════════════════════════╪════════════════════╪════════════╪════════════════════════════════════════════════╡+ │ <https://hackage.haskell.org/package/tagged/docs/Data-Tagged.html Data.Tagged.Tagged> │ attribute │ Attr │ field, column │+ │ <https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html Data.HList.Record.Record> │ tuple │ RTuple │ row │+ │ ( <https://hackage.haskell.org/package/containers/docs/Data-Set.html Set> (<https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html Record> a) ) │ relation │ Relation a │ table │+ │ <https://hackage.haskell.org/package/filepath/docs/System-FilePath.html FilePath> (<https://hackage.haskell.org/package/containers/docs/Data-Set.html Set> (<https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html Record> a)) │ relvar │ Relvar a │ table │+ │ <https://hackage.haskell.org/package/HList/docs/Data-HList-FakePrelude.html#t:Label Data.HList.Record.Label> │ attribute name │ Label │ field name, column name │+ │ <https://hackage.haskell.org/package/HList/docs/Data-HList-Record.html#t:Labels Data.HList.Record.Labels> │ attribute name set │ Labels │ list of field/column names │+ │ function, operator │ operator │ function │ operator, function, procedure, routine, method │+ └───────────────────────────┴────────────────────┴────────────┴────────────────────────────────────────────────┘+ @+ .+ Found in "example\/Terminology.hs". Note that this is just an overview, study+ of [1] or [2], Haskell itself, HList and HaskRel is required to see how terms+ and concepts correlate.+ .+ The term \"RTuple\", or "r-tuple", is chosen to simultaneously distinguish the+ concept from Haskell tuples while relating it to tuples of the relational+ model. For the type of either "Record a" or "Set ( Record a )" in Haskell the+ term "heading" is used in relational theory, and "row type" or "composite+ type" in SQL. In relational theory the term "scalar" is used to refer to data+ types that are neither tuples nor relations, which corresponds to everything+ but "Record a" or "Set ( Record a )" in Haskell. Note also that HaskRel /does/+ use the term "table", but then only in the sense of "presentation of a+ relation value" (see above).+ .+ == The HaskRel library+ .+ Not all modules of this library are relevant to gain an understanding of how+ it functions, the next part to go to at this point is+ "Database.HaskRel.RDBMS", and the modules it reexports.+ .+ [1] <http://shop.oreilly.com/product/0636920022879.do SQL and Relational Theory, 2nd ed. (2011), C.J. Date>+ [2] <http://www.dcs.warwick.ac.uk/~hugh/TTM/TTM-2013-02-07.pdf The Third Manifesto, C. J. Date and Hugh Darwen, February 7th, 2013> license: GPL-2 @@ -71,7 +112,8 @@ extra-source-files: examples/*.hs, examples/*.sh, examples/SuppliersPartsDB/*.hs -data-files: README.md, examples/SuppliersPartsDB/README.md, examples/SuppliersPartsDB/*.rv+data-files: README.md, examples/SuppliersPartsDB/README.md, + examples/SuppliersPartsDB/*.rv cabal-version: >=1.10 @@ -82,13 +124,28 @@ location: https://github.com/thormick/HaskRel library- exposed-modules: Database.HaskRel.RDBMS, Database.HaskRel.FWTabulation, Database.HaskRel.HFWTabulation, Database.HaskRel.Order, Database.HaskRel.Support, Database.HaskRel.Relational.Algebra, Database.HaskRel.Relational.Assignment, Database.HaskRel.Relational.Definition, Database.HaskRel.Relational.Expression, Database.HaskRel.Relational.Unicode, Database.HaskRel.Relational.Variable+ exposed-modules: Database.HaskRel.RDBMS,+ Database.HaskRel.FWTabulation,+ Database.HaskRel.HFWTabulation,+ Database.HaskRel.Order,+ Database.HaskRel.Support,+ Database.HaskRel.Relational.Algebra,+ Database.HaskRel.Relational.Assignment,+ Database.HaskRel.Relational.Definition,+ Database.HaskRel.Relational.Expression,+ Database.HaskRel.Relational.Unicode,+ Database.HaskRel.Relational.Variable default-extensions: DataKinds - other-extensions: TypeOperators, FlexibleContexts, ScopedTypeVariables, PolyKinds, TypeFamilies, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, KindSignatures+ other-extensions: TypeOperators, FlexibleContexts, ScopedTypeVariables,+ PolyKinds, TypeFamilies, FlexibleInstances,+ MultiParamTypeClasses, UndecidableInstances,+ KindSignatures - build-depends: base >=4.8 && <4.9, HList >=0.4 && <0.5, lens >=4.12 && <4.13, containers >=0.5 && <0.6, tagged >=0.8 && <0.9, directory >=1.2 && <1.3, ghc-prim >=0.4 && <0.5+ build-depends: base >=4.8 && <4.9, HList >=0.4 && <0.5,+ containers >=0.5 && <0.6, tagged >=0.8 && <0.9,+ directory >=1.2 && <1.3, ghc-prim >=0.4 && <0.5 hs-source-dirs: src
README.md view
@@ -3,29 +3,133 @@ (C) 2015, Thor Michael Støre -HaskRel aims to define the elements of the relational theory of database management as first-class Haskell objects, thus enabling Haskell in its own right as a DBMS with first-class support for the relational algebra. It does not qualify as a proper RDBMS since it as-is only defines the relational algebra, relational variables and relational assignment. It does not define the relational calculus, views, constraints and transactions (beyond the fundamental requirement that the tuples of relations are to be unique), certain operators like relation valued aggregate operators, nor a few minor or even deprecated operators such as DIVIDE. The implemented parts are decently complete even if there are major implementation shortcomings that prevent this from being practically usable as an actual DBMS.+HaskRel aims to define those elements of the relational theory of database+management that Haskell can accommodate, thus enabling Haskell (or more+precisely GHC) in its own right as a DBMS with first-class support for those+parts of the relational model. It does not qualify as a proper RDBMS since it+as-is only defines the relational algebra, relational variables and relational+assignment. It does not define the relational calculus, views, constraints and+transactions (beyond the fundamental requirement that the tuples of relations+are to be unique), certain operators like relation valued aggregate operators,+nor a few minor or even deprecated operators such as DIVIDE. The implemented+parts are decently complete even if there are major implementation shortcomings+that prevent this from being practically usable as an actual DBMS. -I refer to it as _first-class_ since the types of the relational model are first-class types to Haskell, and the Haskell type system is able to induce the type resulting of relational expressions (for instance that a natural join of two relations results in a relation with a heading that is the setwise union of the headings of the original relations).+I refer to it as _first-class_ since the types of the relational model are+first-class types to Haskell, and the Haskell type system is able to induce the+type resulting of relational expressions (for instance that a natural join of+two relations results in a relation with a heading that is the setwise union of+the headings of the original relations). -As such it isn't as much just an implementation of the relational model built using Haskell (the way, say, PostgreSQL is an implementation of SQL using C), as a Haskell library that accommodates a subset of the relational model (unlike PostgreSQL, which doesn't turn C into an RDBMS), where GHCi is employed as a database terminal front-end.+As such it isn't as much just an implementation of the relational model built+using Haskell (the way, say, PostgreSQL is an implementation of SQL using C), as+a Haskell library that accommodates a subset of the relational model (unlike+PostgreSQL, which doesn't turn C into an RDBMS), where GHCi is employed as a+database terminal front-end. -HaskRel is not based on SQL, there is no support for anything that it defines beyond what the relational model does, nor plans for that. It is instead inspired by Tutorial D. For a project of this kind it is generally more sensible to follow developed theory as it stands rather than an industry standard. More specifically, HaskRel is intended as an exploration of the possibilities to directly employ a general purpose programming language and its associated REPL as a relational database management system, or even as a bit of an academic exercise, and a great deal of SQL is not relevant towards this end. (SQL defines for instance its own types, while HaskRel is explicitly intended to build upon Haskell's, and SQL's type system is weaker than Haskell's, in that it employs a wide range of coercions between types). Features and aspects of SQL are either irrelevant for the purpose of HaskRel, or overlap or conflict with Haskell idioms that are more relevant to adhere to for a "make an RDBMS out of a general purpose programming language" project. (Parts of Tutorial D also overlap, such as the `THE_` operator and selectors vs. show and read, but all in all a fraction of what SQL does.) Even if there were plans to go beyond this and support SQL it would be a good first step to first support the fundamentals of the relational model both as _completely_ as it is reasonable to do, and to do so _properly_. -For an understanding of the foundation HaskRel builds on see for instance [SQL and Relational Theory, 2nd ed.](http://shop.oreilly.com/product/0636920022879.do), [The Third Manifesto](http://www.dcs.warwick.ac.uk/~hugh/TTM/TTM-2013-02-07.pdf), and the additional material on [http://www.thethirdmanifesto.com/]().+Model+----- -Of the shortcomings of model that are mentioned above the relational calculus, database constraints, and transactions would be possible to add in a reasonably complete manner (I hope I am able to work on this). Views would be the most difficult, as far as I able to tell it is not possible to properly implement this fully within Haskell, although I don't know if some part of it can be. (This is something SQL products have had difficulties with, after all, and an area where the SQL standard is obtuse.) One further particular issue is that data definition is performed by writing, compiling and loading Haskell, which is particularly static compared to DDL in a proper DBMS.+HaskRel is based on the relational model of database management, as defined by+Chris Date and Hugh Darwen today. For an understanding of the foundation HaskRel+builds on see for instance+[SQL and Relational Theory, 2nd ed.](http://shop.oreilly.com/product/0636920022879.do),+which has been the principal guide in this endeavor (I have plenty of SQL in my+background so a book on SQL and the relational model has been a good+introduction to the latter). See also+[The Third Manifesto, TTM](http://www.dcs.warwick.ac.uk/~hugh/TTM/TTM-2013-02-07.pdf),+and the additional material on <http://www.thethirdmanifesto.com/> (this might+be better to start with for those mathematically inclined but not steeped in+SQL). Regarding The Third Manifesto it is important to note that all parts of+TTM that overlap with something preexisting in Haskell or which Haskell cannot+support have been disregarded when implementing HaskRel (it's about determining+where we stand, after all). -There are many missing implementation level features that database products rely on, enough so that HaskRel neither is nor will be usable as a production system, even if the shortcomings mentioned regarding the model was in place. Query optimization and indexes are the major ones, just for starters, which would require a monumental effort that there are no plans for. There is also the issue that even slightly complex queries have compile times running into several seconds. HaskRel also builds on Data.Set to manage relation bodies, although this is too limited to support a practically usable RDBMS, at least by itself.+HaskRel is not based on SQL, there is no support for anything that it defines+beyond what the relational model does, nor plans for that. Tutorial D has been+the reference when implementing the functions of the relational algebra,+although HaskRel does not satisfy the criteria of D. For a project of this kind+it is sensible to follow developed theory as it stands rather than an industry+standard that deviates from it. More specifically, HaskRel is intended as an+exploration of the possibilities to directly employ a general purpose+programming language and its associated REPL as a relational database management+system, or even as a bit of an academic exercise, and a great deal of SQL is not+relevant towards this end. (SQL defines for instance its own types, while+HaskRel is explicitly intended to build upon Haskell's, and SQL's type system is+weaker than Haskell's, in that it employs a wide range of coercions between+types). -In any case, HaskRel is very, very far from being a usable database management system, and there is as such no plan to bring it there, but I hope it's useful from an academic perspective.+Features and aspects of SQL that go beyond or deviate from relational theory are+by and large irrelevant for the purpose of HaskRel, and may even overlap or+conflict with Haskell idioms that are more relevant to adhere to for a "make an+RDBMS out of a general purpose programming language" project. (Parts of Tutorial+D also overlap, such as the `THE_` operator and selectors vs. show and read, but+all in all a fraction of what SQL does.) Even if there were plans to go beyond+this and support SQL it would be a good first step to first support the+fundamentals of the relational model both as _completely_ as it is reasonable to+do, and to do so _properly_. +In addition to naming certain other aspects of Tutorial D has been adopted, such+as argument order. Such aspects may be changed in the future if HaskRel is found+to be solid enough to stand on its own, particularly if there are ways to+express this that are more idiomatic vis-a-vis Haskell (Tutorial D is, after+all, not a definition of the relational model, but a vehicle for understanding+it). +Of the shortcomings of model that are mentioned above the relational calculus,+database constraints, and transactions would be possible to add in a reasonably+complete manner (I hope I am able to work on this). Views would be the most+difficult, as far as I able to tell it is not possible to properly implement+this fully within Haskell, although I don't know if some part of it can+be. (This is something SQL products have had difficulties with, after all, and+an area where the SQL standard is obtuse.) One further particular issue is that+data definition is performed by writing, compiling and loading Haskell, which is+very static compared to DDL in a proper DBMS.+++Implementation+--------------++HaskRel is developed and tested with GHC 7.10.2. It builds upon HList (at least+version 0.4.0.0). Separately from this library there is also an optional variant+that builds on TIPs instead of records, which additionally relies on Lens+(tested with version 4.12.3).++There are many missing implementation level features that database products rely+on, enough so that HaskRel neither is nor will be usable as a production system,+even if the shortcomings mentioned regarding the model was in place. Query+optimization and indexes are the major ones, just for starters, which would+require a monumental effort that there are no plans for. There is also the issue+that even slightly complex queries have compile times running into several+seconds. HaskRel also builds on Data.Set to manage relation bodies, although+this is too limited to support a practically usable RDBMS, at least by itself.++In any case, HaskRel is very, very far from being a usable database management+system, and there is as such no plan to bring it there, but I hope it's useful+from an academic perspective.++ Trying it out--------------+============= -To get started with an example database cd into examples, run suppliersPartsDB.sh, look through SuppliersPartsExample.hs, and run examples from the Haddock or that file.+A Haskell version of the ubiquitous suppliers-and-parts database is included as+a [sample database](examples/SuppliersPartsDB/), with GHC installed this can be+loaded by running suppliersPartsDB.sh in examples from the command line. This+loads [`SuppliersPartsExample.hs`](examples/SuppliersPartsExample.hs), which+contains Haskell renditions of Tutorial D expressions used as examples in SQL+and Relational Theory, 2nd ed chapters 6 and 7. In the documentation see+particularly+[`Database.HaskRel.Relational.Expression`](http://hackage.haskell.org/package/HaskRel/docs/Database-HaskRel-Relational-Expression.html),+which "pulls together" both the algebra and assignment functions and generalizes+them to operate as they should in a DBMS (with caveats), on both variables and+values. -Alternatively, if one runs `cabal repl` one will have to either load a file with definitions, or define some elements to play around with. In the example below most everything is ad-hoc, predefining a few values would make certain expressions look less messy. Here are what constants look like:+Alternatively, `cabal repl` will start a GHCi session with the required modules+loaded, although one will have to either load a file with definitions, or define+some elements to play around with. In the example below most everything is+ad-hoc, predefining a few values would make certain expressions look less+messy. Here are what relation constants look like: *Database.HaskRel.RDBMS> let foo = relation' [( 10, "foo" ),( 20, "bar" ) ] :: Relation '[Attr "asdf" Int, Attr "qwer" String] *Database.HaskRel.RDBMS> let bar = relation' [( 10, "one" ),( 30, "two" ) ] :: Relation '[Attr "asdf" Int, Attr "zxcv" String]@@ -50,9 +154,12 @@ │ 10 │ foo │ one │ └─────────────┴────────────────┴────────────────┘ -(Note that certain operating systems and browsers have difficulties displaying the double underline of tables. On OS X Firefox has worked better than Chrome.)+(Note that certain operating systems and browsers have difficulties displaying+the horizontal lines of tables, either single or double. On OS X Firefox has+worked better than Chrome.) -As advertised, Haskell infers the type of relational expressions:+As advertised, Haskell infers the type of relational expressions (mind the type+synonyms though): *Database.HaskRel.RDBMS> :t foo foo :: Relation '[Attr "asdf" Int, Attr "qwer" String]@@ -70,9 +177,11 @@ *Database.HaskRel.RDBMS> assign baz (empty :: Relation '[Attr "asdf" Int, Attr "qwer" String] ) Value assigned to baz.rv -See "examples" for the proper way to do this.+See [Definition.hs](examples/SuppliersPartsDB/Definition.hs) of the+SuppliersPartsDB for a way to do this properly. -`assign` against a newly defined relvar initializes it in the file system, and will create a file relative to the directory the interpreter is run from.+`assign` against a newly defined relvar initializes it in the file system, and+will create a file relative to the directory the interpreter is run from. *Database.HaskRel.RDBMS> pt baz ┌─────────────┬────────────────┐@@ -99,7 +208,7 @@ │ 30 │ frotz │ └─────────────┴────────────────┘ -Concise expression of updates require a set of language extensions:+Concise expression of updates require a set of language extensions (this in addition to DataKinds, which this module enables by default): *Database.HaskRel.RDBMS> :set -XQuasiQuotes -XKindSignatures -XViewPatterns *Database.HaskRel.RDBMS> update baz (\ [pun|asdf|] -> asdf > 15 ) (\ [pun|qwer|] -> case (qwer ++ "-new") of (qwer) -> [pun|qwer|])@@ -113,7 +222,8 @@ │ 30 │ frotz-new │ └─────────────┴────────────────┘ -All functions of the relational algebra that HaskRel implement work against both relation constants and relvars, as do the print functions `p` and `pt`:+All functions of the relational algebra that HaskRel implement work against both+relation constants and relvars, as do the print functions `p` and `pt`: *Database.HaskRel.RDBMS> p$ baz `nJoin` bar ┌──────┬───────────┬──────┐@@ -127,6 +237,16 @@ Contributions ------------- -While contributions to HaskRel itself would be great, the limiting factors for HaskRel to develop further, let alone be usable, is in its foundations. The most interesting work that could be done with HaskRel is to go through it and find points where Haskell or its ecosystem could be improved to the benefit of both HaskRel and other projects, and focus development efforts on that. The two most important libraries that HaskRel builds on are HList and Data.Set; efforts towards improving them or arguments towards replacing them would be beneficial.+While contributions to HaskRel itself would be great, the limiting factors for+HaskRel to develop further, let alone be usable, is in its foundations. The most+interesting work that could be done with HaskRel is to go through it and find+points where Haskell or its ecosystem could be improved to the benefit of both+HaskRel and other projects, and focus development efforts on that. The two most+important libraries that HaskRel builds on are HList and Data.Set; efforts+towards improving them or arguments towards replacing them would be beneficial. -Making HaskRel usable for practical purposes is not on the horizon, but it may be possible to improve it further towards being a demonstrator of the relational model. A list of "things Haskell needs in order to qualify as a first class RDBMS" would also be a fun list to have, even if there isn't a chance of it happening.+Making HaskRel usable for practical purposes is not on the horizon, but it may+be possible to improve it further towards being a demonstrator of the relational+model. A list of "things Haskell/GHC needs in order to qualify as a first class+RDBMS" would also be a fun list to have, even if there isn't a chance of it+happening.
+ examples/MiniDB.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE DataKinds, TemplateHaskell #-}++{- | An even smaller DB for minimal examples -}+module MiniDB where++import Data.HList.CommonMain+import Database.HaskRel.RDBMS++makeLabels6 ["sno","sName","status","city", "pno", "qty"]++dbPath = "./"++sp :: Relvar '[Attr "sno" String,+ Attr "pno" String,+ Attr "qty" Integer]+sp = Relvar ( dbPath ++ "SP.rv" )++{-++>>> let s = ( relation' [ ( "S1", "Smith", 20, "London" ), ( "S2", "Jones", 10, "Paris" ), ( "S3", "Blake", 30, "Paris" ) ] :: Relation '[Attr "sno" String, Attr "sName" String, Attr "status" Integer, Attr "city" String] )++>>> sp `assign` ( relation' [ ("S1", "P1", 300), ("S1", "P3", 400), ("S1", "P5", 100), ("S2", "P1", 300), ("S3", "P2", 200) ] :: Relation '[Attr "sno" String, Attr "pno" String, Attr "qty" Integer] )++-}
examples/SuppliersPartsDB/Default.hs view
@@ -22,7 +22,12 @@ TUPLE { SNO ‘S5’ , SNAME ‘Adams’ , STATUS 30 , CITY ‘Athens’ } } -In Tutorial D attribute identifiers can both be used without arguments (within a query to denote a attribute of a relation in scope), or with an argument as above, as a attribute constructor. The same can be achieved in Haskell in various ways. One way is defining all pertinent functions such that they allow both a label and a constructor without an argument, and then only defining such constructors:+In Tutorial D attribute identifiers can both be used without arguments (within a+query to denote a attribute of a relation in scope), or with an argument as+above, as a attribute constructor. The same can be achieved in Haskell in+various ways. One way is defining all pertinent functions such that they allow+both a label and a constructor without an argument, and then only defining such+constructors: status a = (Label::Label "status") .=. (a::Integer) @@ -30,9 +35,16 @@ rTuple ( sno "S1" , sName "Smith" , status 20 , city "London" ) -While still letting one use sno, sName, etc as attribute identifiers in functions. However, doing this consistently requires some rather convoluted type-level acrobatics that causes exponential growth in function definition complexity. As such only labels are defined in these examples, which one must use .=. to construct into the tagged values that are directly employed by HaskRel as relational attributes.+While still letting one use sno, sName, etc as attribute identifiers in+functions. However, doing this consistently requires some rather convoluted+type-level acrobatics that causes exponential growth in function definition+complexity. As such only labels are defined in these examples, which one must+use .=. to construct into the tagged values that are directly employed by+HaskRel as relational attributes. -An alternative would be https://hackage.haskell.org/package/HList/docs/Data-HList-Labelable.html, though HaskRel records don't build on lens and can't accomodate that approach as-is.+An alternative would be+https://hackage.haskell.org/package/HList/docs/Data-HList-Labelable.html, though+HaskRel records don't build on lens and can't accomodate that approach as-is. -} @@ -74,13 +86,20 @@ ("S4", "P5", 400) ] :: Relation '[SNO, PNO, QTY] {--Also note how the attribute names above, SNO etc, are declared type synonyms in Definition.hs, and not ad-hoc. An equivalent ad-hoc definition would be:+Also note how the attribute names above, SNO etc, are declared type synonyms in+Definition.hs, and not ad-hoc. An equivalent ad-hoc definition would be: Relation '[Tagged "sno" String, Tagged "pno" String, Tagged "qty" Integer] -An alternative approach is to define constructors for each label, which would also make it more type-safe:+An alternative approach is to define constructors for each label, which would+also make it more type-safe:+ sno' a = (Label :: Label "sno") .=. a :: SNO -There may be a way to build the functions of the relational algebra such that they accept both "Label l" and "v -> Tagged l v" types, which would make it possible that instead of label constants one could just define constructors and used those against the functions of the relational algebra, but an investigation into that revealed no attempt that didn't have an unacceptable drawback.+There may be a way to build the functions of the relational algebra such that+they accept both "Label l" and "v -> Tagged l v" types, which would make it+possible that instead of label constants one could just define constructors and+used those against the functions of the relational algebra, but an investigation+into that revealed no attempt that didn't have an unacceptable drawback. -}
examples/SuppliersPartsDB/Definition.hs view
@@ -28,7 +28,10 @@ Which is what one must append to "let " to do the same in GHCi. -} --- Tagged value/attribute constructors. These are convenient to have since the label names collide with the label functions when punning, which is a particular nuisance when using 'extend' to alter an attribute:+{- Tagged value/attribute constructors. These are convenient to have since the+label names collide with the label functions when punning, which is a particular+nuisance when using 'extend' to alter an attribute:+-} _sno a = sno .=. a :: SNO _sName a = sName .=. a :: SName _status a = status .=. a :: Status@@ -62,7 +65,9 @@ type P = '[PNO, PName, Color, Weight, City] --- "Relvar S" could be used instead, but when doing ":t s" it's nicer to list the attributes.+{- "Relvar S" could be used instead, but when doing ":t s" it's nicer to list the+attributes.+-} s :: Relvar '[SNO, SName, Status, City] s = Relvar ( dbPath ++ "S.rv" ) sp :: Relvar '[SNO, PNO, QTY]
examples/SuppliersPartsDB/README.md view
@@ -41,7 +41,7 @@ (Note that certain combinations of operating systems and browsers don't seem to want to display the double line of the tables correctly. On OS X 10.8 Google Chrome doesn't, whereas Firefox seems to do just fine. See also the [Wikipedia entry on box-drawing characters](http://en.wikipedia.org/wiki/Box-drawing_character#Examples).) -Relation values named s', p' and sp' are also defined, of the same values as above.+Relation constants named s', p' and sp' are also defined, of the same values as above. >>> :t s s :: Relvar '[SNO, SName, Status, City]
examples/SuppliersPartsExample.hs view
@@ -8,7 +8,8 @@ -- TODO: Does this have more performance issues than the TIP based version? module SuppliersPartsExample where -import Database.HaskRel.Relational.Definition ( Relation, RTuple, Attr, relation, relation', tableDum, tableDee, empty, rTuple, pt, rPrint )+import Database.HaskRel.Relational.Definition (+ Relation, RTuple, Attr, relation, relation', tableDum, tableDee, empty, rTuple, pt, rPrint ) import Database.HaskRel.RDBMS hiding ( p ) import Database.HaskRel.Support@@ -28,6 +29,8 @@ assign s s' assign sp sp' assign p p'+ putStrLn $ "\nRecreating local relvars at ."+ assign spq spq' putStrLn "\nDone." @@ -41,7 +44,6 @@ pt$ sp snrt2ndExamples - r1 = relation [rTuple (sno .=. "S2", pno .=. "P1"), rTuple (sno .=. "S2", pno .=. "P2"), rTuple (sno .=. "S3", pno .=. "P2"),@@ -54,15 +56,32 @@ rTuple (sno .=. "S4", pnoRel .=. relation [pno .=. "P2" .*. emptyRecord, pno .=. "P4" .*. emptyRecord, pno .=. "P5" .*. emptyRecord])] --- TODO: This should be a relvar, check that RVAs work for them.-spq = relation [rTuple (sno .=. "S5", sName .=. "Adams", status .=. 30, city .=. "Athens", pq .=. relation []),- rTuple (sno .=. "S1", sName .=. "Smith", status .=. 20, city .=. "London", pq .=. relation [rTuple (pno .=. "P1", qty .=. 300), rTuple (pno .=. "P2", qty .=. 200), rTuple (pno .=. "P3", qty .=. 400), rTuple (pno .=. "P4", qty .=. 200), rTuple (pno .=. "P5", qty .=. 100), rTuple (pno .=. "P6", qty .=. 100)]),- rTuple (sno .=. "S2", sName .=. "Jones", status .=. 10, city .=. "Paris", pq .=. relation [rTuple (pno .=. "P1", qty .=. 300), rTuple (pno .=. "P2", qty .=. 400)]),- rTuple (sno .=. "S3", sName .=. "Blake", status .=. 30, city .=. "Paris", pq .=. relation [rTuple (pno .=. "P2", qty .=. 200)]),- rTuple (sno .=. "S4", sName .=. "Clark", status .=. 20, city .=. "London", pq .=. relation [rTuple (pno .=. "P2", qty .=. 200), rTuple (pno .=. "P4", qty .=. 300), rTuple (pno .=. "P5", qty .=. 400)])]+spq = Relvar "SPQ.rv"+ :: Relvar '[Attr "sno" String,+ Attr "sName" String,+ Attr "status" Integer,+ Attr "city" String,+ Attr "pq" (Relation '[Attr "pno" String,+ Attr "qty" Integer])] --- | "undefined" is used liberally enough that a concise alias for it is convenient.-ü = undefined+spq' = relation [rTuple (sno .=. "S5", sName .=. "Adams", status .=. 30, city .=. "Athens",+ pq .=. relation []),+ rTuple (sno .=. "S1", sName .=. "Smith", status .=. 20, city .=. "London",+ pq .=. relation [rTuple (pno .=. "P1", qty .=. 300),+ rTuple (pno .=. "P2", qty .=. 200),+ rTuple (pno .=. "P3", qty .=. 400),+ rTuple (pno .=. "P4", qty .=. 200),+ rTuple (pno .=. "P5", qty .=. 100),+ rTuple (pno .=. "P6", qty .=. 100)]),+ rTuple (sno .=. "S2", sName .=. "Jones", status .=. 10, city .=. "Paris",+ pq .=. relation [rTuple (pno .=. "P1", qty .=. 300),+ rTuple (pno .=. "P2", qty .=. 400)]),+ rTuple (sno .=. "S3", sName .=. "Blake", status .=. 30, city .=. "Paris",+ pq .=. relation [rTuple (pno .=. "P2", qty .=. 200)]),+ rTuple (sno .=. "S4", sName .=. "Clark", status .=. 20, city .=. "London",+ pq .=. relation [rTuple (pno .=. "P2", qty .=. 200),+ rTuple (pno .=. "P4", qty .=. 300),+ rTuple (pno .=. "P5", qty .=. 400)])] -- | As assumed on page 116 pX = p `extendA` (\[pun|weight|] -> status .=. ( floor ( weight / 4 ) * 10 ))@@ -94,11 +113,11 @@ -- putStrLn "\n119" rPrint$- let r1 = ( renameA s (sno `as` (Label :: Label "sa")) ) `project` (ü :: Labels '["sa", "sno"])- r2 = ( renameA s (sno `as` (Label :: Label "sb")) ) `project` (ü :: Labels '["sb", "sno"])+ let r1 = ( renameA s (sno `as` (Label :: Label "sa")) ) `project` (Proxy :: Labels '["sa", "sno"])+ r2 = ( renameA s (sno `as` (Label :: Label "sb")) ) `project` (Proxy :: Labels '["sb", "sno"]) r3 = r1 `nJoin` r2 r4 = r3 `restrict` (\[pun|sa sb|] -> sa < sb)- in r4 `project` (ü :: Labels '["sa", "sb"])+ in r4 `project` (Proxy :: Labels '["sa", "sb"]) -- putStrLn "\n122" rPrint$ ( renameA s (city `as` (Label :: Label "sCity"))@@ -122,7 +141,7 @@ rPrint$ ( ( extend p (\[pun|weight|] -> (Label::Label "gmwt") .=. weight * 454 .*. emptyRecord)) `restrict` (\[pun|gmwt|] -> gmwt > 7000) )- `project` (ü :: Labels '["pno", "gmwt"])+ `project` (Proxy :: Labels '["pno", "gmwt"]) -- putStrLn "\n136" {-@@ -175,8 +194,8 @@ -- ... or readRelvar s could be bound and an expression that takes the -- relation value can be used: do s' <- readRelvar s- putStrLn $ show $ count (s' `project` (ü :: Labels '["status"]))- -- But note: Both "count s" and "count (s `project` (ü :: Labels+ putStrLn $ show $ count (s' `project` (Proxy :: Labels '["status"]))+ -- But note: Both "count s" and "count (s `project` (Proxy :: Labels -- '["status"]))" work just fine in GHCi, without any do block or similar (in -- contrast with lambda expressions). putStrLn "1."@@ -185,7 +204,7 @@ do spx <- readRelvar sp putStrLn $ show $ sum $ agg qty spx putStrLn "2."- do qtys <- aggU ( sp `project` (ü :: Labels '["qty"]) )+ do qtys <- aggU ( sp `project` (Proxy :: Labels '["qty"]) ) putStrLn $ show $ sum qtys putStrLn "3." -- "agg" is a touch more specialized than Tutorial D's "agg op"s, but lists of@@ -226,7 +245,8 @@ -} putStrLn "4:" -- See function "updates" below for 5.- -- TODO: Fix this after rename has been improved+ -- TODO: Fix this after rename has been improved (it always results in IO at+ -- the moment) do spx <- readRelvar sp rPrint$ sp `restrict` (\( image -> ii ) -> count ( ii $ renameA (renameA spx (sno `as` sn)) (pno `as` pn) )@@ -237,22 +257,27 @@ -- putStrLn "\n152" rPrint$ group r1 (rHdr (pno)) (pnoRel .=.)- rPrint$ r4 `ungroup` (ü :: Label "pnoRel")+ rPrint$ r4 `ungroup` pnoRel putStrLn$ show $ ( group r1 (rHdr (pno)) (pnoRel .=.) ) == relRearrange r4- putStrLn$ show $ r4 `ungroup` (ü :: Label "pnoRel") == relRearrange r1+ putStrLn$ show $ r4 `ungroup` pnoRel == relRearrange r1 -- putStrLn "\n154 (using pt instead of rPrint here to show the attribute types)" pt$ do spx <- readRelvar sp extend s (\( image -> ii ) -> pq .=. (ii spx) .*. emptyRecord) -- putStrLn "\n155"- rPrint$ ( spq `ungroup` (ü :: Label "pq") ) `restrict` (\[pun|pno|] -> pno == "P2") `project` (rHdr (sno))- rPrint$ spq `restrict` (\[pun|sno|] -> sno == "S2") `ungroup` (ü :: Label "pq") `project` (rHdr (sno))+ rPrint$ ( spq `ungroup` pq ) `restrict` (\[pun|pno|] -> pno == "P2") `project` (rHdr (sno))+ rPrint$ spq `restrict` (\[pun|sno|] -> sno == "S2") `ungroup` pq `project` (rHdr (sno)) rPrint$ sp `restrict` (\[pun|pno|] -> pno == "P2") `project` (rHdr (sno)) rPrint$ sp `restrict` (\[pun|sno|] -> sno == "S2") `project` (rHdr (pno))--- TODO: putStrLn "\n156"; Insert and update examples in function "updates"--- below, requires spq as a relvar. See above for the query involving an image--- relation.++-- TODO: putStrLn "\n156"; Insert example would work, but the example of insert+-- into an RVA inside an update statement is not supported. This could perhaps+-- be elegantly supported. Also, SRT2ndEd p. 156 states "the non RVA design+-- itself is asymmetric" -- should this be "the RVA design itself is+-- asymmetric"?+-- See above for the query involving an image relation.+ putStrLn "\n157" rPrint$ do spx <- readRelvar sp@@ -266,12 +291,13 @@ putStrLn "\n158" rPrint$ p `restrict` (\[pun|city|] -> city == "Paris")- `extendA` (\_ -> city .=. "Nice")- `extendA` (\[pun|weight|] -> _weight $ 2 * weight)+ `extend` (\[pun|weight|] -> case (2 * weight, "Nice")+ of (weight, city) -> [pun|weight city|] ) -- Equivalent query that does not rely on extend's behavior of replacing attributes: rPrint$ let r1 = p `restrict` (\[pun|city|] -> city == "Paris")- r2 = r1 `extendA` (\_ -> nc .=. "Nice" ) `extendA` (\[pun|weight|] -> nw .=. weight * 2)+ r2 = r1 `extend` (\[pun|weight|] -> case (2 * weight, "Nice")+ of (nw, nc) -> [pun|nw nc|] ) r3 = r2 `projectAllBut` (rHdr (city,weight)) in ( r3 `rename` nAs ((nc `as` city),(nw `as` weight)) ) {-@@ -279,7 +305,7 @@ follows in GHCi: *SuppliersPartsExample> let s1 = p `restrict` (\[pun|city|] -> city == "Paris" )-*SuppliersPartsExample> let s2 = ( p `restrict` (\[pun|city|] -> city == "Paris" ) ) `extendA` (\_ -> city .=. "Nice" ) `extendA` (\[pun|weight|] -> (Label::Label "weight") .=. weight * 2)+*SuppliersPartsExample> let s2 = ( p `restrict` (\[pun|city|] -> city == "Paris" ) ) `extend` (\[pun|weight|] -> case (2 * weight, "Nice") of (weight, city) -> [pun|weight city|] ) *SuppliersPartsExample> p ≔ ( p `minus` s1 ) `union` s2 -} -- Pages 159-163: Recursion. TODO: On hold.
src/Database/HaskRel/FWTabulation.hs view
@@ -16,7 +16,9 @@ import Data.List ( intercalate ) --- | Given two lists of orderables, produces a list of equal length where each element is that which is the maximum of those elements that are in the same position in the two input lists.+-- | Given two lists of orderables, produces a list of equal length where each+-- element is that which is the maximum of those elements that are in the same+-- position in the two input lists. maxLen :: Ord b => [b] -> [b] -> [b] maxLen = zipWith max @@ -40,8 +42,12 @@ buildColumns' [p] [t] str = buildOneColumn p t str buildColumns' (p:ps) (t:ts) str = buildColumns' ps ts $ buildOneColumn p t str ++ " │ " --- These error messages stem from before HList was used and a less disciplined form was used instead, they should be impossible to trigger today barring the introduction of bugs.--- These are pure presentation functions, so it's okay to just inform of the error rather than programmatically signaling it+-- These error messages stem from before HList was used and a less disciplined+-- form was used instead, they should be impossible to trigger today barring the+-- introduction of bugs.++-- These are pure presentation functions, so it's okay to just inform of the+-- error rather than programmatically signaling it buildColumns' [] [t] str = str ++ "Internal Error: " ++ t buildColumns' [] (t:ts) str = str ++ "Internal Error: " ++ intercalate " │ " (t:ts) @@ -49,17 +55,23 @@ buildColumns' (p:ps) [] str = str ++ "Internal Error: " ++ intercalate " │ " ( map show $ p:ps ) --- | Gets the maximum length of each column of a value consisting of a header and a single line+{-| Gets the maximum length of each column of a value consisting of a header and+a single line+-} colWidths :: (Foldable t, Foldable t1) => [[t1 a1]] -> [t a] -> [Int] colWidths l hdr = foldl1 maxLen [ map length hdr, allColWidths l ] --- | Gets the width of the columns of a value in when presented in a columnar format.+{-| Gets the width of the columns of a value in when presented in a columnar+format.+-} allColWidths :: Foldable t => [[t a]] -> [Int] allColWidths = map $ maximum . map length --- | Gets the maximum length of each column of a value consisting of a header and zero or more lines+{-| Gets the maximum length of each column of a value consisting of a header and+zero or more lines+-} nColWidths :: (Foldable t, Foldable t1) => [[[t1 a1]]] -> [t a] -> [Int] nColWidths ll hdr = foldl1 maxLen $ map length hdr : mapListLen ll @@ -104,7 +116,9 @@ buildRow' hPad (strRep:strRepX) = ( "│ " ++ buildColumns hPad strRep ++ " │" ) : buildRow' hPad strRepX --- | Transposes a list of lists of lists, padding the lists of the second dimension with empty lists if they are shorter than the other rows.+{-| Transposes a list of lists of lists, padding the lists of the second+dimension with empty lists if they are shorter than the other rows.+-} padTranspose :: [[[t]]] -> [[[t]]] padTranspose x = padTranspose' x ( maximum ( map length x ) - 1 )
src/Database/HaskRel/HFWTabulation.hs view
@@ -5,19 +5,30 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} --- TODO: Can this be rewritten to be less reliant on Data.Typeable? Should be possible to just use it for getting the field type names, and not for parsing the entire type.--- TODO: Check that nothing is missing from the export list; only HaskRel's dependencies are known to be covered.--- TODO: New functions that did a tiny bit more than the last ones have been added haphazardly, clean up this and the users of this.+{-+TODO: Can this be rewritten to be less reliant on Data.Typeable? Should be+possible to just use it for getting the field type names, and not for parsing+the entire type. +TODO: Check that nothing is missing from the export list; only HaskRel's+dependencies are known to be covered.++TODO: New functions that did a tiny bit more than the last ones have been added+haphazardly, clean up this and the users of this.+-} {-| Module : HFWTabulation-Description : Presentation of HList values in a two-dimensional fixed-width font form.+Description : Presentation of HList values in a two-dimensional fixed-width font+ form. Copyright : © Thor Michael Støre, 2015 License : GPL v2 without "any later version" clause Maintainer : thormichael át gmail døt com Stability : experimental -HList fixed-width tabular presentation. Presentation of HList values in a two-dimensional, tabular fixed-width font form with a header consisting of labels and optionally types. Only records are supported by this module, see `TIPFWTabulation` for support for TIPs.+HList fixed-width tabular presentation. Presentation of HList values in a+two-dimensional, tabular fixed-width font form with a header consisting of+labels and optionally types. Only records are supported by this module, see+`TIPFWTabulation` for support for TIPs. -} module Database.HaskRel.HFWTabulation ( HFWPresent ( hfwPrint, hfwPrintTyped, hfwPrintTypedTS ),@@ -73,7 +84,10 @@ hTIPTS :: s -> String hTIPSetTS :: s -> String --- TODO: This only supports showing a single type synonym, it doesn't support showing "Set ( TIP '[Foo] )", for instance. This is sufficient for HaskRel, which only uses full type synonyms, for instance "Relation '[Foo]".+{- TODO: This only supports showing a single type synonym, it doesn't support+showing "Set ( TIP '[Foo] )", for instance. This is sufficient for HaskRel,+which only uses full type synonyms, for instance "Relation '[Foo]".+-} instance HListTypeSynonym EmptyTS where hRecTS _ = "Record" hRecSetTS _ = "Set-Record"@@ -85,7 +99,9 @@ showTR :: TypeRep -> String showTR = showTRTS EmptyTS --- TODO: Record presentation is copy'n'paste of TIP presentation, which gives the unfortunate 'Record '["foo"]'+{- TODO: Record presentation is copy'n'paste of TIP presentation, which gives+the unfortunate 'Record '["foo"]'+-} -- | Show a TypeRep, using the given type synonyms showTRTS :: HListTypeSynonym ts => ts -> TypeRep -> String showTRTS ts t
src/Database/HaskRel/Order.hs view
@@ -25,7 +25,11 @@ -- import Database.HaskRel.Relational.TIP.Definition -- import Data.Ord ( Down(Down) ) -{- Converts a relation to an ordered set of r-tuples, ordered ascendingly. Because Data.Set orders its elements (which is a no-no in relational theory and a quality we must disregard in the context of it), and as such functions as an ordered set, this will just rearrange the attributes.+{- Converts a relation to an ordered set of r-tuples, ordered ascendingly. Because+Data.Set orders its elements (which is a no-no in relational theory and a+quality we must disregard in the context of it), and as such functions as an+ordered set, this will just rearrange the attributes.+ ordRTupSet :: (Ord (HList l), TagUntagFD a ta, TagUntagFD a1 l, HProject (HList a) (HList a1)) =>
src/Database/HaskRel/RDBMS.hs view
@@ -1,16 +1,22 @@ {-| Module : Database.HaskRel.RDBMS-Description : A re-export of the modules that form the HList record based HaskRel library++Description : A re-export of the modules that form the HList record based+ HaskRel library Copyright : © Thor Michael Støre, 2015 License : GPL v2 without "any later version" clause Maintainer : thormichael át gmail døt com Stability : experimental -Exports the pertinent parts of HaskRel building on HList records. This gives most features of the relational algebra, relation variable support, HList CommonMain, as well as certain non-relational features such as ordering.+Exports the pertinent parts of HaskRel building on HList records. This gives+most features of the relational algebra, relation variable support, HList+CommonMain, as well as certain non-relational features such as ordering. -} --- TODO: Should call this something else than RDBMS seeing as it's so far just a DBMS + relational algebra+{- TODO: Should call this something else than RDBMS seeing as it's so far just a+DBMS + relational algebra+-} module Database.HaskRel.RDBMS ( module Data.HList.CommonMain, -- * The relational model of database management: A subset thereof@@ -22,7 +28,9 @@ module Database.HaskRel.Order, module Database.HaskRel.Support ) where --- TODO: Only import relevant parts of HList. Variant, TIPs and TICs aren't required.+{- TODO: Only import relevant parts of HList. Variant, TIPs and TICs aren't+required.+-} import Data.HList.CommonMain import Data.Tagged (Tagged)
src/Database/HaskRel/Relational/Algebra.hs view
@@ -18,9 +18,18 @@ Relational algebra based on HList records. -It is important to note that, in order to build a straight forward foundation, this module defines pure functions, viz. they only operate upon relational /values/, not relvars or the result of expressions involving relvars. See "Database.HaskRel.Relational.Expression" for functions that function as conveyed by the relational model, which are the ones that are intended to be used directly.+It is important to note that, in order to build a straight forward foundation,+this module defines pure functions, viz. they only operate upon relational+/values/, not relvars or the result of expressions involving relvars. See+"Database.HaskRel.Relational.Expression" for functions that function as conveyed+by the relational model, which are the ones that are intended to be used+directly. -All the examples in "Database.HaskRel.Relational.Expression" are defined in terms of the relvars s, p and sp; to run the examples in that module with the functions of this module one can use the relation values s', p' and sp' instead. The script examples\/algebraExample.sh starts a GHCi session with the imports and pragmas required to run these examples.+All the examples in "Database.HaskRel.Relational.Expression" are defined in+terms of the relvars s, p and sp; to run the examples in that module with the+functions of this module one can use the relation values s', p' and sp'+instead. The script examples\/algebraExample.sh starts a GHCi session with the+imports and pragmas required to run these examples. -} module Database.HaskRel.Relational.Algebra ( -- * Operators of the relational algebra@@ -111,7 +120,9 @@ Relation a -> tr -> Relation r rename r l = Data.Set.map (rhRenameLabels l) r --- | Renames a single attribute. See 'Database.HaskRel.Relational.Expression.renameA'+{- | Renames a single attribute. See+'Database.HaskRel.Relational.Expression.renameA'+-} renameA :: forall l1 v1 r v' v l . (Ord (HExtendR (Tagged l1 v1) (r v')), HasField l (r v) v1, HExtend (Tagged l1 v1) (r v'), HDeleteAtLabel r l v v') =>@@ -119,7 +130,8 @@ renameA r frto = Data.Set.map (hRenameLabel (Label::Label l) (untag frto)) r -{- | Extends the given relation with the r-tuple resulting from the second argument. Existing attributes with the same name will be replaced.+{- | Extends the given relation with the r-tuple resulting from the second+argument. Existing attributes with the same name will be replaced. See 'Database.HaskRel.Relational.Expression.extend'. -}@@ -131,7 +143,9 @@ Relation r' -> (Record r' -> Record r) -> Relation (HAppendListR r r'1) extend r f = Data.Set.map (\t -> f t .<++. t ) r -{- | Extends the given relation with the attribute resulting from the second argument. If an attribute with the same name exists then it will be replaced. This allows for the function of the second argument to be simpler.+{- | Extends the given relation with the attribute resulting from the second+argument. If an attribute with the same name exists then it will be+replaced. This allows for the function of the second argument to be simpler. See 'Database.HaskRel.Relational.Expression.extendA'. -}@@ -140,11 +154,17 @@ HDeleteAtLabel r l v v') => Set (r v) -> (r v -> Tagged l e) -> Set (HExtendR (Tagged l e) (r v')) extendA r f = Data.Set.map (\t -> f t .*. hDeleteAtLabel (Label::Label l) t) r--- TODO: An attempt to define a class with instances for what is now extend and extendA resulted in something that broke inference. Try again? Is it also possible make extendA a class, with an instance that uses .*. without hDeleteByLabel, and one that uses .@.? I can't see a way to. Probably better to make a quasiquoter, as mentioned in Expression.hs. +-- TODO: An attempt to define a class with instances for what is now extend and+-- extendA resulted in something that broke inference. Try again? Is it also+-- possible make extendA a class, with an instance that uses .*. without+-- hDeleteByLabel, and one that uses .@.? I can't see a way to. Probably better+-- to make a quasiquoter, as mentioned in Expression.hs. -{- | Disjoint extension. Extends the given relation with the result of the second argument, as 'extend', but without deleting any that exist. +{- | Disjoint extension. Extends the given relation with the result of the second+argument, as 'extend', but without deleting any that exist.+ See 'Database.HaskRel.Relational.Expression.dExtend'. -} dExtend@@ -157,8 +177,9 @@ Relation r' -> (Record r' -> Record r) -> Relation (HAppendListR r r'1) dExtend = extend -{- |-Disjoint extension of a single attribute. Extends the given relation with the result of the second argument, as 'extend', but without deleting any that exist. @l@ cannot already have any attribute @e@.+{- | Disjoint extension of a single attribute. Extends the given relation with the+result of the second argument, as 'extend', but without deleting any that+exist. @l@ cannot already have any attribute @e@. See 'Database.HaskRel.Relational.Expression.dExtendA'. -}@@ -167,7 +188,8 @@ dExtendA r f = Data.Set.map (\t -> f t .*. t) r --- TODO: imageExtend: Use type level concatenation to build a label from the labels of the body+-- TODO: imageExtend: Use type level concatenation to build a label from the+-- labels of the body {- type family (++) (as :: [k]) (bs :: [k]) :: [k] where (++) a '[] = a@@ -175,7 +197,9 @@ (++) (a ': as) bs = a ': (as ++ bs) -} -{- | Extends the first given relation with an attribute resulting from imaging each tuple of said relation against the second given relation. This gives a superset of the information given by SQL @RIGHT OUTER JOIN@.+{- | Extends the first given relation with an attribute resulting from imaging+each tuple of said relation against the second given relation. This gives a+superset of the information given by SQL @RIGHT OUTER JOIN@. See 'Database.HaskRel.Relational.Expression.imageExtendL'. -}@@ -193,7 +217,9 @@ imageExtendL r1 r2 l = dExtendA r1 (\t -> l .=. t `image` r2) -{- | Restricts the given relation according to the given predicate. Note that this is the well known 'WHERE' operator of both SQL and Tutorial D, but since "where" is a reserved keyword in Haskell it is named "restrict".+{- | Restricts the given relation according to the given predicate. Note that this+is the well known 'WHERE' operator of both SQL and Tutorial D, but since "where"+is a reserved keyword in Haskell it is named "restrict". See 'Database.HaskRel.Relational.Expression.restrict'. -}@@ -213,7 +239,8 @@ Relation t -> proxy ls -> Relation a project r h = Data.Set.map ( hProjectByLabels h ) r -{- | Projects the given relation on the heading of said given relation minus the given heading.+{- | Projects the given relation on the heading of said given relation minus the+given heading. See 'Database.HaskRel.Relational.Expression.projectAllBut'. -}@@ -236,9 +263,14 @@ union r1 r2 = Data.Set.union r1 ( relRearrange r2 ) --- TODO: This should not just cause "error". Perhaps an Either String (Relation a) or Either (Relation ?) (Relation a) instead of causing an error. Will have to amend at least HFWPresent if so.-{- | Performs a disjoint union between the two relvars. This is a union of disjoint relations, where a runtime error is raised if the operands are not disjoint.+-- TODO: This should not just cause "error". Perhaps an Either String (Relation+-- a) or Either (Relation ?) (Relation a) instead of causing an error. Will have+-- to amend at least HFWPresent if so. +{- | Performs a disjoint union between the two relvars. This is a union of+disjoint relations, where a runtime error is raised if the operands are not+disjoint.+ See 'Database.HaskRel.Relational.Expression.dUnion'. -} dUnion@@ -258,7 +290,10 @@ {-| The intersection of two relations. -Note how the name is different from Data.Set, where the comparable function is named "intersection". This is due to it being referred to as "intersect" in material describing the relational model; specifically named \"INTERSECT\" in Tutorial D.+Note how the name is different from Data.Set, where the comparable function is+named "intersection". This is due to it being referred to as "intersect" in+material describing the relational model; specifically named \"INTERSECT\" in+Tutorial D. See 'Database.HaskRel.Relational.Expression.intersect'. -}@@ -272,7 +307,8 @@ {-| The difference of two relations. -The "minus" term is used in material describing relational theory; specifically Tutorial D names the operator \"MINUS\".+The "minus" term is used in material describing relational theory; specifically+Tutorial D names the operator \"MINUS\". See 'Database.HaskRel.Relational.Expression.minus'. -}@@ -284,7 +320,11 @@ Relation l -> Relation r -> Relation l minus l r = difference l $ relRearrange r -{-| The difference of two relations. This differs from 'minus' in that the attribute order of the second argument takes precedence, which is neccessary to swap precedence since 'minus' is non-commutative. This function is as such equal to 'minus' as far as relational theory is concerned, the difference is on a lower level of abstraction.+{-| The difference of two relations. This differs from 'minus' in that the+attribute order of the second argument takes precedence, which is neccessary to+swap precedence since 'minus' is non-commutative. This function is as such equal+to 'minus' as far as relational theory is concerned, the difference is on a+lower level of abstraction. -} minus_ :: (Ord (HList l), HRearrange3 (LabelsOf l) r l,@@ -367,7 +407,8 @@ then hAppend t1' cP : b else b -{- | The cartesian product of two relations. A specialized natural join; the natural join between two relations with disjoint headings.+{- | The cartesian product of two relations. A specialized natural join; the+natural join between two relations with disjoint headings. See 'Database.HaskRel.Relational.Expression.times'. -}@@ -387,7 +428,8 @@ times = naturalJoin --- TODO: Get this working, now it just ends up with overlapping error instead of the Fail IsEmpty+-- TODO: Get this working, now it just ends up with overlapping error instead of+-- the Fail IsEmpty data IsEmpty -- | Failure class restricting a type-level operation to a non-empty result.@@ -395,13 +437,21 @@ instance NotEmpty l -- Is there any way to match on "anything that is not '[]"? instance ( Fail IsEmpty ) => NotEmpty '[] --- Finding out that when : is induced one must simply use ': instead was frustrating+-- Finding out that when : is induced one must simply use ': instead was+-- frustrating -{-| Performs a natural join between two relations with intersecting headings. A specialized natural join.+{-| Performs a natural join between two relations with intersecting headings. A+specialized natural join. -A join upon relations r1, r2 where the intersection of the heading of r1 and of r2 is not empty; the headings are not disjoint. This is the complement of 'times' that together with it forms a natural join; all that would be disallowed for @times@ is allowed here and vice-versa. The name is what I quickly settled on, suggestions for a better one would be welcome. (Attribute-Intersecting Natural Join is another candidate.)+A join upon relations r1, r2 where the intersection of the heading of r1 and of+r2 is not empty; the headings are not disjoint. This is the complement of+'times' that together with it forms a natural join; all that would be disallowed+for @times@ is allowed here and vice-versa. The name is what I quickly settled+on, suggestions for a better one would be welcome. (Attribute-Intersecting+Natural Join is another candidate.) -This function doesn't have a specific identity value, although it holds that @r \`interJoin\` r = r@+This function doesn't have a specific identity value, although it holds that+@r \`interJoin\` r = r@ See 'Database.HaskRel.Relational.Expression.interJoin'. -}@@ -435,7 +485,8 @@ iJoin = interJoin -{- | Performs a semi-join of the first given relation against the second given relation.+{- | Performs a semi-join of the first given relation against the second given+relation. See 'Database.HaskRel.Relational.Expression.matching'. -}@@ -486,7 +537,8 @@ Record t -> Record l1 -> Bool tIntersectEq t1 t2 = hProjectByLabels ( labelsOf t1 ) t2 `unordRecEq` hProjectByLabels ( labelsOf t2 ) t1 -{- | Performs a semi-difference of the first given relation against the second given relation.+{- | Performs a semi-difference of the first given relation against the second+given relation. Aka. antijoin: @@ -506,7 +558,8 @@ Relation t -> Relation l1 -> Relation t notMatching = semiDiff --- | Alias of 'notMatching'. See 'Database.HaskRel.Relational.Expression.semiDiff'.+{- | Alias of 'notMatching'. See+'Database.HaskRel.Relational.Expression.semiDiff'. -} semiDiff :: (Eq (HList l), Ord (HList t), HRearrange3 (LabelsOf l) r l, HLabelSet (LabelsOf l), HLabelSet (LabelsOf r),@@ -538,8 +591,14 @@ {- Image Relations-Definition: Let relations r1 and r2 be joinable (i.e., such that attributes with the same name are of the same type); let t1 be a tuple of r1; let t2 be a tuple of r2 that has the same values for those common attributes as tuple t1 does; let relation r3 be that restriction of r2 that contains all and only such tuples t2; and let relation r4 be the projection of r3 on all but those common attributes. Then r4 is the image relation (with respect to r2) corresponding to t1.- - Chris J. Date, SQL and Relational Theory 2nd ed. p 136++Definition: Let relations r1 and r2 be joinable (i.e., such that attributes with+the same name are of the same type); let t1 be a tuple of r1; let t2 be a tuple+of r2 that has the same values for those common attributes as tuple t1 does; let+relation r3 be that restriction of r2 that contains all and only such tuples t2;+and let relation r4 be the projection of r3 on all but those common+attributes. Then r4 is the image relation (with respect to r2) corresponding to+t1. - Chris J. Date, SQL and Relational Theory 2nd ed. p 136 -} -- Project tuple on relation heading@@ -560,13 +619,14 @@ restrictByRTuple r t = Data.Set.filter ( ( t == ) . hProjectByLabels ( labelsOf t ) ) r -- TODO: It would possibly be more performant and still correct to use partition--- instead of filter, and feed the remainder part of the result of that back into--- this function as parameter r.+-- instead of filter, and feed the remainder part of the result of that back+-- into this function as parameter r. {-| The image of a relation corresponding to an r-tuple. -An application of the first argument only, an r-tuple, to this function yields what is known as the @!!@ operator in Tutorial D.+An application of the first argument only, an r-tuple, to this function yields+what is known as the @!!@ operator in Tutorial D. >>> let qtySum = Label::Label "qtySum" >>> :{@@ -588,16 +648,6 @@ image t r = projectAllBut (restrictByRTuple r $ projectTOnR t r) (labelsOf t) -{---In the last example query for `image`, if one replaces the first relvar, "s", with relvar "sp" then the meaning of the query changes to:-"Get the quantities of items in stock for all suppliers that supply any products."--This form of semi aggregation, basically splitting a relation into two and aggregating one half, is common enough to warrant a specialized function.----}- extendAByImage :: (Eq (HList l), Ord v, Ord (HList l1), Ord (HList r'), HLabelSet (LabelsOf l), HLabelSet (Label t ': LabelsOf l1),@@ -609,9 +659,11 @@ -> (Relation r' -> Tagged t v) -> Relation (Tagged t v ': l1) extendAByImage rel relP fOut = dExtendA relP $ fOut . ( `image` rel ) --- TODO: group/groupAllBut can most likely be optimized by folding over them in a manner equivalent to this (somewhat) relational approach.+-- TODO: group/groupAllBut can most likely be optimized by folding over them in+-- a manner equivalent to this (somewhat) relational approach. -{-| Groups the given attributes of the given relation into a given new relation valued attribute.+{-| Groups the given attributes of the given relation into a given new relation+valued attribute. As the Tutorial D GROUP operator, not SQL GROUP BY. @@ -628,9 +680,16 @@ -> proxy ks -> (Relation r' -> Tagged t v) -> Relation (Tagged t v ': l1) group rel attsIn = extendAByImage rel $ rel `projectAllBut` attsIn --- TODO: That "group" supports both grouping into an RVA and grouping like group by is a bit of a design accident, consider making group only accept a label, and having a function that generalizes group that takes a function as group does now. Further consider a method that just takes a relation and a function, deriving the labels from the function result (I tried to do this but ran into issues).+{- TODO: That "group" supports both grouping into an RVA and grouping like group+by is a bit of a design accident, consider making group only accept a label, and+having a function that generalizes group that takes a function as group does+now. Further consider a method that just takes a relation and a function,+deriving the labels from the function result (I tried to do this but ran into+issues).+-} -{- | Groups the given relation on all but the given attributes into a given new attribute.+{- | Groups the given relation on all but the given attributes into a given new+attribute. See 'Database.HaskRel.Relational.Expression.groupAllBut'. -}@@ -652,7 +711,8 @@ >>> sp' == ungroup ( group sp' (rHdr (pno,qty)) (pq .=.)) pq True -Note the difference to 'Database.HaskRel.Relational.Expression.ungroup', which requires 'Database.HaskRel.Relational.Expression.rEq' for relational comparison.+Note the difference to 'Database.HaskRel.Relational.Expression.ungroup', which+requires 'Database.HaskRel.Relational.Expression.rEq' for relational comparison. -} ungroup :: (Eq (HList l), Ord (HList (HAppendListR t1 t2)),@@ -709,7 +769,8 @@ -> Relation (HAppendListR r r'1) summarize relA relB attsIn f = extendByImage relA ( relB `projectAllBut` attsIn ) f -{- | Auto-summarization. A specialization of 'summarize' with the same source and destination relation.+{- | Auto-summarization. A specialization of 'summarize' with the same source and+destination relation. See 'Database.HaskRel.Relational.Expression.aSummarize'. -}@@ -818,7 +879,9 @@ Relation l -> Relation r -> Bool isSubsetOf l r = Data.Set.isSubsetOf l ( relRearrange r ) -{- | Right-fold of an attribute of a relation (although a "right" fold doesn't make sense in the context of the relational model). Note that the value of the third argument, 'att', is not used and may be "undefined".+{- | Right-fold of an attribute of a relation (although a "right" fold doesn't+make sense in the context of the relational model). Note that the value of the+third argument, 'att', is not used and may be "undefined". See 'Database.HaskRel.Relational.Expression.rafoldr'. -}@@ -828,9 +891,12 @@ rafoldr f b a r = foldr ( f . (.!. a) ) b r -{- | Attribute value aggregation, a specialization of 'rafoldr' that aggregates the values of a single attribute into a list of the values the attribute type wraps.+{- | Attribute value aggregation, a specialization of 'rafoldr' that aggregates+the values of a single attribute into a list of the values the attribute type+wraps. -Note that the value of the first argument, 'att', is not used and may be "undefined".+Note that the value of the first argument, 'att', is not used and may be+"undefined". >>> sum $ agg qty sp' 3100@@ -844,14 +910,16 @@ unwrapUnary :: Record '[Tagged t t1] -> t1 unwrapUnary ( Record (Tagged v `HCons` HNil) ) = v -{- | Right-fold of the attribute of a unary relation.+{- | Right-fold of the attribute of an unary relation. See 'Database.HaskRel.Relational.Expression.rafoldrU'. -} rafoldrU :: Foldable t => (b1 -> b -> b) -> b -> t (Record '[Tagged t1 b1]) -> b rafoldrU f b r = foldr ( f . unwrapUnary ) b r -{- | Aggregation of the single attribute of a unary relation. A specialization of 'agg', and thus in turn of 'rafoldr', that aggregates the single attribute of a unary relation, without requiring the name of that attribute.+{- | Aggregation of the single attribute of an unary relation. A specialization of+'agg', and thus in turn of 'rafoldr', that aggregates the single attribute of an+unary relation, without requiring the name of that attribute. >>> sum $ aggU $ sp' `project` (rHdr (qty)) 1000
src/Database/HaskRel/Relational/Assignment.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE TypeFamilies, FlexibleContexts, ScopedTypeVariables #-} {-# LANGUAGE PolyKinds #-} --- TODO: dInsert and iDelete should not just invoke "error", but a proper fix is most likely to define a relational transaction that supports this correctly +-- TODO: dInsert and iDelete should not just invoke "error", but a proper fix is+-- most likely to define a relational transaction that supports this correctly {-| Module : Database.HaskRel.Relational.Assignment@@ -11,7 +12,10 @@ Maintainer : thormichael át gmail døt com Stability : experimental -Relational assignment and specalizations thereof. As with "Database.HaskRel.Relational.Algebra" this does not support relational expressions building on relvars, but defers that to "Database.HaskRel.Relational.Expression".+Relational assignment and specalizations thereof. As with+"Database.HaskRel.Relational.Algebra" this does not support relational+expressions building on relvars, but defers that to+"Database.HaskRel.Relational.Expression". -} module Database.HaskRel.Relational.Assignment ( -- * The primitive assignment function@@ -69,8 +73,8 @@ -- == Inserts -{-|-Inserts a relation into a relvar. This differs from SQL's INSERT; the relvar is updated to the union of the relvar and the relation value given as arguments.+{-| Inserts a relation into a relvar. This differs from SQL's INSERT; the relvar+is updated to the union of the relvar and the relation value given as arguments. See `Database.HaskRel.Relational.Expression.insert`. -}@@ -223,7 +227,13 @@ >>> count sp 9 -}--- TODO: Fix update count message to reflect the situation in the last example above, although this is tricky as this is most likely something that belongs naturally in the set level functions. Note however that it is not feasable to give update counts at all in RDBSMs, as keeping exact track of the cardinality of relvars constitutes an overhead that is in many cases unacceptable, and doesn't provide information that is as useful as a naïve mind might think anyhow.+-- TODO: Fix update count message to reflect the situation in the last example+-- above, although this is tricky as this is most likely something that belongs+-- naturally in the set level functions. Note however that it is not feasable to+-- give update counts at all in RDBSMs, as keeping exact track of the+-- cardinality of relvars constitutes an overhead that is in many cases+-- unacceptable, and doesn't provide information that is as useful as a naïve+-- mind might think anyhow. update :: (Ord (HList a), Read (HList (RecordValuesR a)), Show (HList (RecordValuesR a)), RecordValues a,@@ -242,12 +252,14 @@ doUpdate rv ( update' rv' p f ) -{-| Updates all tuples of a relvar. The second argument is a function that results in an attribute, making for a simpler function than for `update`.+{-| Updates all tuples of a relvar. The second argument is a function that results+in an attribute, making for a simpler function than for `update`. >>> updateA sp (\ [pun|pno|] -> pno == "P2" || pno == "P3" ) (\ [pun|qty|] -> _qty $ qty - 25) Updated 5 of 12 tuples in SuppliersPartsDB/SP.rv -}--- TODO: Can't get the type signature to compile, HUpdateAtLabel2 isn't exported from Data.HList.Record+-- TODO: Can't get the type signature to compile, HUpdateAtLabel2 isn't exported+-- from Data.HList.Record updateA rv p f = do rv' <- readRelvar rv doUpdate rv ( updateA' rv' p f )@@ -262,7 +274,9 @@ {-| Updates tuples of a relvar that match the given predicate. -In SQL and Tutorial D both the predicate of @UPDATE@ is an optional clause, but optional clauses isn't idiomatic Haskell, hence this separate updateAll function.+In SQL and Tutorial D both the predicate of @UPDATE@ is an optional clause, but+optional clauses isn't idiomatic Haskell, hence this separate updateAll+function. >>> updateAll sp (\ [pun|qty pno|] -> _qty ( qty - 25 ) .*. _pno ( pno ++ "X" ) .*. emptyRecord) Updated 12 tuples in SuppliersPartsDB/SP.rv@@ -290,7 +304,8 @@ rv' <- readRelvar rv doUpdateAll rv (updateAll' rv' f) -{-| Updates all tuples of a relvar. The second argument is a function that results in an attribute, making for a simpler function than for `updateAll`.+{-| Updates all tuples of a relvar. The second argument is a function that results+in an attribute, making for a simpler function than for `updateAll`. >>> updateAllA sp (\ [pun|qty|] -> _qty $ qty - 50) Updated 12 tuples in SuppliersPartsDB/SP.rv@@ -301,7 +316,8 @@ │ S1 │ P1 │ 250 │ ... -}--- TODO: Can't get the type signature to compile, HUpdateAtLabel2 isn't visible from Data.HList.Record+-- TODO: Can't get the type signature to compile, HUpdateAtLabel2 isn't visible+-- from Data.HList.Record updateAllA rv f = do rv' <- readRelvar rv doUpdateAll rv (updateAllA' rv' f)@@ -313,7 +329,8 @@ renameFile ( relvarPath rv ++ ".new" ) ( relvarPath rv ) putStrLn $ "Deleted " ++ nDeleted ++ " tuples from " ++ relvarPath rv -{-| Deletes a specified subset of a relvar. Note that this is not SQL DELETE, but instead a generalization thereof.+{-| Deletes a specified subset of a relvar. Note that this is not SQL DELETE, but+instead a generalization thereof. See `Database.HaskRel.Relational.Expression.delete`. -}@@ -327,8 +344,9 @@ let filtered = Data.Set.difference rv' r in doDelete rv filtered ( show $ size rv' - size filtered ) -{-|-Performs an inclusive delete against a relvar. Also not SQL DELETE. This will fail if the second argument is not a subset of the relation value identified by the relation variable reference.+{-| Performs an inclusive delete against a relvar. Also not SQL DELETE. This will+fail if the second argument is not a subset of the relation value identified by+the relation variable reference. See `Database.HaskRel.Relational.Expression.iDelete`. -}@@ -368,5 +386,6 @@ let filtered = Data.Set.filter ( not . p ) rv' in doDelete rv filtered ( show $ size rv' - size filtered ) --- An iDeleteP function could also be defined, but its utility would be marginal.+-- An iDeleteP function could also be defined, but its utility would be+-- marginal.
src/Database/HaskRel/Relational/Definition.hs view
@@ -6,15 +6,19 @@ {-| Module : Definition-Description : Definition and presentation of relational objects in terms of Haskell and HList records+Description : Definition and presentation of relational objects in terms of+ Haskell and HList records Copyright : © Thor Michael Støre, 2015 License : GPL v2 without "any later version" clause Maintainer : thormichael át gmail døt com Stability : experimental -Definition and presentation of relational objects in terms of Haskell and HList records, and certain implementation level supporting functions.+Definition and presentation of relational objects in terms of Haskell and HList+records, and certain implementation level supporting functions. -Naming convention of the relational model is, to a degree, adopted to clarify how it is mirrored into Haskell. With these aliases in place the following are equivalent:+Naming convention of the relational model is, to a degree, adopted to clarify+how it is mirrored into Haskell. With these aliases in place the following are+equivalent: >>> :t fromList [] :: Set (Record '[Tagged "sno" String, Tagged "status" Int]) @@ -25,7 +29,10 @@ -- * Definition Attr, RTuple, Relation, -- ** Labels- {-| To be in line with the naming convention of the relational model, the @Label@ type could be referred to as an "attribute name", and the @Labels@ type as "attribute names", but since this would not clarify the matter significantly HaskRel re-exports them as-is.+ {-| To be in line with the naming convention of the relational model, the @Label@+type could be referred to as an "attribute name", and the @Labels@ type as+"attribute names", but since this would not clarify the matter significantly+HaskRel re-exports them as-is. It is important to note how labels may either be pre-defined, or used ad-hoc: @@ -37,7 +44,10 @@ -} Label(Label), Labels, -- * Construction- {- | These functions are based on common HList operations, and given aliases that are in line with relational theory. As mentioned for each function they only support 0 to 6-tuples, which is of course a drawback. For r-tuples of other degrees the basic HList approaches to record construction may be used:+ {-| These functions are based on common HList operations, and given aliases that+are in line with relational theory. As mentioned for each function they only+support 0 to 6-tuples, which is of course a drawback. For r-tuples of other+degrees the basic HList approaches to record construction may be used: >>> let sno = Label :: Label "sno" ... etc ...@@ -75,13 +85,18 @@ -- | A shorthand for "attribute", and a synonym of @Data.Tagged@. type Attr = Tagged --- | A "tuple as defined by the relational model", synonym for @Data.HList.Record@.+{-| A "tuple as defined by the relational model", synonym for+@Data.HList.Record@.+-} type RTuple = Record -- | A synonym for @Set ( RTuple a )@. type Relation a = Set ( RTuple a ) --- | A shorthand for "relational header", and a synonym of @Data.HList.Record.Labels@. The header of either an r-tuple or a relation in relational theory.+{-| A shorthand for "relational header", and a synonym of+@Data.HList.Record.Labels@. The header of either an r-tuple or a relation in+relational theory.+-} type RHdr a = Labels a {-@@ -113,7 +128,8 @@ t -> RTuple r rTuple t = mkRecord $ hFromTuple t -{-| Constructs an r-tuple from a tuples of untagged values, where the labels and exact types are inferred from the context. Supports only 0 to 6-tuples.+{-| Constructs an r-tuple from a tuples of untagged values, where the labels and+exact types are inferred from the context. Supports only 0 to 6-tuples. >>> pt$ ( rTuple' ("S1", "Smith", 20, "London") :: RTuple '[SNO, SName, Status, City] ) ...@@ -126,7 +142,9 @@ t -> RTuple b rTuple' t = hMapTaggedFn $ hFromTuple t -{-| Construct a relation value from a list of r-tuples of tagged values. Alias of 'Data.Set.fromList' with a result restricted to 'Relation', with a name from relational theory. Supports only 0 to 6-tuples.+{-| Construct a relation value from a list of r-tuples of tagged values. Alias of+'Data.Set.fromList' with a result restricted to 'Relation', with a name from+relational theory. Supports only 0 to 6-tuples. >>> :{ pt$ relation [rTuple (sno .=. "S1", sName .=. "Smith", status .=. (20::Integer), city .=. "London"),@@ -142,7 +160,9 @@ relation :: (Ord a, a ~ Record b) => [a] -> Relation b relation = fromList -{-| Construct a relation value from a list of tuples of untagged values, where the labels and exact types are inferred from the context. Supports only 0 to 6-tuples.+{-| Construct a relation value from a list of tuples of untagged values, where the+labels and exact types are inferred from the context. Supports only 0 to+6-tuples. >>> pt$ ( relation' [("S1", "Smith", 20, "London"), ("S2", "Jones", 10, "Paris")] :: Relation '[SNO, SName, Status, City] ) @@ -161,7 +181,10 @@ relation'' = fromList . map rTuple -{-| Alias of @Data.HList.HList.hFromTuple@ with a name that is more descriptive for the purpose of constructing "Labels", which are employed as relational headings, from Haskell tuples of label values. When labels have been defined it permits expressing:+{-| Alias of @Data.HList.HList.hFromTuple@ with a name that is more descriptive+for the purpose of constructing "Labels", which are employed as relational+headings, from Haskell tuples of label values. When labels have been defined it+permits expressing: >>> pt$ p `project` (undefined :: Labels '["pno","pName","color"]) @@ -179,7 +202,8 @@ as :: Label l -> v -> Tagged l v as = (.=.) -{-| N-adic constructor of 'as' statements, using @mkRecord . hFromTuple@. Only supports 0 to 6-tuples.+{-| N-adic constructor of 'as' statements, using @mkRecord . hFromTuple@. Only+supports 0 to 6-tuples. >>> let pnu = Label :: Label "pnu" >>> let colour = Label :: Label "colour"@@ -194,7 +218,9 @@ -- == Supporting functions --- | Order-agnostic equality operation, which is neccessary for comparison correct for r-tuples.+{-| Order-agnostic equality operation, which is neccessary for comparison correct+for r-tuples.+-} unordRecEq :: (Eq (HList l), HRearrange3 (LabelsOf l) r l, HLabelSet (LabelsOf l), SameLength' r l,@@ -204,7 +230,9 @@ unordRecEq l r = l == hRearrange' r --- This variant is quite slow, though that should be because of type system issues and not runtime performance; replicate 10 of a comparison has much better performance than repeating it in a 10-tuple.+-- This variant is quite slow, though that should be because of type system+-- issues and not runtime performance; replicate 10 of a comparison has much+-- better performance than repeating it in a 10-tuple. unordRecEq' :: (Eq (HList l), HRearrange3 (LabelsOf l) r l, HLabelSet (LabelsOf l), HLabelSet (LabelsOf r),@@ -214,7 +242,8 @@ RTuple l -> RTuple t -> Bool unordRecEq' l r = l == hProjectByLabels' r --- | Rearrange a set of HList records to context. From the perspective of relational theory this is a presentation function.+-- | Rearrange a set of HList records to context. From the perspective of+-- relational theory this is a presentation function. relRearrange :: (Ord (HList l), HRearrange3 (LabelsOf l) r l, HLabelSet (LabelsOf l), SameLength' r l,@@ -223,7 +252,9 @@ Relation r -> Relation l relRearrange = Data.Set.map hRearrange' --- | Rearrange a set of HList records to the order of a set given by an argument. The value of the second argument will be ignored. From the perspective of relational theory this is a presentation function.+-- | Rearrange a set of HList records to the order of a set given by an+-- argument. The value of the second argument will be ignored. From the+-- perspective of relational theory this is a presentation function. relRearrange' :: (Ord (HList l), HRearrange3 (LabelsOf l) r l, HLabelSet (LabelsOf l), SameLength' r l,@@ -232,7 +263,8 @@ Relation r -> Relation l -> Relation l relRearrange' rel ord = Data.Set.map hRearrange' rel --- That ' is swapped between relRearrange and hRearrange is a work related accident and not the way it must be.+-- That ' is swapped between relRearrange and hRearrange is a work related+-- accident and not the way it must be. rElementType :: Relation r -> RTuple r rElementType rel = undefined@@ -247,7 +279,7 @@ -- == Constants of relational theory -{- | The nullary relation with an empty body+{-| The nullary relation with an empty body >>> pt$ tableDum ┌──┐@@ -260,7 +292,7 @@ tableDum :: Relation '[] tableDum = Data.Set.empty -{- | The nullary relation of cardinality one.+{-| The nullary relation of cardinality one. >>> pt$ tableDee ┌──┐@@ -280,7 +312,8 @@ -- Redefined and not just reexported to add a bit of documentation -- TODO: Check if it's possible to have something that'll work for both tableDum and empty. -{- | The empty set without an explicit type. In a relational context this is a relation with an empty body and no defined heading.+{-| The empty set without an explicit type. In a relational context this is a+relation with an empty body and no defined heading. >>> relation [rTuple (sno .=. "S1", status .=. 5)] == empty False@@ -311,7 +344,8 @@ rPrint :: HFWPresent r => r -> IO () rPrint = hfwPrint --- | Prints a tabular representation of an r-tuple or relation, with type information.+-- | Prints a tabular representation of an r-tuple or relation, with type+-- information. rPrintTyped :: HFWPresent r => r -> IO () rPrintTyped = hfwPrintTypedTS HaskRelTS @@ -323,7 +357,9 @@ pt :: HFWPresent r => r -> IO () pt = rPrintTyped --- | Specialization of @Database.HaskRel.HFWTabulation.showHTypeTS@ that employs HaskRel specific type-synonyms.+{-| Specialization of @Database.HaskRel.HFWTabulation.showHTypeTS@ that employs+HaskRel specific type-synonyms.+-} rShowType :: TypeRep -> String rShowType = showHTypeTS HaskRelTS
src/Database/HaskRel/Relational/Expression.hs view
@@ -9,12 +9,24 @@ Maintainer : thormichael át gmail døt com Stability : experimental -"Database.HaskRel.Relational.Algebra" and "Database.HaskRel.Relational.Assignment" defines the functions of the relational algebra and relational assignment, but in order to keep pertinent concerns separated it only defines functions for relational operations that reference values, not relation variables. This module redefines those functions, generalizing them such that they operate upon relation values, relation variables and relational IO (relational expressions that build upon relvars), and also adds 'HFWPresent' instances for relational IO.+"Database.HaskRel.Relational.Algebra" and+"Database.HaskRel.Relational.Assignment" defines the functions of the relational+algebra and relational assignment, but in order to keep pertinent concerns+separated it only defines functions for relational operations upon values, not+relation variables. This module redefines those functions, generalizing them+such that they operate upon relation values, relation variables and relational+IO (relational expressions that build upon relvars), and also adds 'HFWPresent'+instances for relational IO. -Running "examples/suppliersPartsExample.sh" starts a GHCi session where these examples can be run.+Running "examples/suppliersPartsExample.sh" starts a GHCi session where these+examples can be run.++Note that certain browsers and operating system combinations result in incorrect+display of the horizontal lines of tables. -} module Database.HaskRel.Relational.Expression (- -- * Functions defined according to the definition of monadic operators in relational theory+ {- * Functions defined in accordance with monadic operators of relational+ theory -} {-| (Not to be confused with Haskell monads.) -} -- ** The monadic operator class MonOp (monOp), MonOpRes, MonOpArg,@@ -22,7 +34,7 @@ rename, extend, restrict, project, projectAllBut, group, groupAllBut, ungroup, -- ** Supplementary functions- -- *** Specializations of functions of the relational model, with relational closure+ -- *** With relational closure -- **** Not part of relational theory dExtend, extendA, dExtendA, renameA, aSummarize, imageExtendL, -- *** Without relational closure@@ -30,7 +42,8 @@ -- **** Not part of relational theory rafoldr, rafoldrU, agg, aggU, count, isEmpty, rAgg, rAggU,- -- * Functions defined according to the definition of dyadic operators in relational theory+ {- * Functions defined in accordance with dyadic operators in relational+ theory -} -- ** The dyadic operator class DyaOp, DyaOpRes, DyaOpLeft, DyaOpRight, dyaOp, -- ** The functions defined as dyadic operators in the relational algebra@@ -39,7 +52,8 @@ isSubsetOf, rEq, -- ** Somewhat deprecated operators of the relational algebra summarize,- -- ** Specializations of functions of the relational model, with relational closure+ {- ** Specializations of functions of the relational model, with relational+ closure -} -- **** Not part of relational theory interJoin, iJoin, -- * Assignment functions@@ -49,7 +63,9 @@ assign, -- ** Specialized assignment functions insert, dInsert, delete, iDelete,- -- update[A], updateAll[A] and deleteP only take relvars and optionally predicates as arguments, and not relation values, and are as such not re-defined in this module, but are re-exported for completeness+ -- update[A], updateAll[A] and deleteP only take relvars and optionally+ -- predicates as arguments, and not relation values, and are as such not+ -- re-defined in this module, but are re-exported for completeness Assignment.deleteP, Assignment.update, Assignment.updateAll, -- ** Further specialized and simplified forms of update -- **** Not part of relational theory@@ -90,7 +106,8 @@ -- Alternatively, enforcing relational closure: -- monOp :: (MonOpArg a -> (Relation res)) -> a -> (MonOpRes a (Relation res)) -- But it's convenient to also employ this for aggregating functions--- TODO: This resulted in IO (IO a) at some point, should GHCi warn about that? Or if main results in this, for that matter.+-- TODO: This resulted in IO (IO a) at some point, should GHCi warn about that?+-- Or if main results in this, for that matter. instance MonOp (Relation a) where type MonOpRes (Relation a) res = res@@ -127,7 +144,9 @@ type MonOpArg' (Relvar b) = Relation b monOp' f r = f <$> readRelvar r --- The "values in, value out" variation allows for the same functions to be used inside the functions that rename and extends takes, but this form is necessary in certain cases; see 'rename' below, and the TODO to fix this.+-- The "values in, value out" variation allows for the same functions to be used+-- inside the functions that rename and extends takes, but this form is+-- necessary in certain cases; see 'rename' below, and the TODO to fix this. @@ -142,10 +161,15 @@ │ P1 │ Nut │ Red │ 12 % 1 │ London │ ... -Note that due to an implementation disorder this always results in an IO operation, even on values. This is not an intentional limit and will hopefully be removed in the future. If this is not acceptable (for instance inside 'extend' and 'restrict' functions), then one has to rely on 'renameA', which renames a single attribute.+Note that due to an implementation disorder this always results in an IO+operation, even on values. This is not an intentional limit and will hopefully+be removed in the future. If this is not acceptable (for instance inside+'extend' and 'restrict' functions), then one has to rely on 'renameA', which+renames a single attribute. -} rename r l = monOp' (\r' -> Algebra.rename r' l) r-{- TODO: Figure out why using monOp instead of monOp' breaks down with: Could not deduce (MonOpRes a (Relation r) ~ MonOpRes a (Relation r0))+{- TODO: Figure out why using monOp instead of monOp' breaks down with: Could not+deduce (MonOpRes a (Relation r) ~ MonOpRes a (Relation r0)) This is annoying, since applying it directly in GHCi works: @@ -200,8 +224,8 @@ renameA r ft = monOp (\r' -> Algebra.renameA r' ft) r -{-|-Extends the given relation with the r-tuple resulting from the second argument. Existing attributes with the same name will be replaced.+{-| Extends the given relation with the r-tuple resulting from the second+argument. Existing attributes with the same name will be replaced. The simplest form (aside from a no-op), extend with one attribute: @@ -214,7 +238,12 @@ │ 8626 % 1 │ P6 │ Cog │ Red │ 19 % 1 │ London │ └──────────┴─────┴───────┴───────┴────────┴────────┘ -When replacing an attribute with extend one must take care not to cause a naming collision. Using pattern matching with @case ... of ...@ one can pass values from one context to another with Haskell tuples, and reuse variable names, although this does require some duplication. It is also possible to use @pun@ to build the output, instead of @.*.@ and @emptyRecord@. Add one attribute, replace another:+When replacing an attribute with extend one must take care not to cause a naming+collision. Using pattern matching with @case ... of ...@ one can pass values+from one context to another with Haskell tuples, and reuse variable names,+although this does require some duplication. It is also possible to use @pun@ to+build the output, instead of @.*.@ and @emptyRecord@. Add one attribute, replace+another: >>> rPrint$ extend p (\[pun|weight color|] -> case (weight + 10, color ++ "-ish") of (weight, altColor) -> [pun|weight altColor|]) ┌────────┬───────────┬─────┬───────┬───────┬────────┐@@ -225,16 +254,26 @@ │ 29 % 1 │ Red-ish │ P6 │ Cog │ Red │ London │ └────────┴───────────┴─────┴───────┴───────┴────────┘ -Lining this up with the @EXTEND@ operator of Tutorial D, we can imagine @ case (a, b) of (a', b') @ as a form of @ { a' := a , b' := b } @ (though we can hardly equate them), while @pun@ is needed to unpack and pack this from and to the r-tuples.+Lining this up with the @EXTEND@ operator of Tutorial D, we can imagine @ case+(a, b) of (a', b') @ as a form of @ { a' := a , b' := b } @ (though we can+hardly equate them), while @pun@ is needed to unpack and pack this from and to+the r-tuples. -Also note that if an attribute is replaced then the cardinality of the result will be equal or lower than that of the argument.+If no attributes are replaced then the cardinality of the result will be equal+to the cardinality of the argument, if an attribute is replaced then the+cardinality of the result will be equal or lower than that of the argument. >>> count sp 12 >>> count $ sp `extend` (\_ -> sno .=. "S0" .*. pno .=. "P0" .*. emptyRecord) 4 -It is also notable that since HaskRel is not based on SQL but on relational theory as defined by Chris Date et al today, and explicitly does not have support for nulls and outer joins (as specified in [1] chapter 4), @extend@ is employed to assemble the information SQL assembles with @OUTER JOIN@. The following command (a variant of the first query on [1] page 154) gives a result that includes the information given by SQL @RIGHT OUTER JOIN@:+It is also notable that since HaskRel is not based on SQL but on relational+theory as defined by Chris Date et al. today, and explicitly does not have+support for nulls and outer joins (as specified in [1] chapter 4), @extend@ is+employed to assemble the information SQL assembles with @OUTER JOIN@. The+following command (a variant of the first query on [1] page 154) gives a result+that includes the information given by SQL @RIGHT OUTER JOIN@: >>> :{ do sp' <- readRelvar sp@@ -256,11 +295,16 @@ See material about extend, image relations and RVAs in [1] chapter 7 for more. -Note the additional plumbing required to employ relvars inside the function @extend@ takes; the function 'imageExtendL' has been created to provide a more convenient way to express this, specializing upon @extend@ and 'image'.+Note the additional plumbing required to employ relvars inside the function+@extend@ takes; the function 'imageExtendL' has been created to provide a more+convenient way to express this, specializing upon @extend@ and 'image'. -} extend r f = monOp (`Algebra.extend` f) r -{- TODO: Is it possible to have a quasiquoter that lets one specify roughly the same as the Tutorial D snippet mentioned in the documentation above? For instance instead of:+{- TODO: Is it possible to have a quasiquoter that lets one specify roughly the+same as the Tutorial D snippet mentioned in the documentation above? For+instance instead of:+ (\[pun|weight color|] -> case (weight + 10, color ++ "-ish") of (weight, altColor) -> [pun|weight altColor|]) Having something like:@@ -269,7 +313,10 @@ That would also render extendA/dExtendA obsolete, tidying things up nicely. -} -{-| Extends the given relation with the attribute resulting from the second argument. If an attribute with the same name exists then it will be replaced. This allows for the function of the second argument to be simpler.+{-| Extends the given relation with the attribute resulting from the second+argument. If an attribute with the same name exists then it will be+replaced. This allows for the function of the second argument to be simpler than+for `extend`, which must return an r-tuple. Where @c@ is an expression yielding a single attribute: @@ -284,32 +331,40 @@ >>> let gmwt = (Label::Label "gmwt") >>> rPrint$ extendA p (\[pun|weight|] -> gmwt .=. weight * 454) -Note that if one wants to alter the values of an existing attribute then one has to avoid a name collision. The most convenient option will most often be having a constructor function or label constant with a different name from the actual label:+Note that if one wants to alter the values of an existing attribute then one has+to avoid a name collision. The most convenient option will most often be having+a constructor function or label constant with a different name from the actual+label: >>> let _weight a = (Label::Label "weight") .=. a >>> rPrint$ extendA p (\[pun|weight|] -> _weight $ weight + 10) -} extendA r f = monOp (`Algebra.extendA` f) r {--Alternatively, perform the pattern matching without \"pun\", which allows different variable names from the function names:+Alternatively, perform the pattern matching without \"pun\", which allows+different variable names from the function names: >>> rPrint$ extendA p' (\ ( hProjectByLabels' -> ( Record ( ( Tagged weight' :: Tagged "weight" Rational ) `HCons` HNil ) ) ) -> weight .=. weight' ) -}--- TODO: The issue mentioned above could perhaps be solved by making pun create variables that can serve both as data and as constructors. See also https://hackage.haskell.org/package/HList/docs/Data-HList-Labelable.html+-- TODO: The issue mentioned above could perhaps be solved by making pun create+-- variables that can serve both as data and as constructors. See also+-- https://hackage.haskell.org/package/HList/docs/Data-HList-Labelable.html -{-|-Disjoint extension. Extends the given relation with the result of the second argument, as 'extend', but without deleting any that exist. +{-| Disjoint extension. Extends the given relation with the result of the second+argument, as 'extend', but without deleting any that exist. -} dExtend r f = monOp (`Algebra.dExtend` f) r -{-|-Disjoint extension of a single attribute. Extends the given relation with the result of the second argument, as 'extend', but without deleting any that exist. @l@ cannot already have any attribute @e@.+{-| Disjoint extension of a single attribute. Extends the given relation with the+result of the second argument, as 'extend', but without deleting any that+exist. @l@ cannot already have any attribute @e@. -} dExtendA r f = monOp (`Algebra.dExtendA` f) r -{-|-Restricts the given relation according to the given predicate. Note that this is the well known @WHERE@ operator of both SQL and Tutorial D, but since "where" is a reserved keyword in Haskell it is named "restrict".+{-| Restricts the given relation according to the given predicate. Note that this+is the well known @WHERE@ operator of both SQL and Tutorial D, but since "where"+is a reserved keyword in Haskell it is named "restrict". >>> rPrint$ p `restrict` (\[pun|weight|] -> weight < 17.5) ┌─────┬───────┬───────┬────────┬────────┐@@ -336,9 +391,12 @@ └───────┴────────┘ -} project r a = monOp (`Algebra.project` a) r--- TODO: project/projectAllBut must fail when it is given labels that don't exist in the given relation, right now it accepts all sorts of nonesense without complaining.+-- TODO: project/projectAllBut must fail when it is given labels that don't+-- exist in the given relation, right now it accepts all sorts of nonesense+-- without complaining. -{-| Projects the given relation on the heading of said given relation minus the given heading.+{-| Projects the given relation on the heading of said given relation minus the+given heading. >>> rPrint$ p `projectAllBut` (rHdr (city)) ┌─────┬───────┬───────┬────────┐@@ -355,7 +413,8 @@ projectAllBut r a = monOp (`Algebra.projectAllBut` a) r -{-| Groups the given attributes of the given relation into a given new relation valued attribute.+{-| Groups the given attributes of the given relation into a given new relation+valued attribute. As the Tutorial D GROUP operator, not SQL GROUP BY. @@ -371,7 +430,12 @@ ... └────────────────────────────────────┴───────────────┘ -Note that the last argument is a function that tags any value with a label; an attribute constructor. This is different from Tutorial D @GROUP@, which just takes the equivalent of a label, but as long as an attribute constructor is provided it will function the same way. Here is what we get if we aggregate the relation we get as the argument to the receiving function with 'agg', instead of just supplying an attribute constructor ("@(pq .=.)@" above):+Note that the last argument is a function that tags any value with a label; an+attribute constructor. This is different from Tutorial D @GROUP@, which just+takes the equivalent of a label, but as long as an attribute constructor is+provided it will function the same way. Here is what we get if we aggregate the+relation we get as the argument to the receiving function with 'agg', instead of+just supplying an attribute constructor ("@(pq .=.)@" above): >>> let qtys = (Label :: Label "qtys") >>> pt$ group sp (rHdr (pno,qty))@@ -397,11 +461,14 @@ │ 1300 │ S1 │ └─────────────────┴───────────────┘ -Note the difference between this and the example for 'image'. These last two examples may be more clearly expressed with 'groupAllBut', since we then specify the attributes that the resulting type will have.+Note the difference between this and the example for 'image'. These last two+examples may be more clearly expressed with 'groupAllBut', since we then specify+the attributes that the resulting type will have. -} group rel attsIn fOut = monOp (\r' -> Algebra.group r' attsIn fOut) rel -{-| Groups the given relation on all but the given attributes into a given new attribute.+{-| Groups the given relation on all but the given attributes into a given new+attribute. >>> pt$ groupAllBut sp (rHdr (sno)) (pq .=.) @@ -421,7 +488,8 @@ ungroup r a = monOp (`Algebra.ungroup` a) r -{-| Self-summarization, the special case of `summarize` where the source and target relations is the same. This is closer to SQL GROUP BY.+{-| Self-summarization, the special case of `summarize` where the source and+target relations is the same. This is closer to SQL GROUP BY. >>> pt$ aSummarize sp (rHdr (pno,qty)) (\r -> qty .=. sum ( agg qty r ) .*. emptyRecord) @@ -447,8 +515,12 @@ isEmpty :: (MonOp a, MonOpArg a ~ Set a1) => a -> MonOpRes a Bool isEmpty = monOp Algebra.isEmpty --- TODO: Figure out how all this will function when aggregating values into new relations. That'll most likely happen in an extend clause, where IO support is irrelevant.-{-| Right-fold of an attribute of a relation (although a "right" fold doesn't make sense in the context of the relational model). Note that the value of the third argument is not used and may be "undefined".+-- TODO: Figure out how all this will function when aggregating values into new+-- relations. That'll most likely happen in an extend clause, where IO support+-- is irrelevant.+{-| Right-fold of an attribute of a relation (although a "right" fold doesn't make+sense in the context of the relational model). Note that the value of the third+argument is not used and may be "undefined". >>> rafoldr (+) 0 qty sp 3100@@ -457,7 +529,8 @@ -} rafoldr f b a r = monOp (Algebra.rafoldr f b a) r -{-| Attribute value aggregation, a specialization of 'rafoldr' that aggregates the values of a single attribute into a list of the values the attribute type wraps.+{-| Attribute value aggregation, a specialization of 'rafoldr' that aggregates the+values of a single attribute into a list of the values the attribute type wraps. Note that the value of the first argument is not used and may be "undefined". @@ -471,7 +544,7 @@ Label l -> a -> MonOpRes a [a2] agg = monOp . Algebra.agg -{-| Right-fold of the attribute of a unary relation.+{-| Right-fold of the attribute of an unary relation. >>> rafoldrU (+) 0 $ sp `project` (rHdr (qty)) 1000@@ -480,7 +553,9 @@ -} rafoldrU f b r = monOp (Algebra.rafoldrU f b) r -{-| Aggregation of the single attribute of a unary relation. A specialization of 'agg', and thus in turn of 'rafoldr', that aggregates the single attribute of a unary relation, without requiring the name of that attribute.+{-| Aggregation of the single attribute of an unary relation. A specialization of+'agg', and thus in turn of 'rafoldr', that aggregates the single attribute of a+unary relation, without requiring the name of that attribute. >>> :{ do sp' <- readRelvar sp@@ -493,7 +568,8 @@ a -> MonOpRes a [a1] aggU = monOp Algebra.aggU -{-| Aggregates an attribute and applies a function to the result of that. A specialization of `rafoldr`.+{-| Aggregates an attribute and applies a function to the result of that. A+specialization of `rafoldr`. Note that the value of the first argument is not used and may be "undefined". @@ -504,17 +580,20 @@ Label l -> a -> ([a2] -> res) -> MonOpRes a res rAgg a r f = monOp (f . Algebra.agg a) r -{-| Aggregates the attribute of a unary relation and applies a function to the result of that. A specialization of `rafoldrU`.+{-| Aggregates the attribute of an unary relation and applies a function to the+result of that. A specialization of `rafoldrU`. >>> rAggU (sp `project` (rHdr (qty))) sum 1000 -} rAggU r f = monOp (f . Algebra.aggU) r --- TODO: Can this be made to work not just towards an IO(Relation a) but also from it?+-- TODO: Can this be made to work not just towards an IO(Relation a) but also+-- from it? {-| The image of a relation corresponding to an r-tuple. -An application of the first argument only, an r-tuple, to this function yields what is known as the @!!@ operator in Tutorial D.+An application of the first argument only, an r-tuple, to this function yields+what is known as the @!!@ operator in Tutorial D. >>> rPrint$ rTuple (sno .=. "S2", pno .=. "P1") ┌─────┬─────┐@@ -542,7 +621,8 @@ │ P2 │ 400 │ └─────┴─────┘ -Image relations give rise to summarization. Here is a form of the query, "get the quantities of items in stock for all suppliers":+Image relations give rise to summarization. Here is a form of the query, "get+the quantities of items in stock for all suppliers": >>> :{ do sp' <- readRelvar sp@@ -560,7 +640,8 @@ │ 1300 │ S1 │ └────────┴─────┘ -Note how view patterns are used to build the @ii@ operator, equivalent of Tutorial D's @!!@ operator. An equivalent form of the lambda would be:+Note how view patterns are used to build the @ii@ operator, equivalent of+Tutorial D's @!!@ operator. An equivalent form of the lambda would be: @ (\\t -> (Label::Label "qtySum") .=. ( sum $ agg qty $ t \`image\` sp' ))@@ -595,7 +676,9 @@ -{- TODO: Is there a way to cut down on the number of instances? Building the dyadic operator class directly on the monadic one doesn't work, the following dyaOp'' definition compiles, but using it with nJoin'' breaks down in ambiguity.+{- TODO: Is there a way to cut down on the number of instances? Building the+dyadic operator class directly on the monadic one doesn't work, the following+dyaOp'' definition compiles, but using it with nJoin'' breaks down in ambiguity. dyaOp'' :: (MonOp a, MonOp a1, MonOpRes a1 res1 ~ (MonOpArg a -> res)) =>@@ -702,7 +785,11 @@ DyaOpLeft a ~ Relation l, DyaOpRight b ~ Relation r) => a -> b -> DyaOpRes a b Bool rEq = dyaOp (==)--- TODO: Oh great, now I need an IO version of restrict. This is tricky, my first thought is that a predicate for 'filter' will still take a regular value as before but may result in an IO Bool instead of Bool, so perhaps you need to do something like :: ( a -> f b ) -> f ( a -> b )+{- TODO: Oh great, now I need an IO version of restrict. This is tricky, my first+thought is that a predicate for 'filter' will still take a regular value as+before but may result in an IO Bool instead of Bool, so perhaps you need to do+something like :: ( a -> f b ) -> f ( a -> b )+-} {-| The natural join of the two given relations. @@ -721,7 +808,8 @@ nJoin r1 r2 = dyaOp' Algebra.nJoin r1 r2 -{-| The cartesian product of two relations. A specialized natural join; the natural join between two relations with disjoint headings.+{-| The cartesian product of two relations. A specialized natural join; the+natural join between two relations with disjoint headings. >>> rPrint$ ( sp `projectAllBut` (rHdr (city)) ) `times` ( s `projectAllBut` (rHdr (city)) ) ...@@ -741,11 +829,18 @@ -} times r1 r2 = dyaOp' Algebra.times r1 r2 -{-| The natural join between two relations with intersecting headings. A specialized natural join.+{-| The natural join between two relations with intersecting headings. A+specialized natural join. -A join upon relations r1, r2 where the intersection of the heading of r1 and of r2 is not empty; the headings are not disjoint. This is a complement of 'times' within natural join; all that would be disallowed for @times@ is allowed here and vice-versa. The name is what I quickly settled on, suggestions for a better one would be welcome. (Attribute-Intersecting Natural Join is another candidate.)+A join upon relations r1, r2 where the intersection of the heading of r1 and of+r2 is not empty; the headings are not disjoint. This is a complement of 'times'+within natural join; all values that @naturalJoin@ accepts as operands and+@times@ does not are accepted by @interJoin@, and vice-versa. The name is what I+quickly settled on, suggestions for a better one would be welcome.+(Attribute-Intersecting Natural Join is another candidate.) -This function doesn't have a specific identity value, although it holds that @r \`interJoin\` r = r@+This function doesn't have a specific identity value, although it holds that+@r \`interJoin\` r = r@ >>> rPrint$ ( sp `projectAllBut` (rHdr (sno)) ) `interJoin` ( s `projectAllBut` (rHdr (sno)) ) ...@@ -768,7 +863,10 @@ iJoin r1 r2 = dyaOp' Algebra.iJoin r1 r2 -{- | Extends the first given relation with an attribute resulting from imaging each tuple of said relation against the second given relation. The following command gives a result that includes the information given by SQL @RIGHT OUTER JOIN@:+{- | Extends the first given relation with an attribute resulting from imaging+each tuple of said relation against the second given relation. The following+command gives a result that includes the information given by SQL @RIGHT OUTER+JOIN@: >>> rPrint$ imageExtendL s sp pq ┌───────────────┬─────┬───────┬────────┬────────┐@@ -785,7 +883,8 @@ │ │ P2 │ 200 │ │ │ │ │ │ ... -See also 'extend', which this function specializes, and 'image', which it uses to perform this specialization.+See also 'extend', which this function specializes, and 'image', which it uses+to perform this specialization. -} imageExtendL :: (Eq (HList l), Ord (HList l1), Ord (HList r'),@@ -820,7 +919,8 @@ semiJoin r1 r2 = dyaOp' Algebra.semiJoin r1 r2 -{-| The semi-difference of the first given relation against the second given relation. Aka. antijoin.+{-| The semi-difference of the first given relation against the second given+relation. Aka. antijoin. >>> rPrint$ s `notMatching` sp ┌─────┬───────┬────────┬────────┐@@ -857,7 +957,8 @@ -} union r1 r2 = dyaOp' Algebra.union r1 r2 -{-| The disjoint union between the relations. This is a union of disjoint relations, where a runtime error is raised if the arguments are not disjoint.+{-| The disjoint union between the relations. This is a union of disjoint+relations, where a runtime error is raised if the arguments are not disjoint. >>> :{ rPrint$ ( p' `project` (rHdr (city)) )@@ -876,7 +977,9 @@ {-| The intersection of two relations. -Note how the name is different from Data.Set, where it is named "intersection". This is due to it being referred to as "intersect" in material describing the relational model; specifically named \"INTERSECT\" in Tutorial D.+Note how the name is different from Data.Set, where it is named+"intersection". This is due to it being referred to as "intersect" in material+describing the relational model; specifically named \"INTERSECT\" in Tutorial D. >>> let sX = ( relation [rTuple (sno .=. "S2", sName .=. "Jones", city .=. "Paris", status .=. 10), rTuple (sno .=. "S6", sName .=. "Nena", city .=. "Berlin", status .=. 40)] ) >>> rPrint$ s `intersect` sX@@ -886,17 +989,20 @@ │ S2 │ Jones │ 10 │ Paris │ └─────┴───────┴────────┴───────┘ -Notably, for any given relation values r1 and r2 that are of the same type it holds that:+Notably, for any given relation values r1 and r2 that are of the same type it+holds that: @r1 \`intersect\` r2 == r1 \`naturalJoin\` r2@ -Within relational theory the natural join generalizes as such both intersection and cartesian product.+Within relational theory the natural join generalizes as such both intersection+and cartesian product. -} intersect r1 r2 = dyaOp' Algebra.intersect r1 r2 {-| The difference of two relations. -The "minus" term is used in material describing relational theory; specifically Tutorial D names the operator \"MINUS\".+The "minus" term is used in material describing relational theory; specifically+Tutorial D names the operator \"MINUS\". >>> rPrint$ s `minus` sX ┌─────┬───────┬────────┬────────┐@@ -910,7 +1016,10 @@ -} minus r1 r2 = dyaOp' Algebra.minus r1 r2 -{- The difference of two relations. This differs from 'minus' in that the attribute order of the second argument takes precedence. This function is as such equal to 'minus' as far as relational theory is concerned, the difference is on a lower level of abstraction.+{- The difference of two relations. This differs from 'minus' in that the+attribute order of the second argument takes precedence. This function is as+such equal to 'minus' as far as relational theory is concerned, the difference+is on a lower level of abstraction. >>> rPrint$ s `minus_` sX ┌─────┬───────┬────────┬────────┐@@ -923,7 +1032,8 @@ └─────┴───────┴────────┴────────┘ -} -- minus_ r1 r2 = dyaOp' Algebra.minus_ r1 r2--- TODO: Should minus_ be exported? I don't see a big reason to, Algebra.minus is used directly by Assignment...+-- TODO: Should minus_ be exported? I don't see a big reason to, Algebra.minus+-- is used directly by Assignment... {-| Exclusive union, aka. symmetric difference@@ -1007,7 +1117,8 @@ relAssign f r = f =<< readRelvar r --- TODO: What is the correct fixity? (Should most likely be thought through for the whole library.)+-- TODO: What is the correct fixity? (Should most likely be thought through for+-- the whole library.) infix 1 `assign` {-| Writes a relation value to a relvar file, replacing the existing value. @@ -1018,8 +1129,8 @@ infix 1 `insert`-{-|-Inserts a relation into a relvar. This differs from SQLs INSERT; the relvar is updated to the union of the relvar and the relation value given as arguments.+{-| Inserts a relation into a relvar. This differs from SQLs INSERT; the relvar is+updated to the union of the relvar and the relation value given as arguments. >>> let newSups = relation [rTuple (sno .=. "S6", sName .=. "Nena", city .=. "Berlin", status .=. 40)] >>> insert s newSups@@ -1056,7 +1167,8 @@ infix 1 `delete`-{-| Deletes a specified subset of a relvar. Note that this is not SQL DELETE, but instead a generalization thereof.+{-| Deletes a specified subset of a relvar. Note that this is not SQL DELETE, but+instead a generalization thereof. >>> delete s newSups Deleted 1 tuples from SuppliersPartsDB/S.rv@@ -1064,7 +1176,8 @@ delete rv r = relAssign ( Assignment.delete rv ) r infix 1 `iDelete`-{-| Performs an inclusive delete against a relvar. Also not SQL DELETE. This will fail if the second argument is not a subset of the relation variable.+{-| Performs an inclusive delete against a relvar. Also not SQL DELETE. This will+fail if the second argument is not a subset of the relation variable. >>> iDelete sp $ relation [rTuple (sno .=. "S6", pno .=. "P7", qty .=. 99), rTuple (sno .=. "S4", pno .=. "P4", qty .=. 300), rTuple (sno .=. "S7", pno .=. "P8", qty .=. 200)] *** Exception: Tuples not found in relvar SuppliersPartsDB/SP.rv:
src/Database/HaskRel/Relational/Unicode.hs view
@@ -2,17 +2,21 @@ {-| Module : Database.HaskRel.Relational.Unicode-Description : Functions pertaining to relational theory or set theory named with unicode characters+Description : Functions pertaining to relational theory or set theory named with+ unicode characters Copyright : © Thor Michael Støre, 2015 License : GPL v2 without "any later version" clause Maintainer : thormichael át gmail døt com Stability : experimental -Functions pertaining to relational theory or set theory named with non-ASCII unicode characters, primarily infix operators. Each of these is a synonym for an alphabetically named prefix function.+Functions pertaining to relational theory or set theory named with non-ASCII+unicode characters, primarily infix operators. Each of these is a synonym for an+alphabetically named prefix function. See also: http://hackage.haskell.org/package/base-unicode-symbols -TODO: Fix operator precedence, right now one may need to apply more parenthesis than should be necessary.+TODO: Fix operator precedence, right now one may need to apply more parenthesis+than should be necessary. -} module Database.HaskRel.Relational.Unicode (@@ -70,7 +74,8 @@ -- | Left semidifference. As @(flip 'Database.HaskRel.Relational.Expression.notMatching')@. r1 ▻ r2 = notMatching r2 r1 -{-| Times. The special case of natural join where the headings of the relations are disjoint.+{-| Times. The special case of natural join where the headings of the relations+are disjoint. >>> rPrint$ ( sp `projectAllBut` (rHdr (sno)) ) × ( s `projectAllBut` (rHdr (sno)) ) ...@@ -79,11 +84,15 @@ -} l × r = times l r -{-| Attribute intersecting natural join. The special case of natural join where the headings of the relations are not disjoint.+{-| Attribute intersecting natural join. The special case of natural join where+the headings of the relations are not disjoint. -Using the "box times" or "squared times" (U+22A0) symbol is my own solution. As with the name "(attribute) intersecting natural join" suggestions are welcome.+Using the "box times" or "squared times" (U+22A0) symbol is my own solution. As+with the name "(attribute) intersecting natural join" suggestions are welcome. -As mentioned in "Database.HaskRel.Relational.Algebra", this operation doesn't have a single identity value, although it holds that for any given relation value @r@, @r ⊠ r = r@+As mentioned in "Database.HaskRel.Relational.Algebra", this operation doesn't+have a single identity value, although it holds that for any given relation+value @r@, @r ⊠ r = r@ >>> rPrint$ sp ⊠ s ...@@ -95,7 +104,10 @@ {-| Restriction. -Note that the symbol used here is the divisor symbol, which looks the same but is distinct from the vertical bar, or pipe. However, since the vertical bar is used in Haskell for different purposes and is for that reason not a valid infix operator symbol, this is used instead.+Note that the symbol used here is the divisor symbol, which looks the same but+is distinct from the vertical bar, or pipe. However, since the vertical bar is+used in Haskell for different purposes and is for that reason not a valid infix+operator symbol, this is used instead. >>> rPrint$ p ∣ (\[pun|weight|] -> weight < 17.5) ...@@ -136,12 +148,15 @@ {-| Projection. -Note that no matter how greek it is π is still a character, and Haskell therefore treats it as a prefix operator, which is in line with how it is employed.+Note that no matter how greek it is π is still a character, and Haskell+therefore treats it as a prefix operator, which is in line with how it is+employed. >>> rPrint$ π (rHdr (color,city)) p ... -As 'Database.HaskRel.Relational.Expression.project', but note how the operands are reversed.+As 'Database.HaskRel.Relational.Expression.project', but note how the operands+are reversed. -} π a r = project r a @@ -150,7 +165,8 @@ {-| Relational assignment operator. -This uses the COLON EQUALS UTF-8 character (\&\#8788;), the ASCII variant := wouldn't be allowed in Haskell since it starts with a colon.+This uses the COLON EQUALS UTF-8 character (\&\#8788;), the ASCII variant @ := @+wouldn't be allowed in Haskell since it starts with a colon. >>> s ≔ s' >>>
src/Database/HaskRel/Relational/Variable.hs view
@@ -34,13 +34,15 @@ -- == Relation variable reference == -- -{-| Relation variable reference. This type has a phantom type variable, which generally calls for the type to be explicity specified:+{-| Relation variable reference. This type has a phantom type variable, which+generally calls for the type to be explicity specified: @ s = Relvar "SuppliersPartsDB/S.rv" :: Relvar '[SNO, SName, Status, City]@ -} data Relvar (a::[*]) = Relvar { relvarPath :: FilePath } --- | Gives the type a relvar results in. Note that the value this results in will always be @undefined@.+-- | Gives the type a relvar results in. Note that the value this results in+-- will always be @undefined@. relvarType :: Relvar a -> Relation a relvarType rv = undefined
src/Database/HaskRel/Support.hs view
@@ -7,7 +7,10 @@ Maintainer : thormichael át gmâïl døt cöm Stability : experimental -Predicates and aggregates useful to relational querying. These are not part of the relational algebra but are useful in relational queries in restriction predicates, and extension and summarization expressions. It also has great utility in non-relational queries, such as when performing aggregations.+Predicates and aggregates useful to relational querying. These are not part of+the relational algebra but are useful in relational queries in restriction+predicates, and extension and summarization expressions. It also has great+utility in non-relational queries, such as when performing aggregations. -} module Database.HaskRel.Support@@ -24,7 +27,9 @@ infix 5 &= --- | Reverse predicate application. As "Data.Function.&", the reverse function application operator, but restricted to a boolean result.+{-| Reverse predicate application. As "Data.Function.&", the reverse function+application operator, but restricted to a boolean result.+-} (&=) :: a -> ( a -> Bool ) -> Bool (&=) x f = f x @@ -159,7 +164,10 @@ │ 350.0 │ S2 │ └────────────────────┴───────────────┘ -Note the explicit type in the definition of @_qtys@; it is neccessary to provide a specific type for 'pt' to format it as a table, even when an expression would otherwise be correct without this. The following would blow up if prefixed with "pt$":+Note the explicit type in the definition of @_qtys@; it is neccessary to provide+a specific type for 'pt' to format it as a table, even when an expression would+otherwise be correct without this. The following would blow up if prefixed with+"pt$": >>> group sp (rHdr (pno,qty)) ((qtys .=.) . avg . agg qty) fromList [Record{qtys=200.0,sno="S3"},Record{qtys=216.66666666666666,sno="S1"},Record{qtys=300.0,sno="S4"},Record{qtys=350.0,sno="S2"}]@@ -168,7 +176,8 @@ avg xs = realToFrac (sum xs) / genericLength xs -{- | Minimum of several values, defaulting to the second argument if there are no elements.+{- | Minimum of several values, defaulting to the second argument if there are no+elements. >>> pt$ group sp (rHdr (pno,qty)) ((qtys .=.) . (`minx` 0) . agg qty) ┌─────────────────┬───────────────┐@@ -190,7 +199,8 @@ else F.minimum fl -} -{- | Maximum of several values, defaulting to the second argument if there are no elements+{- | Maximum of several values, defaulting to the second argument if there are no+elements >>> pt$ group sp (rHdr (pno,qty)) ((qtys .=.) . (`maxx` 0) . agg qty) ┌─────────────────┬───────────────┐