Posted on June 5, 2020 by Malte Brandy

A few weeks ago I got ghcide into nixpkgs, the package set of the package manager nix and the distribution nixos. Mind you, that was not a brave act of heroism or dark wizardry. Once I grasped the structure of the nixpkgs Haskell ecosystem, it was actually pretty easy. In this post I want to share my experience and tell you what I learned about the nixpkgs Haskell infrastructure and ghcide.

This post has four parts:

- Why can installing ghcide go wrong?
- How can you install ghcide on nix today?
- The nixpkgs Haskell ecosystem and dependency resolution
- How ghcide got fixed in nixpkgs

Haskell development tooling setup is infamous for being brittle and hard to setup. Every other day when someone asks on reddit or in the `#haskell`

channel, inescapably there will come at least one answer of the form “It’s not worth the pain. Just use ghcid.” I guess one point of this blog series is that this does not have to be the case anymore.

So, what were the reasons for this resignation? One is certainly that `ghcid`

is a really great and easy to use tool. I think it‘s clear that a well done language server can leverage you much further and to me `ghcide`

has already proven this.

One source of frustration is likely that successfully setting up a language server that is deeply interwoven with `ghc`

like `ghcide`

has one very important requirement. **You need to compile ghcide with the same ghc (version) as your project.** This shouldn‘t be hard to achieve nowadays - I’ll show how to do it if you use

`nix`

in this blogpost and I assume it‘s the default in other setups - but if you fail to meet this requirement you are in for a lot of trouble.So why exactly do we need to use “the same ghc” and what does that even mean? Frankly I am not totally sure. I am not a `ghcide`

developer. I guess sometimes you can get away with some slight deviations, but the general recommendation is to use the same `ghc`

version. I can tell you three situations that will cause problems or have caused problems for me:

Using another

`ghc`

release. E.g. using`ghcide`

compiled with`ghc`

8.8 on a`ghc`

