on master help sign in
squeal/scrap-your-nils.md
60 lines 2.6 kB history raw
1## Scrap your Nils
3One of the most useful types I've come across in Haskell is the type of
4"heterogeneous lists". This is the same as the [Rec](http://hackage.haskell.org/package/vinyl-0.8.1.1/docs/Data-Vinyl-Core.html)
5datatype from the [vinyl](http://hackage.haskell.org/package/vinyl) library.
6It's also the same as the [NP](http://hackage.haskell.org/package/generics-sop-0.3.2.0/docs/Generics-SOP-NP.html)
7datatype from the [generics-sop](http://hackage.haskell.org/package/generics-sop) library.
8Squeal makes heavy use of this type.
10```Haskell
11>>> import Generics.SOP (NP(..))
12>>> :i NP
13type role NP representational nominal
14data NP (a :: k -> *) (b :: [k]) where
15 Nil :: forall k (a :: k -> *). NP a '[]
16 (:*) :: forall k (a :: k -> *) (x :: k) (xs :: [k]).
17 (a x) -> (NP a xs) -> NP a (x : xs)
18 -- Defined in ‘Generics.SOP.NP’
19```
21This type allows us to construct "product" types, where the types of the individual
22"terms" are hosted at the type level.
24```Haskell
25>>> :set -XDataKinds
26>>> import Generics.SOP (I(..))
27>>> let example = I "foo" :* I pi :* Nil :: NP I '[String, Double]
28>>> example
29I "foo" :* I 3.141592653589793 :* Nil
30```
32One thing Squeal uses `NP` for is to form lists of aliases,
33using GHC's `OverloadedLabels` extension, hosting the names
34of the aliases themselves at the type level.
36```Haskell
37>>> :set -XKindSignatures -XOverloadedLabels -XFlexibleInstances -XMultiParamTypeClasses
38>>> import GHC.TypeLits
39>>> import GHC.OverloadedLabels
40>>> data Alias (alias :: Symbol) = Alias deriving (Eq,Ord,Show)
41>>> instance IsLabel label (Alias label) where fromLabel = Alias
42>>> let aliases = #foo :* #bar :* Nil :: NP Alias '["foo", "bar"]
43```
45However, it's very ugly to have to end every list with `:* Nil`.
46When I announced the release of Squeal, people rightly [complained](https://www.reddit.com/r/haskell/comments/6yr5v6/announcing_squeal_a_deep_embedding_of_sql_in/dmq8vvn)
47about the syntactic noise. Luckily, there's a neat trick we can use to get rid of it.
48Making an `IsLabel` instance not only for elements of our list
49but also for lists of length 1, we can ask GHC to interpret
50the last label as a list.
52```Haskell
53>>> instance IsLabel label (NP Alias '[label]) where fromLabel = Alias :* Nil
54>>> let aliases' = #foo :* #bar :: NP Alias '["foo", "bar"]
55```
57Version 0.3.1 of Squeal enables the "Scrap your Nils" trick for
58heterogeneous lists of `Alias`es, `Aliased` expressions, `PGlabel`s and `By`s
59with the typeclasses `IsLabel`, `IsQualified`, `IsPGlabel`,
60and the new `Aliasable` typeclass, to eliminate all need of using `Nil` in a list.