# .hs-boot files : should I use them?

**URL:** <https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164>\
**Category:** Uncategorized\
**Created:** [March 26, 2024, 10:47pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164 "2024-03-26T22:47:16Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![haroldcarr](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/haroldcarr/32/4173_2.png) [@haroldcarr](https://discourse.haskell.org/u/haroldcarr)\
**Post date:** [March 26, 2024, 10:47pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/1 "2024-03-26T22:47:16Z")

</div>

.hs-boot files “solve” cyclic-dependencies

[https://wiki.haskell.org/Mutually\_recursive\_modules](https://wiki.haskell.org/Mutually_recursive_modules)

What is the current wisdom on whether to use them or not?

If they are used, what kinds of problems do they introduce?

---

<div class="post-metadata">

**Author:** ![f-a](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/f-a/32/2740_2.png) [@f-a](https://discourse.haskell.org/u/f-a)\
**Post date:** [March 26, 2024, 11:21pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/2 "2024-03-26T23:21:14Z")

</div>

You need to keep `Module.hs` and `Module.hs-boot` in sync. This might be bothersome if you are not yet settled on a design and are modifying small things.

They are not first class citizens, so there are some GHC limitations (you can’t use families inside them), some malformed `.hs-boot` modules will cause GHC to crash or emit unuseful errors.

Same thing for other Haskell tools like cabal and I suppose HLS, there are some small nitpicks especially in error reporting which haven’t been ironed out yet.

You decide whether it is worth the gain.

---

<div class="post-metadata">

**Author:** ![Vlix](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/vlix/32/1551_2.png) [@Vlix](https://discourse.haskell.org/u/Vlix)\
**Post date:** [March 26, 2024, 11:50pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/3 "2024-03-26T23:50:44Z")

</div>

If creating a more organized module structure solves the problem, do that.  
If not (or you just can’t be bothered) it’s not a faux-pas to use `.hs-boot` files or anything, so go ahead.

As @f-a mentioned, you might encounter some annoyances if you do, but as long as you use it sparingly, it shouldn’t be too much of a problem. (And if it does become a problem, then reorganize your modules so you don’t need the `.hs-boot` files 😛 )

---

<div class="post-metadata">

**Author:** ![Kleidukos](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/kleidukos/32/1213_2.png) [@Kleidukos](https://discourse.haskell.org/u/Kleidukos)\
**Post date:** [March 27, 2024, 8:39am UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/4 "2024-03-27T08:39:49Z")

</div>

If you have cyclic dependencies, you have an architectural problem. Most organisations would absolutely reject a PR that introduces hs-boot, because it increases the complexity and is a poisoned gift for the next engineers who have to make the codebase evolve.

It’s better to tackle the root cause of a cycle early on.

---

<div class="post-metadata">

**Author:** ![romes](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/romes/32/2912_2.png) [@romes](https://discourse.haskell.org/u/romes)\
**Post date:** [March 27, 2024, 10:43pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/5 "2024-03-27T22:43:21Z")

</div>

I agree that some (most?) cyclical module imports are caused by not-ideal module hierarchies, and the solution there is to re-think that structure.

I disagree with “cyclical modules are evil”. Some well-designed structures use module dependency cycles reasonably, and hs-boot modules are perfectly suited for breaking those cycles to please the compiler.

For instance, I’d rather avoid shoving every type into a single `Types` module or module hierarchy. Or having 4000 line modules of mixed domains just to avoid the cycle. That is, I don’t think “avoid cycles at all costs” is necessarily constructive.

Despite some limitations mentioned by others (e.g. lack of something regarding families), hs-boot modules are battle tested. For the majority of cycle-breaking use cases they should fit the bill without issue.

---

<div class="post-metadata">

**Author:** ![michaelpj](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/michaelpj/32/417_2.png) [@michaelpj](https://discourse.haskell.org/u/michaelpj)\
**Post date:** [March 28, 2024, 11:26am UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/6 "2024-03-28T11:26:57Z")

</div>

> [@Kleidukos](#):
>
> Most organisations would absolutely reject a PR that introduces hs-boot, because it increases the complexity and is a poisoned gift for the next engineers who have to make the codebase evolve.

I think this is a bit hyperbolic. I think the claim about most organizations rejecting such PRs is also false; mine would not 🙂

In particular, I find the following line of thought helpful: _within_ a module, we have unrestricted mutual recursion between all declarations, and nobody finds this strange or concerning. If you chop such a module into several modules, you might now have multiple modules with mutual recursion between them, but the structure of the actual definitions is identical. I claim that if it is architecturally problematic afterwards then it was architecturally problematic before… and yet I have never heard anyone argue that complex cyclic dependencies within a module are bad 🤷‍♂️

I do agree that it can make the module structure harder to keep in your head if abused, but changing the module structure to avoid cycles can _also_ make it harder to comprehend. “Why is this utility function on `Foo` in a separate module by itself instead of next to the definition of `Foo`? Ah, because otherwise it would make a module cycle…”

It’s not actually conceptually very hard to say what happens in a program with cyclic module dependencies (again, GHC can do it fine within a single module). I worked on a programming language that had unrestricted cyclic module dependencies, and it was fine.

---

<div class="post-metadata">

**Author:** ![sgraf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sgraf/32/392_2.png) [@sgraf](https://discourse.haskell.org/u/sgraf)\
**Post date:** [March 28, 2024, 11:44am UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/7 "2024-03-28T11:44:11Z")

</div>

Note also that mutual recursion across modules is common in languages such as Java or C#. I think it makes great sense not wanting to think about mutual recursion; you can still use a tool to visualise dependency SCCs.

C# goes even further and provides the ability to split individual _classes_ and even _methods_ over multiple files: [Partial Classes and Methods - C# Programming Guide - C# | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods). As the docs say, this is beneficial for code gen tools; very pragmatic.

Really, what’s problematic about mutually recursive modules in GHC is that we have to write these dreadful .hs-boot files rather than letting the compiler infer them for us. This is the corresponding GHC issue to fix this: [#1409: Allow recursively dependent modules transparently (without .hs-boot or anything) · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/1409).

---

<div class="post-metadata">

**Author:** ![hasufell](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hasufell/32/1250_2.png) [@hasufell](https://discourse.haskell.org/u/hasufell)\
**Post date:** [March 28, 2024, 4:29pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/8 "2024-03-28T16:29:18Z")

</div>

hs boot files are fine. They clearly show what parts are “shared”.

I sometimes end up with them if:

- I want to keep types (and their instances) strictly in a separate module `Types.hs`
- have a `Utility.hs` module
- need some of those utility functions in my type instances
- also import the Types in the utility module

There are two ways to solve this:

- using orphan instances
- mix everything together in an internal module and then do clean re-exports

Mushing everything into an internal module just destroyed your code architecture. Using orphan instances is debatable too.

Still… doing hs boot files often/prematurely may be a sign of bad code architecture. It’s a good exercise to try to solve the cyclic imports in another fashion. But if you find that really takes tremendous effort or leads to weird code structure, just stop.

* * *

There are also other ways you can end up with cyclic issues: if you want a set of functions in a separate module for “visibility” reasons, like grouping unsafe stuff. That won’t necessarily follow traditional tree hierarchies.

* * *

I think the main issue is ergonomics: many Haskell users don’t know how to exactly use hs boot files to break cycles. If you’ve done that a few times, it doesn’t seem very hard, but it can indeed be confusing to do manually. Why does the compiler not do it for us automatically?

---

<div class="post-metadata">

**Author:** ![chreekat](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chreekat/32/2669_2.png) [@chreekat](https://discourse.haskell.org/u/chreekat)\
**Post date:** [March 28, 2024, 4:58pm UTC](https://discourse.haskell.org/t/hs-boot-files-should-i-use-them/9164/9 "2024-03-28T16:58:53Z")

</div>

See [When is a module too big? When is a module too small? - #5 by chreekat](http://discourse.haskell.org/t/when-is-a-module-too-big-when-is-a-module-too-small/8865/5) for an earlier discussion on this topic. I was also on team What’s-the-big-deal-with-boot-files. I still am, I guess, but I understand that they _may_ represent a code smell.