8.6 project I get:`Step 4/6, Cradle 1/1: Loading GHC Session ghcide: /nix/store/3ybbc3vag4mpwaqglpdac4v413na3vhl-ghc-8.6.5/lib/ghc-8.6.5/ghc-prim-0.5.3/HSghc-prim-0.5.3.o: unknown symbol `stg_atomicModifyMutVarzh' ghcide: ghcide: unable to load package `ghc-prim-0.5.3'`

Using the same

`ghc`

version but linked against different external libraries like`glibc`

. This can happen when different releases of nixpkgs are involved. This could look like this:`Step 4/6, Cradle 1/1: Loading GHC Session ghcide: <command line>: can't load .so/.DLL for: /nix/store/hz3nwwc0k32ygvjn63gw8gm0nf9gprd8-ghc-8.6.5/lib/ghc-8.6.5/ghc-prim-0.5.3/libHSghc-prim-0.5.3-ghc8.6.5.so (/nix/store/6yaj6n8l925xxfbcd65gzqx3dz7idrnn-glibc-2.27/lib/libm.so.6: version `GLIBC_2.29' not found (required by /nix/store/hz3nwwc0k32ygvjn63gw8gm0nf9gprd8-ghc-8.6.5/lib/ghc-8.6.5/ghc-prim-0.5.3/libHSghc-prim-0.5.3-ghc8.6.5.so))`

or like this

```
Unexpected usage error
can't load .so/.DLL for: /nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27/lib/libpthread.so
(/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27/lib/libpthread.so.0: undefined symbol:
__libc_vfork, version GLIBC_PRIVATE)
```

- Using the same
`ghc`

release but with a patch to`ghc`

. This e.g. happened to me while using the`obelisk`

framework which uses a modified`ghc`

.

To sum up, both ghcs should come from the same source and be linked against the same libraries. Your best bet is to use the same binary. But that is not necessary.

When you want to use `ghcide`

with nix you now have two options. Either `haskellPackages.ghcide`

from nixpkgs or `ghcide-nix`

which uses the `haskell.nix`

ecosystem. I will describe both solutions and their pros and cons from my point of view.

First make sure you are on a new enough version of nixpkgs. You can try installing `ghcide`

user or system wide, with e.g. `nix-env -iA haskellPackages.ghcide`

or via your `configuration.nix`

on nixos. But that has a greater danger of being incompatible with the `ghc`

you are using in your specific project. The less brittle and more versatile way is to configure `ghcide`

in your projects `shell.nix`

. You probably already have a list with other dev tools you use in there, like `with haskellPackages; [ hlint brittany ghcide ]`

. Just add `ghcide`

in that list and you are good to go. See e.g. this post for a recent discussion about a Haskell dev setup with nix. If you are stuck with an old nixpkgs version, have a look at the end of part 4.

- Easy to setup
- Builds ghcide with the same ghc binary as your project, so no danger of incompatabilities between ghc and ghcide.

- We only have released versions of ghcide in nixpkgs. If you use nixpkgs-stable it might not even be the last release.
- When you use another
`ghc`

version than the default in your nixpkgs version, nix will compile ghcide on your computer because it isn‘t build by hydra. (build times are totally fine though.)

You can import the `ghcide-nix`

repo as a derivation and install the ghcide from there. Consult the README for more details.

- Cached binaries for all supported
`ghc`

versions via cachix. - Always a recent version from the ghcide master branch.
- Definitely recommended when you are already using the
`haskell.nix`

infrastructure for your project.

- Danger of incompatibilities, when your nixpkgs version and the pinned one of
`ghcide-nix`

don‘t match. - Not compatible with a patched ghc, which is not build for the
`haskell.nix`

infrastructure. - Larger nix store closure.

**EDIT:** I have been made aware of an alternative method to install `ghcide`

with `haskell.nix`

:

you can add it to a haskell.nix shell (one created with the

`shellFor`

function) with`x.shellFor { tools = { ghcide = "0.2.0"; }; }`

. This will build`ghcide`

with the`ghc`

version in the shell.

Have a look at the `haskell.nix`

documentation for more details.

Of course, after installing you need to test `ghcide`

and possible write a `hie.yaml`

file to get `ghcide`

to work with a specific project. This is not very nix specific and will probably change in the future, so I don‘t dive into it right now. Consult the readmes of `ghcide`

and `hie-bios`

.

There is though one point to note here and that is package discovery. `ghcide`

needs to know all the places that `ghc`

uses to lookup dependencies. When (and I think only when) you use the `ghc.withPackages`

function from `nixpkgs`

the dependencies are provided to `ghc`

via environment variables set in a wrapper script. In general `ghcide`

will not know about this variables and fail to find dependencies. E.g.:

```
Step 4/6, Cradle 1/2: Loading GHC Session
ghcide: <command line>: cannot satisfy -package-id aeson-1.4.7.1-5lFE4NI0VYBHwz75Ema9FX
```

To prevent this you need to find a way to set those environment variables when starting `ghcide`

. I have a PR underway which should do this for you if you install `ghcide`

by putting it in the same `withPackages`

list.

This section might be slightly off-topic here, so feel free to skip it. I think this is really useful to know if you work with Haskell and nixpkgs and I regard it as necessary context to understand the fix outlined in part 4.

Dependency resolution problems have a long history in Haskell, but today there are two solutions that both work quite well in general.

- Specify upper and lower bounds for every dependency in your cabal file and let cabal figure out a build plan. The times of cabal hell are over and this works quite well. Notably this is the way ghcide is supposed to be compiled in general.
- Pin a stack LTS release for your dependencies and pin the version for packages not on stackage.

Now solution two is in some sense less complex to use, because at compile time you don‘t need to construct a build plan. Of course, as I said, today cabal can do this for you very smoothly, which is why I personally prefer the first approach.

So how does nixpkgs do it? Well basically solution two. Everyday a cronjob pulls a list of **all packages from a pinned stack LTS release** and creates a derivation for every one of them. It also pulls **all other packages from hackage** and creates a derivation for the **latest released version** of them. (This happens on the haskell-updates branch of nixpkgs which get‘s normally merged into nixpkgs master i.e. unstable once per week. So then, you ask, how does cabal2nix do dependency resolution? Well the short form is, it doesn‘t. What I mean by that is: It completely ignores any version bounds given in a cabal file or a pinned stack LTS release. It will just **take the one version of every dependency that is present in nixpkgs** by the method I told you above.

When I first learned about this I thought this was ludicrous. This is prone to fail. And indeed it does. For a large number of packages the build will either fail at compile time or more often cabal will complain that it can‘t create a build plan. What that actually means: cabal says the one build plan we provided it with is invalid because it does not match the given version bounds. duh. So that packages get automatically marked broken after hydra, the nixos build server, fails to build them. And oh boy, there are a lot of Haskell packages broken in nixpkgs.

Before grasping how this setup comes together, I was very frustrated by this. And I guess for others casually encountering broken Haskell packages in nixpkgs, without understanding this setup can be annoying.

What could be a suitable alternative to this for nixpkgs? Tough to say. We could try to use some solution like the go, rust or node ecosystem and check in a build plan for every package. Actually that can be a nice solution and if you are interested in that you should definitely checkout the `haskell.nix`

infrastructure. But that really does not go well together with providing all of hackage in nixpkgs. For starters having every version of every Haskell package in nixpkgs would already be very verbose. And Haskell dependency resolution is structured in a way that in one build all dependencies have to agree on the version of mutual further dependencies. That means two build plans that use the same version of one package might still need different builds of that package. As a result it could very well happen that your project could not profit a lot from the nixpkgs binary cache even when it had precompiled every version of every Haskell package.

There can probably be said a lot more about this, but I have accepted that the chosen solution in nixpkgs actually has a lot of advantages (mainly fewer compilation work for everyone) and I actually haven‘t encountered a package I couldn‘t get to build with nixpkgs. The truth is the best guess build plan nixpkgs provides us with is normally not very far away from a working build plan. And it actually is a reasonable build plan. As a Haskell developer I think it is a good rule of thumb to always make your project work with the newest versions of all dependencies on hackage. And then it‘s very likely that your package will also work in nixpkgs.

Above I complained that a lot of Haskell packages are broken in nixpkgs. In truth, all commonly used packages work and most other packages are very easy to fix.

So what can we do to fix a broken package on nixpkgs?

(Also watch this video if you are interested in this).

- Often the error is actually fixed by an upstream version bound change, so you can always just try to compile the package. If it works make a PR against nixpkgs to remove the broken flag.
- Often the problem is that the package can actually build with the supplied build plan but cabal doesn‘t believe us. So we can do a “jailbreak” and just tell cabal to ignore the version constraints. We don‘t do this by default because even if the package builds, it might now have changed semantics because of a change in a dependency. So a jailbreaked package should be tested and reported upstream so that the cabal restrictions of that package can get fixed.
- If those two don‘t help we can still override the build plan manually to use different versions of the dependencies, not the ones provided by nixpkgs by default.

And the third option is what needed to be done for ghcide.

There were the following problems on nixpkgs-20.03:

`hie-bios`

was broken because of failing tests. Test fails during nix builds are very often false positives, so I disabled the tests.`ghcide`

needed`regex-tdfa`

and`haddock-library`

newer than in the stack-lts. So I just used newer versions of those two libraries. This was not necessary on the`haskell-updates`

branch because it uses a new enough stack lts release.`ghcide`

pins the version of`haskell-lsp`

and`haskell-lsp-types`

. This will probably be the reason why maintaining`ghcide`

in nixpkgs will always be a little bit of manual work because, it would have to be by chance*exactly*the`haskell-lsp`

version from the stack lts release, to work without manual intervention.

So in summary only very few lines of code were needed to get `ghcide`

to work. If you are curious look at the commit. It

- enables the generation of
`haskell-lsp`

and`haskell-lsp-types`

0.19. - uses those packages as dependencies for
`ghcide`

- disables test for
`hie-bios`

- and marks
`ghcide`

and`hie-bios`

as unbroken.

Sometimes you are stuck with an older nixpkgs version. E.g. I wanted `ghcide`

to work with my obelisk project. Obelisk uses a pinned nixpkgs version *and* a patched ghc. So what I did was putting the overrides I describe above as overrides into my projects `default.nix`

. That‘s always a nice way to first figure out how to fix a dependency, but of course you help a lot more people if you find a way to upstream the fixes into nixpkgs.

I had to manually create some of the packages with a function called `callHackageDirect`

because the nixpkgs version in reflex-platform was so old. It’s kinda the last way out, but it is very flexible and should be enough to solve most dependency issues. If nothing else helps, create a build plan with cabal and reproduce it by hand with nix overrides. That actually worked for me, when I tried to get ghcide to run with obelisk.

Thank you for following me this long. I hope I have illuminated a bit the situation with getting Haskell packages and `ghcide`

specifically to run under nixpkgs. If someday you meet a broken Haskell package in nixpkgs you now hopefully know why, and how to fix it, or at least that fixing it is probably not hard and you should give it a shot.

Installing `ghcide`

for sure isn’t hard anymore. It even works in fairly custom special case development situations like obelisk. So my recommendation is, set it up right now, you won’t want to work without it anymore.

In this post I have touched a lot of topics, which could all use more concrete how-to explanations, and on all of them I am far from an expert. So if you think something is amiss or if you don’t understand something feel free to contact me and maybe we can clarify it.

A big thank you to everyone involved with ghcide, nixpkgs or obelisk who helped me with figuring all of this out! The nice people you meet are what actually makes all of this so much fun.

I personally am definitely looking forward to the first official release of haskell-language-server and I am sure we can land it in nixpkgs quickly. ghcide 0.2.0 will probably be merged into nixpkgs master around the same time that this post is getting released.

Index

Haskell—with its powerful type system—has a great support for type-level programming and it has gotten much better in the recent times with the new releases of the GHC compiler. But type-level programming remains a daunting topic even with seasoned haskellers. *Thinking with Types: Type-level Programming in Haskell* by Sandy Maguire is a book which attempts to fix that. I’ve taken some notes to summarize my understanding of the same.

This post was originally published on abhinavsarkar.net.

- Type-level Programming (TLP) is writing programs that run at compile-time, unlike term-level programming which is writing programs that run at run-time.
- TLP should be used in moderation.
- TLP should be mostly used
- for programs that are catastrophic to get wrong (finance, healthcare, etc).
- when it simplifies the program API massively.
- when power-to-weight ratio of adding TLP is high.

- Types are not a silver bullet for fixing all errors:
- Correct programs can be not well-typed.
- It can be hard to assign type for useful programs. e.g.
`printf`

from C.

- Types can turn possible runtime errors into compile-time errors.

*Cardinality*of a type is the number of values it can have ignoring bottoms. The values of a type are also called the*inhabitants*of the type.

`data Void -- no possible values. cardinality: 0 data Unit = Unit -- only one possible value. cardinality: 1 data Bool = True | False -- only two possible values. cardinality: 2`

- Cardinality is written using notation:
`|Void| = 0`

- Two types are said to be
*Isomorphic*if they have same cardinality. - An
*isomorphism*between types`a`

and`b`

is a pair of functions`to`

and`from`

such that:

`Either a b`

is a*Sum*type. Its number of inhabitants is sum of the number of inhabitants of type`a`

and`b`

like so:`|a|`

possible values with the`Left`

constructor and`|b|`

possible values with the`Right`

constructor. Formally:

`(a, b)`

is a*Product*type. Its number of inhabitant is the product of the number of inhabitants of types`a`

and`b`

. Formally:

- Some more examples:

`|Maybe a| = |Nothing| + |Just a| = 1 + |a| |[a]| = 1 + |a| + |a|^2 + |a|^3 + ... |Either a Void| = |a| + 0 = |a| |Either Void a| = 0 + |a| = |a| |(a, Unit)| = |a| * 1 = |a| |(Unit, a)| = 1 * |a| = |a|`

- Function types are exponentiation types.

For every value in domain `a`

there can be `|b|`

possible values in the range `b`

. And there are `|a|`

possible values in domain `a`

. So:

- Data can be represented in many possible isomorphic types. Some of them are more useful than others. Example:

`data TicTacToe1 a = TicTacToe1 { topLeft :: a , topCenter :: a , topRight :: a , middleLeft :: a , middleCenter :: a , middleRight :: a , bottomLeft :: a , bottomCenter :: a , bottomRight :: a } |TicTacToe1 a| = |a| * |a| * ... * |a| -- 9 times = |a|^9 emptyBoard1 :: TicTacToe1 (Maybe Bool) emptyBoard1 = TicTacToe1 Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing -- Alternatively data Three = One | Two | Three data TicTacToe2 a = TicTacToe2 (Three -> Three -> a) |TicTacToe2 a| = |a|^(|Three| * |Three|) = |a|^(3*3) = |a|^9 emptyBoard2 :: TicTacToe2 (Maybe Bool) emptyBoard2 = TicTacToe2 $ const $ const Nothing`

- Every logic statement can be expressed as an equivalent computer program.
- Helps us analyze mathematical theorems through programming.

- Since multiple equivalent representations of a type are possible, the representation in form of sum of products is considered the canonical representation of the type. Example:

`Either a (Either b (c, d)) -- canonical (a, Bool) -- not canonical Either a a -- same cardinality as above but canonical`

*Terms*are things manipulated at runtime.*Types*of terms are used by compiler to prove “things” about the terms.- Similarly,
*Types*are things manipulated at compile-time.*Kinds*of types are used by the compiler to prove “things” about the types. - Kinds are “the types of the Types”.
- Kind of things that can exist at runtime (terms) is
`*`

. That is, kind of`Int`

,`String`

etc is`*`

.

- There are kinds other than
`*`

. For example:

- Higher-kinded types have
`(->)`

in their kind signature:

`> :kind Maybe Maybe :: * -> * > :kind Maybe Int Maybe Int :: * > :type Control.Monad.Trans.Maybe.MaybeT Control.Monad.Trans.Maybe.MaybeT :: m (Maybe a) -> Control.Monad.Trans.Maybe.MaybeT m a > :kind Control.Monad.Trans.Maybe.MaybeT Control.Monad.Trans.Maybe.MaybeT :: (* -> *) -> * -> * > :kind Control.Monad.Trans.Maybe.MaybeT IO Int Control.Monad.Trans.Maybe.MaybeT IO Int :: *`

`-XDataKinds`

extension lets us create new kinds.- It lifts data constructors into type constructors and types into kinds.

```
> :set -XDataKinds
> data Allow = Yes | No
> :type Yes
Yes :: Allow
-- Yes is data constructor
> :kind Allow -- Allow is a type
Allow :: *
> :kind 'Yes
'Yes :: Allow
-- 'Yes is a type too. Its kind is 'Allow.
```

- Lifted constructors and types are written with a preceding
`'`

