Haskell in Production: Mercury

Gints Dreimanis
Article by Gints Dreimanis
August 28th, 2022
10 min read

In our Haskell in Production series, we interview developers and technical leaders from companies that use Haskell for real-world tasks. We cover benefits, downsides, common pitfalls, and tips for building useful Haskell products.

Our today’s guest is Max Tagher. He’s the co-founder and CTO of Mercury, a company that provides banking products to startups. Read further to learn where Mercury uses Haskell, why they chose it, and what they like about it.

Interview with Max Tagher

Could you give our readers a brief introduction to Mercury and your role there?

Mercury provides banking* products that empower founders to build great startups. We offer checking and savings accounts with no monthly fees, debit cards, ACH payments, checks, and free domestic and USD international wire transfers. Complementing those are advanced features like Treasury management, user permissions, and Venture Debt lending. Mercury’s major competitive advantages include having an excellent website, online signup, almost no fees, and a compliance department that understands how startups work. You can see every feature of Mercury on our demo website.

I’m one of the co-founders of Mercury, and I also run the engineering department as CTO.

Card - Max_Tagher.png

*Mercury is a financial technology company, not a bank. Banking services provided by Choice Financial Group and Evolve Bank & Trust®; Members FDIC.

Where in your stack do you use Haskell?

Mercury’s backend is exclusively in Haskell. For completeness, our stack is TypeScript and React on the frontend, Nix and AWS for our infrastructure, Postgres for our database, Swift for iOS and Kotlin for Android.

Why did you decide to choose Haskell for the project?

At the previous company where my co-founders and I worked, we used Ruby on Rails for the backend. I found that the dynamic nature of the language and framework meant we had a lot of unnecessary runtime errors, like the equivalent of null pointer errors or even typos. We knew we wanted to use something statically typed for Mercury, since customers have higher correctness expectations for their bank.

I looked at a few different languages that I wasn’t satisfied with. Common themes were the limitations of the typesystem (e.g. Go), dependence on some base language (e.g. Scala inherits a lot of Java’s warts, and may require learning both languages), or immaturity of the language. Haskell has a really powerful typesystem, and has been built that way from the ground up, so libraries are likely to use those features.

I had used Haskell in open-source contexts, contributing to the Yesod web framework and Persistent database library, so I was familiar with the benefits. I also knew a few people in the Haskell community that I could reach out to for advice and referrals.

Finally, I just really liked writing Haskell.

Are there any specific qualities of Haskell that make it valuable for fintech projects?

Haskell has a reputation of being more commonly used in finance and cryptocurrency contexts. I think this is largely as a general-purpose programming language with nice correctness properties and maybe good parsing libraries – I don’t think there is an equivalent of Erlang’s OTP or Python’s statistics libraries that makes Haskell an obvious pick for those domains.

To elaborate on the language’s general benefits, the simplest Haskell features make modeling data accurately idiomatic and effortless:

  • Newtypes make it a single line to create types like RoutingNumber or AccountNumber that function as Text at runtime, but can’t be interchanged at compile time. You can additionally customize the capabilities of these types, for example not supporting a ToJSON operation for a HashedPassword, while inheriting Text’s default ToJSON implementation for a RoutingNumber.

  • Algebraic data types allow modeling different shapes of data, for example data TransferOrigin = ManualTransfer UserId | AutomatedTransfer.

  • Data types like Maybe and Either (and a corresponding lack of null) help for modeling optional data and checking for error cases.

I would especially emphasize that all this is totally normal and boring in Haskell. Many of these things are possible in other languages, but often feel kludgy, unidiomatic, and aren’t the default in libraries.

In addition to those core features, we also benefit from:

  • Typesafe SQL joins, which ensure two tables are JOINed by a UserId on each table.

  • Compile-time metaprogramming to eliminate brittle boilerplate.

  • A green-thread concurrency model that requires no extra thinking.

When people think about building startups, Haskell is usually not the first language they have in mind. Could you talk a little about your experience building a startup with the language?

Our experience has been great. Mercury was especially well suited to Haskell, as a startup that had a clearly defined target (build the core features of a bank) and valued correctness. Hiring is likely the strongest benefit, as I describe below.

Did you run into any downsides of Haskell while developing the project? If so, could you describe those?

  • IDE support was weak when Mercury started, and currently doesn’t handle our codebase’s scale. We are contracting to have this solved.

  • For better or worse, Haskell has many ways of doing things. Error handling especially lacks established patterns.

  • We’re one of the larger companies using Haskell and ran into issues with the MacOS linker and compiler performance that we contracted out to fix.

  • Docs are often great at a function level, but poor at an integration level. Getting started with a library especially can be tricky.

Could you list some Haskell libraries that your team found very useful while developing Mercury and that you would like to feature?

The Haskell package ecosystem is huge, and we directly depend on 194 open source packages. I try to support these OSS authors when I see they’re open to sponsorships.

I want to especially credit Michael Snoyman for creating Yesod and Persistent, and also for his pragmatic attitude towards software development. It’s fairly likely Mercury would have never used Haskell if not for his work.

What kind of an effect system do you use: RIO, mtl, fused-effects, Polysemy, or something else?

We largely use the “ReaderT over IO” pattern, so similar to RIO. mtl is in there, but we don’t have a complicated monad transformer stack.

I’m not very familiar with the more advanced effects systems, but I’d generally discourage people from building a company with them. Overall I think the benefits are limited, the complexity is higher, and there are a lot of unknown unknowns you’d deal with using them.

As far as I understand, you contract out some GHC work to Haskell software development companies. Could you talk about the benefits of this to Mercury? Is it because of potential gains or flaws of the compiler that you’ve encountered?

