What makes library docs great [Library review: Iron]
Tags: functional, scala, documentation, tutorial
Published: 2023-06-30 03:21:46

📹 Hate reading articles? Check out the complementary video, which covers the same content: https://youtu.be/4pq1elOap9k

---

Using most of the libraries for the first time usually sucks. But it doesn’t have to be this way.

[Iron](https://github.com/Iltotore/iron) is a specific library for a specific use case, but we can all learn from it. It’s a simple no-bullshit library with great docs. Let’s review it and talk about documentation from the onboarding and user experience perspective.

## Initial impression

Here is how it usually goes. I see an unfamiliar library in the imports or dependencies, or a colleague suggests one. I look it up and go to the GitHub page.

> 👀 Note: I’m looking at the library (and readme) at [this point in time](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#readme).

First plus – right away when opening the readme – a concise description: **what** the library does, **why** I should care, and a bit of **how**.

![Readme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jyouwyl7n0ywvowfsr2y.png)


It doesn’t assume that I know what it does, nor what refined types do.

It’s impressive how often libraries don’t do this! And I spend significant time jumping through the docs and forums just to figure out what the library is for.

Readme also links to the microsite; we’ll return to it shortly. Let’s scroll through the rest.

The next part is a little [example](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#example):

```scala
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.*

def log(x: Double :| Positive): Double =
  Math.log(x) // Used like a normal `Double`

log(1.0) // Automatically verified at compile time.
log(-1.0) // Compile-time error: Should be strictly positive

val runtimeValue: Double = ???
log(runtimeValue.refine) // Explicitly refine your external values at runtime.

runtimeValue.refineEither.map(log) // Use monadic style for functional validation
runtimeValue.refineEither[Positive].map(log) // More explicitly
```

It showcases imports and elementary usage, so we know what to expect. The snippet is small enough to be quickly digestible but still representative – we see how to refine a type as positive and right away how to use it at compile and runtime.

Then, it briefly demonstrates [error messages](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#helpful-error-messages), [dependency](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#import-in-your-project) for sbt and mill, [platform support](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#platform-support), [adopters](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#adopters), and [useful links](https://github.com/Iltotore/iron/tree/7de3848bfa84481a912864c3cbe335b22bc075b8#useful-links).

If this isn’t **perfect to-the-point readme**, I don’t know what is.

## Great expectations

Quick side note: here’s what I typically want from the library site or the docs.

### When it’s my first time using a library:

- **Getting Started Guide**
    - I want introductory information every developer will need.
    - Such as an overview of the library and its components, a Hello World tutorial, and an introduction to the fundamental concepts.
    - *(And I don’t want to read a whole book full of definitions right away.)*
    - Bonus points: If the quickstart docs are usable for returning users, for example, when setting up a new project.
- **Tutorials** and concrete topics
    - (*“That book” that I didn’t want to read right away.)*
    - I want to dive deeper into the library as I’m getting familiar with it and wish to extend my usage or knowledge.
    - I don’t mind if the documentation holds my hand while we’re walking through the steps at this point.

### When I’m working with a library:

- **How-to guides** and examples
    - I want task-based instructions for how to do something or solve common problems.
    - I expect conceptual content organized by topic or task.
- **API Reference:**
    - I want readable API docs – the actual public API, not the internals of the sausage.
    - It should show how to create “things”, “interact” with things, and so on.

## [Microsite](https://iltotore.github.io/iron/docs/index.html)

With this in mind, let’s see what Iron’s microsite offers.

> ☀️ It offers a day/night toggle; who doesn’t like a good day/night toggle.

The welcome page exhibits links to navigate the docs and some [code](https://github.com/Iltotore/iron/tree/main/examples/formCats) [examples](https://github.com/Iltotore/iron/tree/main/examples/formZio).

### Discover

![Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6ca1dwl7fzzyzh8rh58.png)

The [Overview](https://iltotore.github.io/iron/docs/overview.html) page introduces the fundamental concepts: the purpose of this library, why refined types matter, and the use cases. It also includes a tiny hello world snippet, which we’ll try soon. This page is a big part of the **getting started guide** I wished for.

And then, it links to the [Getting Started](https://iltotore.github.io/iron/docs/getting-started.html) page to set up and start using Iron and [References](https://iltotore.github.io/iron/docs/reference/index.html) for details about the concepts of Iron.

[Getting Started](https://iltotore.github.io/iron/docs/getting-started.html) provides the rest of the **getting started guide**: dependency and standard imports. The import sections cover what they bring – which implicits and functions.

```scala
libraryDependencies += "io.github.iltotore" %% "iron" % "2.1.0"
```

> 👀 Notice that the header (in the top-right corner) shows the library version and allows us to navigate the docs at that point.
> 

> 💡 Other pages on this level are [Code of Conduct](https://iltotore.github.io/iron/docs/code-of-conduct.html) and [Contributing](https://iltotore.github.io/iron/docs/contributing.html). But we don’t care at this moment. We’re exploring the library and not planning to contribute anything right now.
>

### Reference, not reference

The next section of the docs is [Iron references](https://iltotore.github.io/iron/docs/reference/index.html), where we can “find detailed documentation about the main concepts of Iron”. 

Note that this is not the *API Reference* I introduced before – from my perspective, this section includes **tutorials** and **how-to guides.**

**Tutorials**: [Iron Type](https://iltotore.github.io/iron/docs/reference/iron-type.html), [Refinement Methods](https://iltotore.github.io/iron/docs/reference/refinement.html), [Constraint](https://iltotore.github.io/iron/docs/reference/constraint.html), and [Implication](https://iltotore.github.io/iron/docs/reference/implication.html). These cover the main datatypes and how to use them. After going through these sections, I could start using the library – I felt confident enough and didn’t feel like a learner anymore. These docs have concrete, practical examples.

**How-to guides:** [Creating New Types](https://iltotore.github.io/iron/docs/reference/newtypes.html). It shows how to create no-overhead new types using [opaque types](https://docs.scala-lang.org/scala3/book/types-opaque-types.html). Which is excellent, just a different type of documentation – it doesn’t fit the rest. This is not something a first-time user needs right off the bat.

### [Modules](https://iltotore.github.io/iron/docs/modules/index.html)

The last section documents **how-to** connect external modules: how to add support for JSON decoders, how to support validation, etc.

> … “support”/“interoperability” modules that provide out-of-the-box features to make Iron work seamlessly with other ecosystems.

Each page includes a short description, dependency, imports, and a **how-to** guide or an example.

### [Scaladoc](https://iltotore.github.io/iron/index.html)

We can seamlessly switch to the API docs – the **API Reference** we work with while using the library.

![API docs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iq2dz9ntfa6uzxcbkeu2.png)

The definitions are pretty concise but easy enough to navigate. Somehow it’s more pleasant than a typical Scaladoc.

> 🤔 I usually avoid Scaladocs. I don’t understand how to navigate them and go to the sources.

### Putting it to work

While we’re here, let’s try using this `Not` constraint. We can modify the hello-world example:

```scala
case class User(age: Int :| Not[Positive])
```

Compared to using just `Positive`, this refinement type has an opposite effect:

- `User(1)` doesn’t compile (`Could not satisfy a constraint`);
- `User(-1)` compiles.

---

🤔 What do you think happens if we add another `Not`?

```scala
case class User(age: Int :| Not[Not[Positive]])

User("1") // ???
User(-1)  // ???
```

*Exercise for the reader.*

---

### Adding json support

And to make it more interesting, let’s add a json [support](https://iltotore.github.io/iron/docs/modules/circe.html) using circe.

To get encoders and decoders for refined types, we have to add an `iron-circe` module:

```scala
"io.github.iltotore" %% "iron-circe" % "2.1.0"
```

---

💡 It doesn’t say which circe dependencies the example relies on, which might be a hurdle for people who never used either of the libraries. We can add dependencies from circe [Quick Start](https://circe.github.io/circe/#quick-start):

```scala
"io.circe" %% "circe-core" % "0.14.1",
"io.circe" %% "circe-parser" % "0.14.1",
"io.circe" %% "circe-generic" % "0.14.1",
```

---

And then, we draw the rest of the owl using the [example](https://iltotore.github.io/iron/docs/modules/circe.html#encoderdecoder-instances) provided on the page:

```scala
import io.circe.*
import io.circe.parser.*
import io.circe.generic.auto.*
import io.circe.syntax.*

import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.*
import io.github.iltotore.iron.circe.given

case class User(age: Int :| Not[Positive])

User(-8).asJson // { "age" : -8 }

decode[User]("""{"age": -8}""") // Right(User(-8))

decode[User]("""{"age": 18}""") // Left(DecodingFailure _)
```

## Expected `shouldEqual` Actual

**When it’s my first time using a library:** 

- **Getting Started Guide**
    - [Overview](https://iltotore.github.io/iron/docs/overview.html) covers an introduction to the fundamental concepts and a tiny hello world.
    - [Getting Started](https://iltotore.github.io/iron/docs/getting-started.html) covers the dependency and common imports (also handy for returning users).
- **Tutorials** and concrete topics
    - Some pages from [Iron References](https://iltotore.github.io/iron/docs/reference/index.html) cover main datatypes and how to use them.

**When I’m working with a library:**

- **How-to guides** and examples
    - [How To Create New Types](https://iltotore.github.io/iron/docs/reference/newtypes.html) from [Iron References](https://iltotore.github.io/iron/docs/reference/index.html) shows how to do a concrete thing.
    - [Modules](https://iltotore.github.io/iron/docs/modules/index.html) shows how to support external modules (interoperability).
- **API Reference:**
    - [API](https://iltotore.github.io/iron/index.html) docs (aka Scaladocs) show how to create “things”, “interact” with things, and so on.

## In Summary

The funny thing is that I wasn’t even a fan of using refinement-type libraries. For some reason, I used to believe they were ugly and there are other ways to check if the string is empty.

But then, the other day, a colleague was migrating some code to scala 3 and found that the existing library has no scala 3 support. I heard of Iron, so I suggested taking a look. And we were both pleasantly surprised.

The library and its docs looked so nit; I just wanted to start using it and talking about it.

Cause it’s a “review”, the grade is 4 docs out of 4.

---

{% embed https://youtu.be/4pq1elOap9k %}

--- 

💡 Useful links:
* [Iron](https://github.com/Iltotore/iron)
* [Iron site](https://iltotore.github.io/iron/docs/index.html)
* [Documentation system](https://documentation.divio.com/)