Five years of decline

(the command-line parsing library)

It’s been a little over five years since I first published decline, a functional and UNIXy command-line parser for Scala. (If you’re not familiar with decline, the documentation might help.)

For me, five years is enough time to start forgetting things… so before that happens, this page collects some of the early history of decline and the motivations and ideas behind it. It may be interesting to others thinking about API design, or anyone who’s just curious about the library.

Why decline?

Imagine it: you’re me, in the mid-2010s, writing Scala. The teams I worked on tended to write in the popular “mostly functional” style, which stresses things like composition and immutability. For new team members, who often came in with a Java/OOP-ish background, this style was often fairly new. (As it was for me just a couple years earlier!)

One common way to learn a language is to write a little command-line application; and if you do, one of the first libraries you may reach for is a command-line parser. There were a couple popular Scala libraries for this already, but they were designed around mutation and made composition rather awkward. I don’t mean to slight these libraries; they do the job effectively, and they’re still very popular! But it sure doesn’t look that great for functional programming if it can’t handle the first simple problem you encounter.

If you want to figure out how to solve a problem with functional programming, Haskell is one obvious place to look. Haskellers in particular tend to reach for optparse-applicative; it has a clean, compositional API and generates very good help output out of the box. Unfortunately, porting Haskell code directly tends to make for very and obscure awkward Scala… a particularly bad idea if you’re hoping to make something approachable. So, decline’s job: pull the key ideas from optparse-applicative over to Scala, but in a way that feels as idiomatic as possible and plays nicely with the rest of the language.

Small and boring

If you’re working on a fun and exciting library like Spark or Cats — the sort of thing that someone might write a book about, or add as a skill to their CV — you can afford a bit of a learning curve if it pays off in the long run.

decline is not that kind of library. Relatively few people are especially fired up about command-line parsing. (This is true for most domains, I think… a bit of a trap for a library author, because almost any domain can be interesting once you get really deep into it, so it’s easy to complicate things in ways that won’t benefit most users.) So: one priority for decline was to keep the API as simple as possible. This has had a major impact on every part of the design:

This tradeoff is probably not the right one for all libraries, or all authors, and I suspect some users have found this annoying… but on balance, I think it’s been pretty fruitful for decline.

One way decline manages to add value without making the interface more complicated is by improving the quality of the error messages or the help. (Probably half the code in decline is dedicated to this, and there’s still some room to do better.) These aren’t especially glamorous features, but I think they do a lot to improve the experience for the end users of the CLI, as well as the developers who care about them.

On Alternative

Type classes are a common tool for writing generic code in Scala, but they also work a bit like design patterns are intended to. If you know some type implements a particular type class, that often gives you a pretty good hint about how to work with it; and when you’re designing a type it’s often helpful to think a bit about the type classes it might implement to guide your work.

decline is designed around the Alternative type class. This is the key idea it takes from optparse-applicative. It’s not especially common in Scala, though; decline is often the first place people encounter Alternative in the wild. Since it’s unfamiliar, this adds some learning curve, but it’s also critical for features like the autogenerated help output or powerful validation.

I’d love for Alternative to be better known: it is an extraordinarily useful type class. However, decline’s docs barely mention it! It’s possible to be effective with decline without understanding Alternative, so forefronting it might be unnecessarily daunting or confusing. Personally, I find a type class easier to get an intuition for when I’ve already seen the pattern a few times; I hope that having some experience with decline makes it easier for people to figure out Alternative later on, if they decide to take that plunge.

Releasing, and what comes after

decline existed for nearly a year before I really told anyone about it. I have never been that interested in the glamorous life of an open-source maintainer — for me, the responsible-maintenancey aspects of the job are not the fun parts, important as they are — but also wasn’t keen to just throw the codebase over the wall and walk away. I did eventually submit decline to the Typelevel incubator, and it’s mostly spread by word of mouth since then.

I have not regretted this so far! In part, the narrow scope of the library has helped keep things manageable… and while it does require saying “no” a lot, most users have been pretty good sports about it. decline has also attracted a wonderful set of contributors who’ve added features or helped the library keep up with change in the world around it; my sincere thanks to everyone who’s chosen to spend their time on this!

decline also owes a huge debt to Typelevel. Aside from the cats library, which is great and decline uses to full advantage — community moderation is a hard and often thankless job, and Typelevel has built what is perhaps the healthiest community in the Scala ecosystem. decline would likely not exist without it.