We’ve contracted out to several companies, currently Tweag, Well-Typed and Foxhound Systems. We primarily contract for improvements to GHC (the compiler) and HLS (the IDE tool), especially on performance.

Contracting is helpful because it lets us focus on building our product, and enlist GHC experts for work on compiler performance. This is especially helpful because those people often have a pretty good idea of where to begin, or have already in-progress work that a new developer wouldn’t know about.

That said, we’re now large enough to start to build our own in-house teams for this – we just hired our first GHC developer.

Has your team run into any hiring difficulties when hiring Haskell developers?

No, in fact our VP of People has previously said that backend Haskell engineer is the “easiest role to hire for” in the entire company, and in my opinion hiring is Haskell’s “killer app”. I think a few things contribute to this:

  1. There is a greater demand for Haskell jobs than the market provides.

  2. Too many companies recruit for common languages. Our recruiters have to send 10 times as much outreach to get a response for TypeScript jobs.

  3. Many excellent developers are interested in Haskell.

  4. Interest in Haskell acts as a decent proxy for baseline developer quality.

(See also Paul Graham’s “The Python Paradox”)

Hiring is absolutely critical for a startup. If you don’t have an edge in hiring (self-driving cars, curing cancer, a reality distortion field, etc.), you may struggle to attract and retain talented people. Early on, Haskell was a big draw for Mercury before we had any reputation. Having great developers early establishes a strong foundation, and draws in people who want to work with other talented engineers.

We now have over 100 developers, and we continue to hire people with extensive Haskell experience, as well as people with no Haskell experience at all. If you’d like to be one of those people, check out https://mercury.com/jobs.

Do you have any in-house training programs to teach or upskill Haskell developers?

Yes, we have a team internally that teaches people Haskell, run by Matt Parsons. Having such a program is pretty important I think, since Haskell can be fairly foreign if you’re coming from most imperative languages. Our training program usually takes 6 to 8 weeks to complete, mixing book learning with 1:1 instruction.

My one word answer is “mixed”. In many more than a few words:

Nix is a collection of software with a unique take on package management. Instead of a single global package like python or python3, multiple versions of dependencies can co-exist. Each is addressed in the filesystem by a hash of its contents. This requires deterministic building of packages, which are configured with the Nix language. There’s also a Linux distro that uses Nix for configuration (NixOS), a development tool to create a shell using packages specified by Nix (nix-shell), and more. We use Nix for a quite a few things:

We run NixOS on our servers, use it for deploys (but not via NixOps), and partially for CI (we’re migrating away from Hydra though). We use nix-shell for our frontend, backend, and Android development environments.

Upsides:

  • Easy-ish development environment setup. You still have to set up Nix, but that’s a constant cost vs. setup growing more complex all the time.

  • Consistent development environments. You don’t have to deal with half the team being on an old GHC version, or a weird error because Debian comes with an old C library. A corollary: it’s easier to support a wide variety of Linux distributions.

  • Caching dependencies.

  • Instant rollbacks for the entire NixOS OS.

Downsides:

  • The development workflow for modifying 3rd party Haskell packages is arduous. It often involves large recompiles, and makes iterating on an open-source package a much worse experience than Stack, for example. (There are supposedly workarounds for this, though we haven’t started using them.)

  • Nix’s documentation is pretty bad. Finding options for a package often involves digging into source code.

  • The Nix language itself feels very unergonomic. Weird syntax, lack of types, poor error messages.

  • You will need to hire people who are experienced with Nix.

  • Stack doesn’t support specifying packages via Nix, so you lose Stack features like --file-watch.

In our internal surveys, some people say it’s the best thing about working at Mercury, and some say it’s the worst thing. Your satisfaction with Nix may depend heavily on your past experiences and current role – the memory of a painful Ruby system upgrade that was carefully tested in staging – but nonetheless hosed production – is now rolled back instantly. But conversely, the ease with which you patched a dependency using stack could now involve recompiling all your custom dependencies, turning a 30 second iteration cycle into a 30 minute one as amazonka builds.

On balance, I’m satisfied with Nix but give it a weak endorsement.

What tips would you give to other teams that want to launch a startup with Haskell?

I would get comfortable with Haskell before you start a company with it – at least enough to write a small webapp with it. I would mostly write imperative looking code, and avoid writing code that seems “fancy”. Don’t feel embarrassed by do notation, or feel the need to use operators or point-free style.

I would be more hesitant about using Haskell if you are a solo developer (less benefit from types I’d think, but still more than you’d expect). I’d similarly be more hesitant if speed of development is more important than correctness for your business.

When I started Mercury, I wanted to limit my “complexity budget” by choosing otherwise solid technologies. Haskell turned out to be less costly here than I thought, but I would still advise most startups to use a single Postgres database (including for queued jobs), a monolithic architecture, and deploy on AWS. Less universally, I’d probably recommend a React+TypeScript SPA and native iOS and Android apps, using JSON as an interchange format.

I would caution against planning to start with something else and switching to Haskell. It’s never the right time for a rewrite, you’ll have lost some of Haskell’s hiring benefits, and your team may not like the switch. It’s definitely possible, but “prototype in a dynamic language, rewrite in a static one at scale” is risky advice.


Hope you enjoyed our interview with Max!

To read more interviews with companies that use Haskell to solve real-world problems, head to our interview section. Also, be sure to follow us on Twitter or subscribe to our mailing list (via form below) to receive new Serokell articles via email.

Haskell in Production: Mercury
Banner that links to Serokell Shop. You can buy awesome FP T-shirts there!
More from Serokell
across the kmettverse with edward kmettacross the kmettverse with edward kmett
functional programming and web3 interview thumbnailfunctional programming and web3 interview thumbnail
telegram blockchain competition serokelltelegram blockchain competition serokell