Day 5 of 5
⏱ ~60 minutes
Haskell in 5 Days — Day 5

Real-World Haskell: Web & Concurrency

Haskell excels at high-performance, correct server programs. Today covers the Scotty web framework, concurrent programming with STM, and packaging with Cabal and Stack.

Scotty Web Framework

Scotty is a lightweight web framework inspired by Ruby's Sinatra. Routes are defined with get, post, put, delete, taking a pattern and an action in the ActionM monad. param extracts path/query parameters; json sends JSON responses; html sends HTML. Scotty runs on Warp, a high-performance Haskell HTTP server — benchmarks show Warp handling hundreds of thousands of requests per second.

Software Transactional Memory (STM)

STM provides composable, lock-free concurrency. TVar is a shared mutable variable. atomically executes a transaction: all reads and writes succeed together or the transaction retries from scratch. No deadlocks possible — STM detects conflicts automatically. This is far simpler than mutex-based concurrency: you cannot forget to acquire a lock because the type system forces all shared state through atomically.

Cabal and Stack

Cabal is Haskell's build tool and package manager. A .cabal file lists dependencies, modules, and build options. Stack wraps Cabal with reproducible builds via LTS (Long Term Support) Stackage snapshots — a known set of mutually compatible package versions. Create a project: 'stack new myproject', add dependencies to package.yaml, run 'stack build' and 'stack test'. Hackage hosts 16,000+ Haskell packages.

haskell
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Control.Concurrent.STM
import Data.Text.Lazy (Text)
import qualified Data.Map.Strict as Map

type KVStore = TVar (Map.Map Text Text)

main :: IO ()
main = do
  store <- newTVarIO Map.empty
  scotty 3000 $ do
    -- GET /kv/:key
    get "/kv/:key" $ do
      k <- param "key"
      m <- liftIO $ readTVarIO store
      case Map.lookup k m of
        Nothing -> text "Not found"
        Just v  -> text v
    -- PUT /kv/:key with body as value
    put "/kv/:key" $ do
      k <- param "key"
      v <- body
      liftIO $ atomically $
        modifyTVar store (Map.insert k (lazyTextFromStrict v))
      text "OK"

  where lazyTextFromStrict = id  -- simplified
💡
Use Data.Text instead of String for all text processing. String is [Char] — a linked list of characters — which is 5-10x slower and 3x more memory than Text's packed UTF-16 representation.
📝 Day 5 Exercise
Build a REST API with Scotty
  1. Create a Stack project: stack new haskell-api && cd haskell-api
  2. Add scotty, aeson, and stm to package.yaml dependencies
  3. Run stack build to download and compile dependencies
  4. Implement the key-value store from the code example
  5. Test with curl: curl -X PUT -d 'hello' http://localhost:3000/kv/greeting && curl http://localhost:3000/kv/greeting

Day 5 Summary

  • Scotty provides concise route definitions on top of the Warp HTTP server
  • STM (Software Transactional Memory) provides lock-free, composable concurrency
  • TVar wraps shared state; atomically ensures consistent reads and writes
  • Stack provides reproducible Haskell builds via Stackage LTS snapshots
  • Use Data.Text instead of String for 5-10x better text processing performance
Challenge

Build a complete Haskell REST API for a todo list: POST /todos (create), GET /todos (list all), GET /todos/:id (get one), DELETE /todos/:id (delete). Store data in a TVar Map. Return JSON using Aeson. Write 5 curl-based integration tests.

Finished this lesson?