Haskell in Production: CollegeVine

In this article, we interview Fyodor Soikin from CollegeVine – an online platform that connects high school students with college admissions guidance and mentorship.

We talk about CollegeVine’s use of Haskell and PureScript in production, as well as how they have managed to integrate these languages with a Rails backend.

Interview with Fyodor Soikin

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

CollegeVine is an online platform that offers college admissions guidance and mentorship to high school students, aiming to help students navigate the often complex and stressful college admissions process with confidence and support. The platform offers a range of services, including college essay review, application strategy development, a college admissions blog, a school research tool, and a free admissions calculator. But the real value is the networking aspect: with a professional CollegeVine profile highlighting their achievements, many students engage directly with admissions officers and eventually receive generous financial aid and direct admissions offers right on our website.

Technically, CollegeVine is a pretty traditional web app (not an SPA), backed by a relational database. It’s using Rails as backend, a couple supporting services written in Haskell, and the browser front-end is mostly (but not 100%) written in PureScript. We have a non-trivial data science department, which powers many of our user-facing features, but I don’t think I’m qualified to talk about it, and not sure what I can say and what I can’t.

My official title in the company is “Principal Software Engineer”, and it reflects what I do pretty well. Like everybody else, I write code, work with product owners and designers, but on top of that I do things like mentorship, supporting lower-level libraries and tools, weighing in on design decisions, technology choices, promoting practices, and so on.

Where in your stack do you use Haskell?

We don’t really use Haskell all that much at the moment. In a previous incarnation of CollegeVine, our whole backend was written 100% in Haskell, but after a sharp pivot in June of 2019 we adopted Rails as general-purpose backend, but a couple supporting services are still written in Haskell. These services are parts of the system that particularly benefit from the reliability and strictness imposed by Haskell.

The services in question are very stable, not only in the sense of not crashing or providing erroneous results, but also in the sense that the modifications they require are few and far between, just because of the nature of the tasks they perform. As a result, we very rarely do any work on them.

How did you decide to choose Haskell for the project?

The original decision to use Haskell happened before I even joined the company. I’m told that our CTO and co-founder was a fan of Haskell and thought it would be a good way to attract outsized engineering talent for a small, unknown startup. And I must say, that’s exactly how it happened.

In the current landscape, the decision to use Haskell for those couple services was two-fold: on one hand, they support some of the core, most important features of CollegeVine, and therefore making them stable has far-reaching ripples over the whole system; on the other hand, a sizeable portion of their code already existed, being inherited from the pre-2019 codebase.

Are there any specific qualities of Haskell that make it well suited for the particular use case?

Like I said above: reliability, strictness, painless refactoring. Somewhat less important advantages specifically over Rails – performance and ease of deployment.

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

One significant pain point of Haskell is database access. In the past, we used Esqueleto (as well as naked Persistent), and in the current code we use Beam. I found both libraries quite hard to work with because they suffer from over-generalization. Happy path is, of course, painless, but as soon as I make a mistake in a query, the resulting error message is not helpful at all, often referring to obscure types from the library internals, and often appearing in a place different from the actual error location. To be fair, this is kind of par for the course for Haskell, but I found that the DB libraries tend to suffer from this more than usual.

Another pain point is decent support for records. I always knew this was one of Haskell’s ugly warts, but working with PureScript reinforced this idea, revealing just how much easier and more pleasant programming can be with decent ad-hoc record support.

Alongside Haskell, you also use PureScript. What is your experience with using both of these languages together?

We don’t really use Haskell and PureScript together. Rails backend calls Haskell services on one side and renders PureScirpt UI on the other, but services do not interact with UI directly.

As far as using Haskell and PureScript simultaneously and comparing with each other, my experience has been mostly in favour of PureScript. Even though PureScript lacks many advanced features of Haskell (personally, the one I miss the most is type applications), it feels much more self-consistent and “solid”. I found myself enjoying programming in PureScript quite a bit more than in Haskell. Not to mention the glorious extendable ad-hoc records.

