Haskell in Production in a Startup in 2017? Yup

Clément Delafargue @clementd

Yup

Yup
Frédéric Menou
@ptit_fred

Why Haskell?

A lot of parsing (parser combinators <3).

A leap of faith.

Haskell as a teaching tool for FP to write better code.

Command-Line Tools

October 2016

Migration tool for Emails (OVH -> Gmail). A CLI. Mostly IO + Maybe.

Refactoring from existing codebase written for YouTube APIS: ADTS + Aeson.

Never touch it again, still used right now in a larger CLI calling providers APIs left & right.

APIs + Google Sheets = <3

March 2017

First actual HTTP service in Haskell, consuming GMaps API and OSRM APIs.

[Screenshot of distance computation tool integrated in Google Sheet]

Wrote once, forgot about it, and it's still working (in CleverCloud <3).

Added Redis cache by wrapping an IO method.

CLI converted to HTTP service (changing the main mostly).

HTTP with Servant

June 2017

With more experience regarding Haskell code and the benefit you can got, the idea to migrate actual production code to a fresh version in pure Haskell was finally taken, following the arrival of a new developer.

Consuming Databases

import Database.PostgreSQL.Simple
    (Query, Connection, Serial, query)
import Data.Text (Text)

countryByCode :: Query
countryByCode =
  "SELECT country_id FROM countries" <>
  "WHERE iso3166_code = ?"

getCountryByCode :: Connection
                 -> Text
                 -> IO Serial
getCountryByCode conn t =
  query conn countryByCode (Only t)

Serving data

Supporting back-office functions

Business rules should be readable

With more experience regarding Haskell code and the benefit you can got, the idea to migrate actual production code to a fresh version in pure Haskell was finally taken, following the arrival of a new developer.

The first service was domain specific and important part of the code should be expressive enough for some expert to review it. In our case our expert is our Product Manager who is not developer at all.

You can avoid implementing EDSL just by writing proper Haskell code. The challenge here is to split the logic from the plumbing such IO, HTTP, Filesystem, error handling.

More services follow since then and we have gained experience about implementing a whole HTTP service in Haskell from the source to the production. Let us present that to you.

On a daily basis

It ain't easy at first. Exotic compile error messages can be very frightening at the beginning.

Make it work and you can forget about it for months. You'll get back to it later.

The key is to start with some ugly code. Don't hesitate to mix IO with logic, to badly name things, to use type aliases. Your job is to draw the baseline of your code, you'll make it solid and pretty in the next step.

Write dirty code first

Your job is to write code that's working. Then you refactor, affine the concepts.

make it work

then make it clean

Code-golf is bad

Your job is to write code that's working. Then you refactor, affine the concepts.

It's pretty common for Haskell developers to fill overwelm by all the advanced topics discussed in Reddit reddit-haskell or the wiki. But you don't need those to write useful code. Lucas Di Cioccio call it the Haskell Pyramid haskell-pyramid.

Types, properties, module separation (and git) are the best tool to write production-ready software.

[Examples of Quickcheck properties to express behavior]

[Rant about Uncle Bob opposing types and tests?]

Usable code is simple

credits: Lucas Di Cioccio

https://cokmett.github.io/cokmett

Unit Tests: HUnit & RSpec

Property Testing: QuickCheck

hackage for the libs

When building a Haskell project, you would most likely use Cabal or stack (which wraps Cabal with a notion of distribution of deps).

stackage for the LTS

With Cabal, packages are available in hackage. Stack handle various distributions of packages in its own repository stackage. You can forget about Stackage and consider Hackage the reference. Documentation is also available in those web sites, generated from the source code with haddock (same way javadoc and other do).

hoogle: lookup functions by its signature

One very cool feature of the Haskell ecosystem is hoogle, a search engine for functions and types, indexing most packages from hackage, and letting you search for functions by passing a signature. Hoogle is an API so you can easily use in your terminal or your IDE.

ADT

case class Peer(
  cidrs: List[String],
  pubkey: String,
  endpoint: String,
  keepalive: Int)
sealed trait WgCommand
case class InitConfig(…)
  extends WgCommand
case class SetPeer(peer: Peer)
  extends WgCommand
case class RemovePeer(pubkey: String)
  extends WgCommand
data Peer = Peer
  { cidrs     :: [Text]
  , pubkey    :: Text
  , endpoint  :: Text
  , keepalive :: Int
  }



{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RecordWildCards       #-}
data WgCommand =
    InitConfig Text Text Text Int
  | SetPeer Peer
  | RemovePeer Text
def handleCommand(c: WgCommand) = c match {
  case InitConfig(_)      => ???
  case SetPeer(peer)      => ???
  case RemovePeer(pubkey) => ???
}
handleCommand :: Command -> IO ()
handleCommand InitConfig{..} = error "ToDo"
handleCommand SetPeer{..}    = error "ToDo"
handleCommand RemovePeer{..} = error "ToDo"

Typeclasses

class Monoid a where
    mempty  :: a
    mappend :: a -> a -> a
    mconcat :: [a] -> a
    mconcat = foldr mappend mempty
instance Monoid Ordering where
    mempty         = EQ
    LT `mappend` _ = LT
    EQ `mappend` y = y
    GT `mappend` _ = GT

Convergence

Orphan Instances

Hole-Driven Development

trait Hole
case object Hole_

def compose[A,B,C](
    f: A => B,
    g: B => C
): (A => C) = Hole_
compose :: (a -> b) -> (b -> c) -> (a -> c)
compose = _

Tooling

Devloop

'use strict'

let compile = run({
  sh: 'stack build',
  watch: '*.hs'
})

let server = runServer({
  httpPort,
  env: { "PORT": httpPort },
  sh: `./.stack-work/*/*/*/*/bin/cestpasnous`
}).dependsOn(compile)

proxy(server, 8080).dependsOn(compile)

Haskell in Production

stressant par rapport à java, mais finalement pas si méchant

Build: <3 stack LTS && lockfile

Même build pour tout le monde, build automatisable et robuste

Single binary

Easy for CLI tooling, stuff like that

Dependencies caching

Essential for continuous deployment.

Monitoring && metrics: <3 EKG

EKG way easier to use than JMX (IMO). Easy to integrate in statsd, prometheus, pure HTTP Dashboard available