LabHub

Blog

Modern Haskell 2026 — GHC 9.10 / 9.12 / GHCup / Cabal 3.14 / Stack / IHP / Servant / Effectful / Pandoc / Cardano Deep Dive

한국어English日本語

Prologue — People still use Haskell in 2026

Every year someone asks: "Haskell? People still use that?"

Yes. And quite seriously.

In the 2010s the real enemy was not "Haskell is hard". It was that the build tooling was split between Cabal and Stack, and both were subtly broken in different ways. What does that look like in 2026?

Answer: GHCup manages both well at the same time. So the "I got a Cabal file but they sent it from Stack and it does not build" experience is mostly gone. And GHC itself got another step faster in 9.10 and 9.12 thanks to modular compilation and better multi-core scheduling.

This article walks the 2026 Haskell stack in one breath. Compiler, build tools, full-stack web frameworks, effect libraries, ORMs, parsers, JSON, tests, formal verification, ML, document conversion, and the people behind it all.


1. Modern Haskell 2026 — The GHC 9.x era

One picture of the landscape.

                Modern Haskell 2026 stack

[install]        GHCup (recommended) -> ghc / cabal / stack / hls
[compiler]       GHC 9.10 (2024-05) / 9.12 (2024-12)
[build]          Cabal 3.14   |   Stack (LTS 22.x, Stackage)
[packages]       Hackage (all)  |  Stackage (curated LTS)

[full-stack web] IHP (Rails-style, digitally induced)
[web API]        Servant (typed HTTP DSL)
[other web]      Yesod / Snap / Scotty / Spock

[effects]        Effectful (mtl successor, 2026 default)
                 Polysemy / fused-effects / Bluefin

[ORM]            Persistent / Beam / Esqueleto

[parser]         Megaparsec / Attoparsec / Earley
[JSON]           Aeson
[strings/bytes]  text / bytestring / vector

[testing]        hedgehog (property) / tasty / hspec / QuickCheck
[verification]   Liquid Haskell (refinement types)

[ML]             Hasktorch (libtorch bindings)
[docs]           Pandoc (John MacFarlane)

[production]     GitHub / Cardano / Mercury / IOG /
                 Hasura / Anduril / Channable / Tweag

That is the 2026 Haskell map. Let us walk each box.


2. GHC 9.10 (2024-05) / 9.12 (2024-12)

GHC (Glasgow Haskell Compiler) is the Haskell reference implementation. For all practical purposes, the only industrial implementation.

GHC 9.10 (May 2024)

GHC 9.12 (December 2024)