(called*tick*).

`-XDataKinds`

extension promotes built-in types too.- Strings are promoted to the kind
`Symbol`

. - Natural numbers are promoted to the kind
`Nat`

.

```
> :kind "hi"
"hi" :: GHC.Types.Symbol
-- "hi" is a type-level string
> :kind 123
123 :: GHC.Types.Nat
-- 123 is a type-level natural number
```

- We can do type level operations on
`Symbol`

s and`Nat`

s.

`> :m +GHC.TypeLits GHC.TypeLits> :kind AppendSymbol AppendSymbol :: Symbol -> Symbol -> Symbol GHC.TypeLits> :kind! AppendSymbol "hello " "there" AppendSymbol "hello " "there" :: Symbol = "hello there" GHC.TypeLits> :set -XTypeOperators GHC.TypeLits> :kind! (1 + 2) ^ 7 (1 + 2) ^ 7 :: Nat = 2187`

`-XTypeOperators`

extension is needed for applying type-level functions with symbolic identifiers.- There are type-level lists and tuples:

```
GHC.TypeLits> :kind '[ 'True ]
'[ 'True ] :: [Bool]
GHC.TypeLits> :kind '[1,2,3]
'[1,2,3] :: [Nat]
GHC.TypeLits> :kind '["abc"]
'["abc"] :: [Symbol]
GHC.TypeLits> :kind 'False ': 'True ': '[]
'False ': 'True ': '[] :: [Bool]
GHC.TypeLits> :kind '(6, "x", 'False)
'(6, "x", 'False) :: (Nat, Symbol, Bool)
```

