.. _generalised-list-comprehensions:
Generalised (SQL-like) List Comprehensions
------------------------------------------
.. index::
single: list comprehensions; generalised
single: extended list comprehensions
single: group
single: SQL
.. extension:: TransformListComp
:shortdesc: Enable generalised list comprehensions.
:since: 6.10.1
Allow use of generalised list (SQL-like) comprehension syntax. This
introduces the ``group``, ``by``, and ``using`` keywords.
Generalised list comprehensions are a further enhancement to the list
comprehension syntactic sugar to allow operations such as sorting and
grouping which are familiar from SQL. They are fully described in the
paper `Comprehensive comprehensions: comprehensions with "order by" and
"group by" `__,
except that the syntax we use differs slightly from the paper.
The extension is enabled with the extension :extension:`TransformListComp`.
Here is an example:
::
employees = [ ("Simon", "MS", 80)
, ("Erik", "MS", 100)
, ("Phil", "Ed", 40)
, ("Gordon", "Ed", 45)
, ("Paul", "Yale", 60) ]
output = [ (the dept, sum salary)
| (name, dept, salary) <- employees
, then group by dept using groupWith
, then sortWith by (sum salary)
, then take 5 ]
In this example, the list ``output`` would take on the value:
::
[("Yale", 60), ("Ed", 85), ("MS", 180)]
There are three new keywords: ``group``, ``by``, and ``using``. (The
functions ``sortWith`` and ``groupWith`` are not keywords; they are
ordinary functions that are exported by ``GHC.Exts``.)
There are five new forms of comprehension qualifier, all introduced by
the (existing) keyword ``then``:
- ::
then f
This statement requires that
f
have the type
forall a. [a] -> [a]
. You can see an example of its use in the motivating example, as
this form is used to apply
take 5
.
- ::
then f by e
This form is similar to the previous one, but allows you to create a
function which will be passed as the first argument to f. As a
consequence f must have the type
``forall a. (a -> t) -> [a] -> [a]``. As you can see from the type,
this function lets f "project out" some information from the elements
of the list it is transforming.
An example is shown in the opening example, where ``sortWith`` is
supplied with a function that lets it find out the ``sum salary`` for
any item in the list comprehension it transforms.
- ::
then group by e using f
This is the most general of the grouping-type statements. In this
form, f is required to have type
``forall a. (a -> t) -> [a] -> [[a]]``. As with the ``then f by e``
case above, the first argument is a function supplied to f by the
compiler which lets it compute e on every element of the list being
transformed. However, unlike the non-grouping case, f additionally
partitions the list into a number of sublists: this means that at
every point after this statement, binders occurring before it in the
comprehension refer to *lists* of possible values, not single values.
To help understand this, let's look at an example:
::
-- This works similarly to groupWith in GHC.Exts, but doesn't sort its input first
groupRuns :: Eq b => (a -> b) -> [a] -> [[a]]
groupRuns f = groupBy (\x y -> f x == f y)
output = [ (the x, y)
| x <- ([1..3] ++ [1..2])
, y <- [4..6]
, then group by x using groupRuns ]
This results in the variable ``output`` taking on the value below:
::
[(1, [4, 5, 6]), (2, [4, 5, 6]), (3, [4, 5, 6]), (1, [4, 5, 6]), (2, [4, 5, 6])]
Note that we have used the ``the`` function to change the type of x
from a list to its original numeric type. The variable y, in
contrast, is left unchanged from the list form introduced by the
grouping.
- ::
then group using f
With this form of the group statement, f is required to simply have
the type ``forall a. [a] -> [[a]]``, which will be used to group up
the comprehension so far directly. An example of this form is as
follows:
::
output = [ x
| y <- [1..5]
, x <- "hello"
, then group using inits]
This will yield a list containing every prefix of the word "hello"
written out 5 times:
::
["","h","he","hel","hell","hello","helloh","hellohe","hellohel","hellohell","hellohello","hellohelloh",...]