Haskell in Production

Services at Klarna written in Haskell

Why Haskell?

We loved functional, but 💔 Scala

So, Haskell.

What Haskell is, and what it isn’t.

Common misconceptions

Common misconceptions

  • Reading Haskell code is hard

Common misconceptions

  • You need to understand the m-word

Common misconceptions

  • Haskell is slow

Common misconceptions

  • The libraries you want don’t exist

Common misconceptions

  • The tooling you want doesn’t exist

Common misconceptions

  • You need a PhD to get anything done

Common misconceptions

  • Writing features is slow

None of these are inherently true.

Some of them can be.

Honestly it’s the same for Erlang.

Leaving the Ivory Tower

From the Ivory Tower to the Happy Island

  • Learning resources
  • Inclusive community
  • User-friendly libraries
  • Golden path

Golden Path

  • Language
  • Libraries
  • Tools
  • Patterns

Haskell at Klarna

We’re not officially supporting it.

Yet.

Our Haskell stack

Libraries

  • servant
  • postgresql-simple (+ opalaye)
  • hedgehog

Servant

A set of packages to declare web APIs on the type-level

type GetPet
    = "pets" :> Capture "petId" PetId :> Get '[JSON] Pet
type GetPet
    = "pets" :> Capture "petId" PetId :> Get '[JSON] Pet

type PostPet
    = "pets" :> Capture "petId" PetId :> Post '[JSON] NoContent
   :> ReqBody '[JSON] Pet
type GetPet
    = "pets" :> Capture "petId" PetId :> Get '[JSON] Pet

type PostPet
    = "pets" :> Capture "petId" PetId :> Post '[JSON] NoContent
   :> ReqBody '[JSON] Pet

type PetAPI
    = GetPet
 :<|> PostPet
 :<|> DeletePet

But that’s not the best part of servant.

Types can actually give you much more.

  • Statically defined OpenAPI
  • Statically defined documentation
  • Clients for free in both Haskell and JavaScript!
  • Mock an entire server from the type!

OpenAPI

type PetApi = ...

petApiSwagger = encode $ toSwagger $ Proxy @UserApi

OpenAPI

type PetApi = ...

petApiSwagger = encode $ toSwagger $ Proxy @UserApi
-- error:
-- • No instance for (ToSchema Pet)
--     arising from a use of ‘toSwagger’
-- • In the second argument of ‘($)’, namely
--     ‘toSwagger $ Proxy @GetPet’
--   In the expression: encode $ toSwagger $ Proxy @GetPet
--   In an equation for ‘swaggy’:
--       swaggy = encode $ toSwagger $ Proxy @GetPet

OpenAPI

data Pet = Pet
  { name :: Text
  , age  :: Int
  }
  deriving (Generic)

OpenAPI

data Pet = Pet
  { name :: Text
  , age  :: Int
  }
  deriving (Generic)

instance ToSchema Pet

OpenAPI

type PetApi = ...

petApiSwagger = encode $ toSwagger $ Proxy @UserApi
-- { "swagger": "2.0", ... }

Clients - Haskell

type PetApi = ...

getPetClient :: PetId -> ClientM Pet
postPetClient :: PetId -> Pet -> ClientM NoContent
getPetClient :<|> postPetClient = client (Proxy @PetApi)

getPet :: ClientEnv -> PetId -> IO (Either ClientError Pet)
getPet env petId = runClientM (getPetClient petId) env

Clients - JS

import Servant.JS (jsForApi)
import Servant.JS.Axios (axios)

type PetApi = ...

jsCode :: String
jsCode = jsForApi (Proxy @PetApi) axios

Documentation

apiDocs :: String
apiDocs = _ $ docs $ Proxy @PetApi

Documentation - markdown!

apiDocs :: String
apiDocs = markdown $ docs $ Proxy @PetApi

Documentation - pandoc!

apiDocs :: String
apiDocs = _ $ pandoc $ docs $ Proxy @PetApi

AsciiDoc, CommonMark, ConTeXt, Custom, Docbook, Docx, DokuWiki, EPUB, FB2, HTML, Haddock, ICML, Ipynb, JATS, Jira, LaTeX, Man, Markdown, Math, MediaWiki, Ms, Muse, Native, ODT, OOXML, OPML, OpenDocument, Org, Powerpoint, RST, RTF, Shared, TEI, Texinfo, Textile, XWiki, ZimWiki

Mocked server

petGen :: Gen Pet
    = Pet
  <$> choice [ "Diddy", "Tupac" ]
  <*> Gen.int (Range 0 10)

Mocked server

import Servant.Mock

petApi :: Proxy PetApi
petApi = Proxy

main :: IO ()
main = run 8080 $ serve petApi (mock petApi Proxy)

Property based integration tests

import Hedgehog.Servant

genApi :: Gen (BaseUrl -> Request)
genApi = genRequest (Proxy @PetApi) (genPet :*: GNil)

<100 LOC

So far, we haven’t written a single line of implementation code.

Other integrations

  • SwaggerUI
  • Other language clients:
    • Purescript
    • Elm
    • Kotlin
    • Python
    • C#
    • Ruby
  • Checked exceptions
type GetPet
    = "pets" :> Capture "petId" PetId :> Get '[JSON] Pet
getPet :: PetId -> Maybe Pet
getPet :: PetId -> Maybe Pet
getPet :: PetId -> Maybe Pet

-- Which we'll translate to:

getPet :: PetId -> Handler Pet

So, what’s a Handler?

Yes, it’s an m-word.

Handler

pets :: Map PetId Pet
pets = Map.fromList
  [ ("cat", undefined)
  , ("dog", undefined)
  ]

getPet :: PetId -> Handler Pet
getPet petId =
  case Map.lookup petId of
    Just pet -> return pet
    Nothing  -> throw404

Putting it all together

apiHandler :: Server PetApi
apiHandler
     = getPet
  :<|> postPet

main :: IO ()
main
  = run 8080
  $ serve (Proxy @PetApi) apiHandlers

In summary

  • Getting stuff done is quick, and sans footguns

  • We’re really happy with what Haskell offers us

Klarna is hiring

Build the next generation of banking using FP

  • Internal TheKonferen.se
  • External conference of choice
  • Smoooth Lambdas - Category Theory study group

Thank you!