- With the
`-XTypeFamilies`

extension, it’s possible to write new type-level functions as closed type families:

```
> :set -XDataKinds
> :set -XTypeFamilies
> :{
| type family And (x :: Bool) (y :: Bool) :: Bool where
| And 'True 'True = 'True
| And _ _ = 'False
| :}
> :kind And
And :: Bool -> Bool -> Bool
> :kind! And 'True 'False
And 'True 'False :: Bool
= 'False
> :kind! And 'True 'True
And 'True 'True :: Bool
= 'True
> :kind! And 'False 'True
And 'False 'True :: Bool
= 'False
```

- There are three types of
*Variance*(`T`

here a type of kind`* -> *`

):- Covariant: any function of type
`a -> b`

can be lifted into a function of type`T a -> T b`

. Covariant types are instances of the`Functor`

typeclass:

- Contravariant: any function of type
`a -> b`

can be lifted into a function of type`T b -> T a`

. Contravariant functions are instances of the`Contravariant`

typeclass:

- Covariant: any function of type
- Variance of a type
`T`

is specified with respect to a particular type parameter. A type`T`

with two parameters`a`

and`b`

could be covariant wrt.`a`

and contravariant wrt.`b`

. - Variance of a type
`T`

wrt. a particular type parameter is determined by whether the parameter appears in positive or negative*position*s.- If a type parameter appears on the left-hand side of a function, it is said to be in a negative position. Else it is said to be in a positive position.
- If a type parameter appears only in positive positions then the type is covariant wrt. that parameter.
- If a type parameter appears only in negative positions then the type is contravariant wrt. that parameter.
- If a type parameter appears in both positive and negative positions then the type is invariant wrt. that parameter.
- positions follow the laws of multiplication for their
*sign*s.