How does using typed functional programming languages like PureScript and Haskell go with using Ruby on Rails? Is your team split into a functional and non-functional team, or do people more or less work on anything?

We do not have the silly specialization to frontend/backend or to business logic/database or whatever it might be. Our team is small, but strong, and every engineer is perfectly capable of working on everything, and they do. When working on a task, every engineer is expected to work with PureScript, JavaScript (as needed), Ruby, Haskell, SQL, and any external APIs that might be required. One unfortunate exception is data science, which is a separate team of people, and they mainly use R for their work, though even they venture into Ruby land to make small alterations from time to time.

As for functional vs. non-functional – personally I think that’s a false dichotomy in this case. Ruby is definitely object-oriented to its core, but this does not prevent it from being functional. We do try to be as functional in our Ruby code as the Rails libraries would allow us to be. And this definitely helps a lot with maintainability of the codebase.

A separate dichotomy (true this time) is static vs. dynamic typing. I haven’t worked with large dynamically typed codebases before, and now that I have, I can definitively say that static typing is far superior. As I work on features, switching from Ruby to PureScript is always a breath of fresh air, especially if I was working with Rails libraries. Outside of Rails, it turns out to be possible to structure the code defensively, almost kind of a Lispy feeling.

That said, I think coupling Ruby with PureScript has actually helped us with stability. Our PureScript code is organized in a way that more or less forces it to verify the shape of data structures it receives from Ruby, and combined with test coverage, this allows us to discover Ruby mistakes early (or at all). Even if not caught by tests, such mistakes are discovered via the error stream from production.

To integrate PureScript into the Rails app, we have implemented a bridge that snaps into Rails asset pipeline (aka “Sprockets”) and exposes an interface very similar to what Rails uses for including JS or CSS files, but behind the scenes takes care of compilation, bundling, instantiating, and passing parameters, as well as server-side render and caching – all transparently to the developer. All the developer needs to do is write a PureScript module that exports a value of a specific type, and then, on the Rails side, provide the name of that module and any parameters.

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

For Haskell – no, but we do for PureScript.

A newly hired engineer is given a Rails project that is almost empty, but structured the same way as our main production app, including the PureScript bridge and other in-house libraries. They are then given a series of features of increasing complexity to implement in that project, and a senior member of the team is assigned to be their “onboarding buddy” as we call it, which basically means supervising, answering questions, and so on.

This project is intended to train the new hire in multiple things at once: Rails as such, PureScript, our way of structuring code, our way of handling changes (via GH pull requests), and so on.

What tips would you give for teams that want to start including typed FP languages in their codebase?

Based on my own experience, I can’t offer much here. I have only used FP in production code at two positions: this one and the previous one. At the previous one, I was the source of FP advocacy, and it had a minimal effect by the time I left, pretty much an uphill battle the whole time. And at CollegeVine, FP was already in place when I arrived.

Generally speaking, however, I can offer some observations and speculations:

  1. Don’t do it just for fun. Find an actual application that would benefit from FP, even if marginally.
  2. Don’t do it if a significant portion of the team is against it. This will just create a big disruption and result in extra distrust for FP in the future.
  3. Don’t rewrite the world, or even part of it. Start with a small new feature or service written in an FP language.
  4. Since the codebase will have to remain hybrid pretty much forever, really invest in the interface between FP language and whatever the rest of the codebase uses. A lot of friction will kill the whole thing.
  5. Use a statically typed FP language, no matter what Rich Hickey tells you. :-)
  6. Do not split the team into FP and non-FP. They will compete for resources.

We hope you enjoyed your interview with Fyodor!

To read more interviews about the use of Haskell in production, head to our interviews section. And don’t forget to follow us on Twitter to stay updated about new articles!

Haskell courses by Serokell
More from Serokell
Type families is the most powerful type-level programming features in Haskell.Type families is the most powerful type-level programming features in Haskell.
Parser combinators in HaskellParser combinators in Haskell
fintech functional programmingfintech functional programming