-- GHC 9.12 MultilineStrings + OrPatterns
{-# LANGUAGE MultilineStrings #-}
{-# LANGUAGE OrPatterns #-}

greeting :: String
greeting = """
  Hello,
  modern Haskell 2026.
  """

classify :: Int -> String
classify n = case n of
  (0 | 1 | 2) -> "small"
  (3 | 4 | 5) -> "medium"
  _           -> "large"

Which version to pick


Around 2020 the typical Haskell install story was "install Stack and that is it". Cabal users installed separately and the two did not quite get along.

In 2026, GHCup is the officially recommended installer. Run the one-line script from haskell.org/ghcup and it pulls GHC, Cabal, Stack, and HLS (Haskell Language Server).

# Official one-line install
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh

# Or Windows PowerShell
# Set-ExecutionPolicy Bypass -Scope Process -Force
# Invoke-WebRequest ... ghcup-windows-... .ps1 | Invoke-Expression

After install, run ghcup tui for a text-mode UI where you can pick which GHC / Cabal / Stack / HLS versions to install or remove.

ghcup install ghc 9.10.1
ghcup install ghc 9.12.1
ghcup set ghc 9.10.1            # set 9.10 as the system default
ghcup install cabal 3.14.1.0
ghcup install hls 2.9.0.0       # Haskell Language Server (LSP)
ghcup install stack 3.1.1
ghcup list                       # installed and available versions

The key value of GHCup is multiple GHC versions side by side on the same host. It picks the right one automatically from cabal.project or stack.yaml.

Note: macOS and Linux use that script as standard. Windows GHCup installs natively rather than via Chocolatey. WSL users can use the same script.


4. Cabal 3.14 / Stack — the build tools

If GHC is the compiler, Cabal and Stack are the package and project managers on top of it.

Cabal 3.14

Cabal is actually two things.

  1. The Cabal library — parses *.cabal files and builds dependency graphs.
  2. The cabal-install CLI — uses that library; usually what we call "cabal".

Cabal 3.14 (late 2024) brings:

-- my-app.cabal (Cabal 3.14)
cabal-version:      3.14
name:               my-app
version:            0.1.0.0
synopsis:           A sample modern Haskell app
license:            BSD-3-Clause
build-type:         Simple

common warnings
  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns

library
  import:           warnings
  exposed-modules:  MyApp
                    MyApp.Server
  build-depends:    base ^>= 4.20
                  , servant ^>= 0.20
                  , servant-server ^>= 0.20
                  , warp ^>= 3.4
                  , aeson ^>= 2.2
                  , text ^>= 2.1
  hs-source-dirs:   src
  default-language: GHC2024

executable my-app
  import:           warnings
  main-is:          Main.hs
  build-depends:    base, my-app
  hs-source-dirs:   app
  default-language: GHC2024

test-suite my-app-test
  import:           warnings
  type:             exitcode-stdio-1.0
  main-is:          Spec.hs
  build-depends:    base, my-app, hspec ^>= 2.11, tasty ^>= 1.5
  hs-source-dirs:   test
  default-language: GHC2024

Stack

Stack started at FP Complete and is now maintained by the community under Haskell Foundation. The key difference from Cabal is that Stack defaults to a curated Stackage LTS snapshot.

# stack.yaml
resolver: lts-22.30  # Stackage LTS — packages tested together
packages:
  - .
extra-deps: []

Stackage LTS ships weekly minor releases and a new major every six months. The promise is "these packages build together and their tests pass together".

Which tool

In 2026, the Cabal camp moved faster, but Stack's "low-noise" UX is still attractive.


5. Hackage / Stackage — the package ecosystem

Hackage

hackage.haskell.org — the central Haskell package repository. Around 18,000 packages in 2026. Anyone can publish; versions follow PVP (Package Versioning Policy), similar to SemVer.

cabal update                     # refresh Hackage index
cabal install --installdir=./bin pandoc
cabal info aeson                 # package metadata

Stackage

stackage.org — a curation layer on top of Hackage, split into LTS and Nightly.

Why Stackage matters: "package X conflicts with Y" is filtered out at the curation step. A library author can write resolver: lts-22.30 in stack.yaml and every package in that bundle builds together.


6. IHP (Integrated Haskell Platform) — the full-stack web

ihp.digitallyinduced.com — a full-stack web framework by Germany's digitally induced GmbH. The goal is to give Haskell a Rails / Phoenix / Laravel-class experience.

IHP philosophy

# New IHP project
nix-shell -p ihp-new --run "ihp-new my-blog"
cd my-blog
./start                          # http://localhost:8000

IHP model example

-- Application/Schema.sql (defined directly in SQL)
CREATE TABLE posts (
    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
    title TEXT NOT NULL,
    body TEXT NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);

-- Web/Controller/Posts.hs (lightly edited from the generator)
module Web.Controller.Posts where
import Web.Controller.Prelude
import Web.View.Posts.Index

instance Controller PostsController where
    action PostsAction = do
        posts <- query @Post |> fetch
        render IndexView { .. }
-- Web/View/Posts/Index.hs
module Web.View.Posts.Index where
import Web.View.Prelude

data IndexView = IndexView { posts :: [Post] }

instance View IndexView where
    html IndexView { .. } = [hsx|
        <h1>Posts</h1>
        <ul>
            {forEach posts renderPost}
        </ul>
    |]
        where
            renderPost post = [hsx|<li>{get #title post}</li>|]

Inside the hsx quasi-quoter IHP interprets curly-brace interpolation as Haskell values safely. That only works inside the IHP DSL, not in ordinary Haskell files.

When IHP fits


7. Servant — typed HTTP API

Where IHP is full-stack, Servant is an API-only library. One central idea: describe the API at the type level.

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}

module Api where

import Data.Aeson (FromJSON, ToJSON)
import GHC.Generics (Generic)
import Servant

data User = User
  { userId   :: Int
  , userName :: String
  } deriving (Generic)

instance ToJSON User
instance FromJSON User

-- The API as a type
type UserAPI =
       "users" :> Get '[JSON] [User]
  :<|> "users" :> Capture "id" Int :> Get '[JSON] User
  :<|> "users" :> ReqBody '[JSON] User :> Post '[JSON] User

-- Handlers for that type
userAPI :: Server UserAPI
userAPI = listUsers :<|> getUser :<|> createUser
  where
    listUsers     = pure [User 1 "alice", User 2 "bob"]
    getUser uid   = pure (User uid "alice")
    createUser u  = pure u

The magic of this code is that UserAPI — the type — is the source of truth.

Servant + warp server

import Network.Wai.Handler.Warp (run)
import Servant

main :: IO ()
main = do
  putStrLn "Listening on http://localhost:8081"
  run 8081 (serve (Proxy :: Proxy UserAPI) userAPI)

Who uses Servant

API changes that turn into compile errors are the core value.


8. Yesod / Snap / Scotty / Spock — other web frameworks

Yesod (Michael Snoyman)

yesodweb.com — by Michael Snoyman, the creator of Stack and Stackage. Older than IHP, famous for heavy type use including type-safe URLs and type-safe widgets.

-- Foundation.hs (Yesod routing snippet)
mkYesodData "App" [parseRoutes|
/                HomeR   GET
/posts           PostsR  GET POST
/posts/#PostId   PostR   GET DELETE
|]

Snap

snapframework.com — academically rooted framework. Known for Snaplets module composition. Less active development, still around in existing projects.

Scotty

hackage.haskell.org/package/scotty — a Sinatra-style microframework. Great for spinning up something short and lightweight.

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

main :: IO ()
main = scotty 3000 $ do
  get "/" $ text "hello scotty"
  get "/hello/:name" $ do
    n <- captureParam "name"
    text ("hello, " <> n)

Spock

A Scotty-class microframework that bundles session, CSRF, and DB pooling as built-in middleware.

Which one


9. Effect systems — Effectful / Polysemy / fused-effects / Bluefin

The strength and the entry barrier of Haskell are its effect systems. Side effects — IO, state, exceptions, logging, DB access — are tracked by types.

The classic was the mtl style: stack monad transformers (StateT, ReaderT, ExceptT). Powerful but error messages get long and the performance trade-off is real.

In 2026, Effectful is effectively the new default.

Effectful

hackage.haskell.org/package/effectful — simpler model than Polysemy / fused-effects, compiler-friendly performance, mtl-like ergonomics.

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE GADTs #-}

import Effectful
import Effectful.Reader.Static (Reader, ask, runReader)
import Effectful.Error.Static (Error, throwError, runError)

-- Explicit effects: Reader + Error
loadUser :: (Reader String :> es, Error String :> es) => Int -> Eff es String
loadUser uid = do
  prefix <- ask
  if uid > 0
    then pure (prefix ++ ": user-" ++ show uid)
    else throwError "invalid uid"

main :: IO ()
main = do
  result <- runEff
          . runError @String
          . runReader "PREFIX"
          $ loadUser 42
  print result   -- Right "PREFIX: user-42"

Polysemy

hackage.haskell.org/package/polysemy — academic free-monad + free-algebra approach. Strong reinterpretation power, heavier types.

fused-effects

hackage.haskell.org/package/fused-effects — carrier-based effects. The compiler fuses effect composition at compile time for performance.

Bluefin

hackage.haskell.org/package/bluefin — a new entrant in 2024-25. Focused on scoped effects and delimited continuations as first class. Uses new RTS facilities in GHC 9.10.

-- Bluefin-style (conceptual example)
import Bluefin.Eff
import Bluefin.State

bumpCounter :: e :> es => State Int e -> Eff es Int
bumpCounter ref = do
  modify ref (+ 1)
  get ref

Which to use


10. ORM — Persistent / Beam / Esqueleto

Persistent (the Yesod camp)

Michael Snoyman's persistent — write your model in a DSL, the compiler turns it into data types and migrations. Supports SQL and some NoSQL backends.

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

import Database.Persist.TH

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Post
    title Text
    body  Text
    deriving Show
User
    name  Text
    posts [PostId]
    deriving Show
|]

Beam

haskell-beam.github.io — generic-based, more type-safe SQL. Closer to SQL than Persistent, with Postgres / MySQL / SQLite backends.

Esqueleto

Sits on top of Persistent and exposes type-safe JOINs and subqueries as an EDSL.

import qualified Database.Esqueleto.Experimental as E

-- Post count per user
usersWithPostCount = E.select $ do
  (u E.:& p) <- E.from $ E.table @User
       `E.leftJoin` E.table @Post
       `E.on` (\(u E.:& p) -> E.just (u E.^. UserId) E.==. p E.?. PostUserId)
  E.groupBy (u E.^. UserId)
  pure (u, E.count (p E.?. PostId))

Which one


11. Megaparsec — parser combinators

hackage.haskell.org/package/megaparsec — the 2026 default parser combinator library. Successor to Parsec; unlike Attoparsec, the core value is human-readable error messages.

{-# LANGUAGE OverloadedStrings #-}
import Data.Void (Void)
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Data.Text (Text)

type Parser = Parsec Void Text

data JsonValue
  = JNull
  | JBool Bool
  | JNumber Double
  | JString Text
  | JArray  [JsonValue]
  deriving Show

sc :: Parser ()
sc = L.space space1 (L.skipLineComment "//") empty

lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc

jsonNull :: Parser JsonValue
jsonNull = JNull <$ lexeme (string "null")

jsonBool :: Parser JsonValue
jsonBool = JBool True  <$ lexeme (string "true")
       <|> JBool False <$ lexeme (string "false")

jsonNumber :: Parser JsonValue
jsonNumber = JNumber <$> lexeme L.signed L.float

jsonValue :: Parser JsonValue
jsonValue = choice [jsonNull, jsonBool, jsonNumber]

Megaparsec strengths:

Pandoc itself uses Megaparsec and earlier Parsec extensively internally.


12. Aeson — JSON

hackage.haskell.org/package/aeson — the de facto Haskell JSON library. The 2.2 major line is stable in 2026.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson
import GHC.Generics

data Post = Post
  { postId    :: Int
  , postTitle :: String
  } deriving (Show, Generic)

instance ToJSON Post where
  toEncoding = genericToEncoding defaultOptions
instance FromJSON Post

main :: IO ()
main = do
  let p  = Post 1 "Hello"
      bs = encode p
  print bs
  print (decode bs :: Maybe Post)

Aeson 2 highlights

options :: Options
options = defaultOptions
  { fieldLabelModifier = camelTo2 '_'   -- postId -> post_id
  , omitNothingFields  = True
  }

13. Testing — hedgehog / tasty / hspec

The Haskell testing ecosystem rests on three pillars.

hspec (BDD)

import Test.Hspec

main :: IO ()
main = hspec $ do
  describe "sum" $ do
    it "sums an empty list to 0" $
      sum ([] :: [Int]) `shouldBe` 0
    it "sums [1,2,3] to 6" $
      sum [1, 2, 3 :: Int] `shouldBe` 6

tasty (test runner)

A runner that ties many test libraries into one tree: tasty-hspec, tasty-hedgehog, tasty-hunit, and more.

import Test.Tasty
import Test.Tasty.HUnit

main :: IO ()
main = defaultMain $ testGroup "all"
  [ testCase "trivial" $ (1 + 1) @?= (2 :: Int)
  ]

hedgehog (property testing)

QuickCheck's successor. Its standout feature is integrated shrinking — failing cases automatically shrink to a minimal counter-example.

import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

prop_reverse_involutive :: Property
prop_reverse_involutive = property $ do
  xs <- forAll $ Gen.list (Range.linear 0 100) (Gen.int Range.linearBounded)
  reverse (reverse xs) === xs

Why property tests matter: instead of "five cases" you assert "this invariant holds across thousands of random inputs". When it fails, hedgehog auto-shrinks to a tiny readable counter-example.

Pick guide


14. Liquid Haskell — refinement types

ucsd-progsys.github.io/liquidhaskell-blog/ — a formal verification tool that layers refinement types on top of ordinary Haskell.

The core idea: write a predicate beside the type, and an SMT solver (Z3 and friends) proves the predicate always holds at compile time.

{-@ measure llen @-}
llen :: [a] -> Int
llen []     = 0
llen (_:xs) = 1 + llen xs

{-@ head :: { xs:[a] | llen xs > 0 } -> a @-}
head :: [a] -> a
head (x:_) = x
head []    = error "won't happen"   -- LH proves this line is unreachable

LH lets you encode "this function cannot be called with an empty list" as part of the type, and the compiler rejects callers that fail to satisfy that condition.

Used in academia and parts of industry (blockchain, aviation and automotive safety).


15. Hasktorch — machine learning

hasktorch.org — Haskell bindings to PyTorch's libtorch. Actively developed at Tweag and by HaskellML.

{-# LANGUAGE DataKinds #-}
import Torch

main :: IO ()
main = do
  x <- randn' [2, 3]     -- Tensor [2, 3]
  y <- randn' [3, 4]
  let z = matmul x y
  print z

What makes Hasktorch attractive is encoding tensor shapes in types.

import Torch.Typed

-- Matrix multiplication whose shapes are checked at compile time
matmul' :: Tensor '[2, 3] Float -> Tensor '[3, 4] Float -> Tensor '[2, 4] Float
matmul' = matmul

If you try to multiply incompatible shapes, GHC refuses. The classic PyTorch shape mismatch becomes a compile-time error.


16. Pandoc — John MacFarlane

pandoc.org — UC Berkeley philosophy professor John MacFarlane has maintained the universal document converter since 2006. Around 40 input formats and 60 output formats. It travels freely between Markdown, LaTeX, DOCX, HTML, ePub, PDF, MediaWiki, RST, and more.

# Markdown -> PDF
pandoc README.md -o readme.pdf

# Markdown -> DOCX with citations
pandoc paper.md --citeproc --bibliography refs.bib -o paper.docx

# HTML -> EPUB
pandoc book.html -o book.epub --metadata title="My Book"

Pandoc's internals are elegant too.

It is the most visible "end users use this every day" project in the Haskell ecosystem.


17. Cardano — Charles Hoskinson and IOG

cardano.org — the blockchain platform built by Ethereum co-founder Charles Hoskinson after leaving the Ethereum project. Run by IOG (Input Output Global, formerly IOHK). The node is entirely Haskell, and smart contracts are written in Plutus (a Haskell DSL).

Why Haskell:

Cardano core consists of:

-- Plutus Tx (conceptual example)
{-# INLINABLE mkValidator #-}
mkValidator :: () -> Integer -> ScriptContext -> Bool
mkValidator _ guess _ = guess == 42

This validator runs on-chain to verify transactions. Plutus compiles a Haskell subset to its own core (Plutus Core), which the node evaluates.


18. Korea / Japan — Haskell Korea, ja-haskell, IOHK Japan

Korea

Japan

Beyond the English communities (r/haskell, Haskell Discourse, Discord), reading both regional communities makes the on-ramp smoother.


19. Who should learn Haskell

Two clear recommendations.

1. To learn functional programming head-on

Writing FP in JavaScript / Kotlin / Scala / OCaml is one thing. Writing it in an environment where purity and laziness are default is another. Haskell leaves no escape hatch. Every side effect shows up in types. That feels restrictive at first, but once it clicks the mental model carries to every other language you write later.

2. Domains where verification and DSLs are high-value

When Haskell is the wrong choice


20. References

Comments

No comments yet.

Sign in to leave a comment