a | b | a * b |
---|---|---|

+ | + | + |

+ | - | - |

- | + | - |

- | - | + |

- Examples:

`newtype T1 a = T1 (Int -> a) -- a is in +ve position, T1 is covariant wrt. a. newtype T2 a = T2 (a -> Int) -- a is in -ve position, T2 is contravariant wrt. a. newtype T3 a = T3 (a -> a) -- a is in both -ve and +ve position. T3 is invariant wrt. a. newtype T4 a = T4 ((Int -> a) -> Int) -- a is in +ve position but (Int -> a) is in -ve position. -- So a is in -ve position overall. T4 is contravariant wrt. a. newtype T5 a = T5 ((a -> Int) -> Int) -- a is in -ve position but (a -> Int) is in -ve position. -- So a is in +ve position overall. T5 is covariant wrt. a.`

- Covariant parameters are said to be
*produced*or*owned*by the type. - Contravariant parameters are said to be
*consumed*by the type. - A type that has two parameters and is covariant in both of them is an instance of
`BiFunctor`

. - A type that has two parameters and is contravariant in first parameter and covariant in second parameter is an instance of
`Profunctor`

.

- Standard Haskell has no notion of scopes for types.
`-XScopedTypeVariables`

extension lets us bind type variables to a scope. It requires an explicitly`forall`

quantifier in type signatures.

```
-- This does not compile.
> :{
| comp :: (a -> b) -> (b -> c) -> a -> c
| comp f g a = go f
| where
| go :: (a -> b) -> c
| go f' = g (f' a)
| :}
<interactive>:11:11: error:
• Couldn't match expected type ‘c1’ with actual type ‘c’
‘c1’ is a rigid type variable bound by
the type signature for:
go :: forall a1 b1 c1. (a1 -> b1) -> c1
at <interactive>:10:3-21
‘c’ is a rigid type variable bound by
the type signature for:
comp :: forall a b c. (a -> b) -> (b -> c) -> a -> c
at <interactive>:7:1-38
• In the expression: g (f' a)
<interactive>:11:14: error:
• Couldn't match expected type ‘b’ with actual type ‘b1’
‘b1’ is a rigid type variable bound by
the type signature for:
go :: forall a1 b1 c1. (a1 -> b1) -> c1
at <interactive>:10:3-21
‘b’ is a rigid type variable bound by
the type signature for:
comp :: forall a b c. (a -> b) -> (b -> c) -> a -> c
at <interactive>:7:1-38
• In the first argument of ‘g’, namely ‘(f' a)’
<interactive>:11:17: error:
• Couldn't match expected type ‘a1’ with actual type ‘a’
‘a1’ is a rigid type variable bound by
the type signature for:
go :: forall a1 b1 c1. (a1 -> b1) -> c1
at <interactive>:10:3-21
‘a’ is a rigid type variable bound by
the type signature for:
comp :: forall a b c. (a -> b) -> (b -> c) -> a -> c
at <interactive>:7:1-38
• In the first argument of ‘f'’, namely ‘a’
-- But this does.
> :set -XScopedTypeVariables
> :{
| comp :: forall a b c. (a -> b) -> (b -> c) -> a -> c
| comp f g a = go f
| where
| go :: (a -> b) -> c
| go f' = g (f' a)
| :}
```

`-XTypeApplications`

extension lets us directly apply types to expressions:

```
> :set -XTypeApplications
> :type traverse
traverse
:: (Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
> :type traverse @Maybe
traverse @Maybe
:: Applicative f =>
(a -> f b) -> Maybe a -> f (Maybe b)
> :type traverse @Maybe @[]
traverse @Maybe @[]
:: (a -> [b]) -> Maybe a -> [Maybe b]
> :type traverse @Maybe @[] @Int
traverse @Maybe @[] @Int
:: (Int -> [b]) -> Maybe Int -> [Maybe b]
> :type traverse @Maybe @[] @Int @String
traverse @Maybe @[] @Int @String
:: (Int -> [String]) -> Maybe Int -> [Maybe String]
```

- Types are applied in the order they appear in the type signature. It is possible to avoid applying types by using a type with an underscore:
`@_`

`> :type traverse @Maybe @_ @_ @String traverse @Maybe @_ @_ @String :: Applicative w1 => (w2 -> w1 String) -> Maybe w2 -> w1 (Maybe String)`

- Sometimes the compiler cannot infer the type of an expression.
`-XAllowAmbiguousTypes`

extension allow such programs to compile.

```
> :set -XScopedTypeVariables
> :{
| f :: forall a. Show a => Bool
| f = True
| :}
<interactive>:7:6: error:
• Could not deduce (Show a0)
from the context: Show a
bound by the type signature for:
f :: forall a. Show a => Bool
at <interactive>:7:6-29
The type variable ‘a0’ is ambiguous
• In the ambiguity check for ‘f’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the type signature: f :: forall a. Show a => Bool
```

`Proxy`

is a type isomorphic to`()`

except with a phantom type parameter:

- With all the three extensions enabled, it is possible to get a term-level representation of types using the
`Data.Typeable`

module:

```
> :set -XScopedTypeVariables
> :set -XTypeApplications
> :set -XAllowAmbiguousTypes
> :m +Data.Typeable
Data.Typeable> :{
Data.Typeable| typeName :: forall a. Typeable a => String
Data.Typeable| typeName = show . typeRep $ Proxy @a
Data.Typeable| :}
Data.Typeable> typeName @String
"[Char]"
Data.Typeable> typeName @(IO Int)
"IO Int"
```

*Constraints*are a kind different than the types (`*`

).- Constraints are what appear on the left-hand side on the fat context arrow
`=>`

, like`Show a`

.

`> :k Show Show :: * -> Constraint > :k Show Int Show Int :: Constraint > :k (Show Int, Eq String) (Show Int, Eq String) :: Constraint`

- Type equalities
`(Int ~ a)`

are another way of creating Constraints.`(Int ~ a)`

says`a`

is same as`Int`

. - Type equalities are
- reflexive:
`a ~ a`

always - symmetrical:
`a ~ b`

implies`b ~ a`

- transitive:
`a ~ b`

and`b ~ c`

implies`a ~ c`

- reflexive:

*GADTs*are Generalized Algebraic DataTypes. They allow writing explicit type signatures for data constructors. Here is the code for a length-typed list using GADTs:

`> :set -XGADTs > :set -XKindSignatures > :set -XTypeOperators > :set -XDataKinds > :m +GHC.TypeLits GHC.TypeLits> :{ GHC.TypeLits| data List (a :: *) (n :: Nat) where GHC.TypeLits| Nil :: List a 0 GHC.TypeLits| (:~) :: a -> List a n -> List a (n + 1) GHC.TypeLits| infixr 5 :~ GHC.TypeLits| :} GHC.TypeLits> :type Nil Nil :: List a 0 GHC.TypeLits> :type 'a' :~ Nil 'a' :~ Nil :: List Char 1 GHC.TypeLits> :type 'b' :~ 'a' :~ Nil 'b' :~ 'a' :~ Nil :: List Char 2 GHC.TypeLits> :type True :~ 'a' :~ Nil <interactive>:1:9: error: • Couldn't match type ‘Char’ with ‘Bool’ Expected type: List Bool 1 Actual type: List Char (0 + 1) • In the second argument of ‘(:~)’, namely ‘'a' :~ Nil’ In the expression: True :~ 'a' :~ Nil`

- GADTs are just syntactic sugar for ADTs with type equalities. The above definition is equivalent to:

`> :set -XGADTs > :set -XKindSignatures > :set -XTypeOperators > :set -XDataKinds > :m +GHC.TypeLits GHC.TypeLits> :{ GHC.TypeLits| data List (a :: *) (n :: Nat) GHC.TypeLits| = (n ~ 0) => Nil GHC.TypeLits| | a :~ List a (n - 1) GHC.TypeLits| infixr 5 :~ GHC.TypeLits| :} GHC.TypeLits> :type 'a' :~ Nil 'a' :~ Nil :: List Char 1 GHC.TypeLits> :type 'b' :~ 'a' :~ Nil 'b' :~ 'a' :~ Nil :: List Char 2`

- Type-safety of this list can be used to write a safe
`head`

function which does not compile for an empty list:

`GHC.TypeLits> :{ GHC.TypeLits| safeHead :: List a (n + 1) -> a GHC.TypeLits| safeHead (x :~ _) = x GHC.TypeLits| :} GHC.TypeLits> safeHead ('a' :~ 'b' :~ Nil) 'a' GHC.TypeLits> safeHead Nil <interactive>:21:10: error: • Couldn't match type ‘1’ with ‘0’ Expected type: List a (0 + 1) Actual type: List a 0 • In the first argument of ‘safeHead’, namely ‘Nil’ In the expression: safeHead Nil In an equation for ‘it’: it = safeHead Nil`

We can use GADTs to build heterogeneous lists which can store values of different types and are type-safe to use.

First, the required extensions and imports:

`{-# LANGUAGE KindSignatures #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ScopedTypeVariables #-} module HList where import Data.Typeable`

`HList`

is defined as a GADT:

Example usage:

`*HList> :type HNil HNil :: HList '[] *HList> :type 'a' :# HNil 'a' :# HNil :: HList '[Char] *HList> :type True :# 'a' :# HNil True :# 'a' :# HNil :: HList '[Bool, Char]`

We can write operations on `HList`

:

`hLength :: HList ts -> Int hLength HNil = 0 hLength (x :# xs) = 1 + hLength xs hHead :: HList (t ': ts) -> t hHead (t :# _) = t`

Example usage:

`*HList> hLength $ True :# 'a' :# HNil 2 *HList> hHead $ True :# 'a' :# HNil True *HList> hHead HNil <interactive>:7:7: error: • Couldn't match type ‘'[]’ with ‘t : ts0’ Expected type: HList (t : ts0) Actual type: HList '[] • In the first argument of ‘hHead’, namely ‘HNil’ In the expression: hHead HNil In an equation for ‘it’: it = hHead HNil • Relevant bindings include it :: t (bound at <interactive>:7:1)`

We need to define instances of typeclasses like `Eq`

, `Ord`

etc. for `HList`

because GHC cannot derive them automatically yet:

`instance Eq (HList '[]) where HNil == HNil = True instance (Eq t, Eq (HList ts)) => Eq (HList (t ': ts)) where (x :# xs) == (y :# ys) = x == y && xs == ys instance Ord (HList '[]) where HNil `compare` HNil = EQ instance (Ord t, Ord (HList ts)) => Ord (HList (t ': ts)) where (x :# xs) `compare` (y :# ys) = x `compare` y <> xs `compare` ys instance Show (HList '[]) where show HNil = "[]" instance (Typeable t, Show t, Show (HList ts)) => Show (HList (t ': ts)) where show (x :# xs) = show x ++ "@" ++ show (typeRep (Proxy @t)) ++ " :# " ++ show xs`

The instances are defined recursively: one for the base case and one for the inductive case.

Example usage:

`*HList> True :# 'a' :# HNil == True :# 'a' :# HNil True *HList> True :# 'a' :# HNil == True :# 'b' :# HNil False *HList> True :# 'a' :# HNil == True :# HNil <interactive>:17:24: error: • Couldn't match type ‘'[]’ with ‘'[Char]’ Expected type: HList '[Bool, Char] Actual type: HList '[Bool] • In the second argument of ‘(==)’, namely ‘True :# HNil’ In the expression: True :# 'a' :# HNil == True :# HNil In an equation for ‘it’: it = True :# 'a' :# HNil == True :# HNil *HList> show $ True :# 'a' :# HNil "True@Bool :# 'a'@Char :# []"`

- Type families can be used to create new Constraints:

`> :set -XKindSignatures > :set -XDataKinds > :set -XTypeOperators > :set -XTypeFamilies > :m +Data.Constraint Data.Constraint> :{ Data.Constraint| type family AllEq (ts :: [*]) :: Constraint where Data.Constraint| AllEq '[] = () Data.Constraint| AllEq (t ': ts) = (Eq t, AllEq ts) Data.Constraint| :} Data.Constraint> :kind! AllEq '[Bool, Char] AllEq '[Bool, Char] :: Constraint = (Eq Bool, (Eq Char, () :: Constraint))`

`AllEq`

is a type-level function from a list of types to a constraint.- With the
`-XConstraintKinds`

extension,`AllEq`

can be made polymorphic over all constraints instead of just`Eq`

:

```
> :set -XConstraintKinds
Data.Constraint> :{
Data.Constraint| type family All (c :: * -> Constraint)
Data.Constraint| (ts :: [*]) :: Constraint where
Data.Constraint| All c '[] = ()
Data.Constraint| All c (t ': ts) = (c t, All c ts)
Data.Constraint| :}
```

- With
`All`

, instances for`HList`

can be written non-recursively:

`instance All Eq ts => Eq (HList ts) where HNil == HNil = True (a :# as) == (b :# bs) = a == b && as == bs`

I’m still in the process of reading the book and I’ll post the notes for the rest of the chapters in a later post. The complete code for `HList`

can be found here. For now, you can discuss this post on lobsters, r/haskell, hacker news, twitter or in the comments below.

If you liked this post, please leave a comment.

- 2020-10-27: Military alliances from a @wikidata query.
- 2020-10-26: Today's #haskell problem captures the Cypher of countries, continents, and airbases.
- 2020-10-23: Today's #haskell problem: "Unicode? What, even, is that?" ... sez Haskell, smh. Also, if you know how to get Text not to escape unicode points on output (to, say, a REST endpoint), much obliged if you told me this dark magic. Today's #haskell solution shows airbases (with their associated countries) added to the graph database.
- 2020-10-20: Next evolutionary step. We have Continents and countries as data structures. For today's #haskell problem let's find out how we can (can't?) merge in airbases with countries. Hoo, doggies! "Upload airbases to the graph database!" he said. "It'll be easy!" he said. Today's #haskell solution, if not in book-form, then in book-length!
- 2020-10-16: Map of a -> b or b -> a? ... If you're asking that question, why not Graph? Today's #haskell exercise. A mapping of continents to countries. Surprisingly, wikidata.org does not have this as data amenable to extraction.
- 2020-10-15: From continent->country mapping to country->continent mapping for today's #haskell problem. `sequence` is the secret-sauce for today's #haskell solution.
- 2020-10-14: Today's #haskell problem is to get countries by continent from a ... 'markdown' file? That is not markdown. Deal with it. Countries: meet your Continents.
- 2020-10-13: Okay. #StringsAreWrong. Everybody knows this. Wikidata: "Let's encode LongLats as strings ... IN JSON!" Please let's not. Today's #haskell exercise. The solution that produces airbases with lat/longs. REAL lat/longs, smh.
- 2020-10-12: Today's #haskell problem is airbases of the world from wikidata.org ... with some duplicates. Today's #haskell solution reads in JSON, even ... 'JSON' that encodes 'LongLat's (not lat/longs) as ... strings? Really? Yes, even points-as-strings. Remember: #StringsAreWrong ~ Richard A. O'Keefe, 26 April 1994
- 2020-10-09: Today's #haskell problem is this: Production data: "Let's see if we can make the simple act of parsing a 'JSON file' [that isn't a JSON file] impossible for the ol' el geophf!" Nice try, production data. Nice try.

Â

A striking visual example of recursion!