Skip to content
Volume H · The TensorCore.si Library

Haskell

Pure functions, powerful types.

Haskell is a purely functional language with a strong static type system and lazy evaluation. It changes how you think about programming, and its ideas (type inference, monads, immutability) now shape Rust, Scala, Swift, TypeScript and modern ML frameworks.

Prologue

Why learn Haskell?

Even if you never ship Haskell in production, learning it makes you a better programmer in every other language.

  • Correctness: the type checker catches whole classes of bugs before your code runs.
  • Pure functions: no hidden state, so code is easy to test and reason about.
  • Ideas that spread: functional patterns used in JAX, React and Rust started here.
Prologue

Set up and run your first program

Install GHCup, which gives you GHC, the Cabal build tool and the Haskell language server.

terminal
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
ghc --version
runghc hello.hs
hello.hs
main :: IO ()
main = do
  let name = "Ada"
  putStrLn ("Hello, AI! Welcome to Haskell, " ++ name ++ ".")
TipUse ghci for an interactive prompt: load a file with :load hello.hs.
Chapter 1

Values and types

Values never change once defined. Type signatures are optional because GHC infers them, but writing them documents your code.

values.hs
layers :: Int
layers = 32

lr :: Double
lr = 3e-4

fp16GB :: Double -> Double
fp16GB params = params * 2 / 1e9

main :: IO ()
main = print (fp16GB 8.03e9)   -- 16.06
Chapter 2

Functions and pattern matching

Functions are defined by equations and pattern matching. All functions are curried, so you can apply them partially.

functions.hs
precision :: Int -> String
precision 32 = "FP32"
precision 16 = "FP16"
precision 8  = "FP8"
precision _  = "other"

scale :: Double -> Double -> Double
scale k x = k * x

double :: Double -> Double
double = scale 2          -- partial application

main = do
  putStrLn (precision 8)
  print (double 21)
Chapter 3

Lists and higher-order functions

map, filter and foldr replace most loops. List comprehensions read like maths.

lists.hs
vram :: [(String, Int)]
vram = [("H100", 80), ("H200", 141), ("RTX 5090", 32)]

big :: [String]
big = [name | (name, gb) <- vram, gb >= 80]

total :: Int
total = sum (map snd vram)

main = do
  print big                 -- ["H100","H200"]
  print total
  print (foldr (+) 0 [1..10])
Chapter 4

Custom data types

Define your own types with data. The compiler checks that every case is handled. Maybe replaces null.

types.hs
data Device = CPU | GPU String Int deriving Show

memory :: Device -> Maybe Int
memory CPU         = Nothing
memory (GPU _ gb)  = Just gb

describe :: Device -> String
describe d = case memory d of
  Nothing -> "no dedicated memory"
  Just gb -> show gb ++ " GB of VRAM"

main = mapM_ (putStrLn . describe) [CPU, GPU "H100" 80]
Chapter 5

IO and do notation

Side effects live in the IO type, so pure and impure code stay separate. do blocks sequence actions.

io.hs
import qualified Data.Map as M

main :: IO ()
main = do
  text <- readFile "notes.txt"
  let counts = M.fromListWith (+) [(w, 1 :: Int) | w <- words text]
  mapM_ print (take 5 (M.toList counts))
Part II · AI with Haskell

AI with Haskell

Haskell is not a mainstream ML language, but it has typed tensor libraries and is used to build safe AI tooling, compilers for ML hardware and LLM clients. Its ideas of pure functions and function transformations are at the heart of JAX.

The essential libraries

hasktorchHaskell bindings to libtorch (PyTorch C++).
aesonFast JSON encoding and decoding.
http-conduitHTTP requests to model APIs.
hmatrixLinear algebra with BLAS and LAPACK.

Gradient descent from scratch

Fit y = 3x + 0.5 with pure functions and a fold.

gradient.hs
xs, ys :: [Double]
xs = [-1, -0.98 .. 1]
ys = map (\x -> 3 * x + 0.5) xs

step :: (Double, Double) -> Int -> (Double, Double)
step (w, b) _ = (w - lr * gw, b - lr * gb)
  where
    lr   = 0.1
    n    = fromIntegral (length xs)
    errs = zipWith (\x y -> w * x + b - y) xs ys
    gw   = 2 / n * sum (zipWith (*) errs xs)
    gb   = 2 / n * sum errs

main = print (foldl step (0, 0) [1 .. 500])   -- about (3.0, 0.5)
Part III · Build an AI agent

Build an AI agent in Haskell

With http-conduit and aeson you can run a tool-calling loop against a local model. Types make the message format explicit.

Your goal→Model plans→Tool call→Your code runs the tool→Result back to model→Final answer
Agent.hs
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import Data.Aeson
import Data.Aeson.Lens            -- from the lens-aeson package
import Control.Lens ((^?), (^..))
import qualified Data.Vector as V

tools :: Value
tools = toJSON [object ["type" .= ("function" :: String), "function" .= object
  [ "name" .= ("get_weather" :: String), "description" .= ("Weather for a city" :: String)
  , "parameters" .= object ["type" .= ("object" :: String)
      , "properties" .= object ["city" .= object ["type" .= ("string" :: String)]]
      , "required" .= ["city" :: String]]]]]

chat :: [Value] -> IO Value
chat msgs = do
  let req = setRequestBodyJSON (object ["model" .= ("llama3.1" :: String), "messages" .= msgs, "tools" .= tools])
          $ "POST http://localhost:11434/v1/chat/completions"
  getResponseBody <$> httpJSON req

loop :: Int -> [Value] -> IO ()
loop 0 _ = putStrLn "gave up"
loop n msgs = do
  res <- chat msgs
  let Just msg = res ^? key "choices" . nth 0 . key "message"
      calls = msg ^.. key "tool_calls" . values
  if null calls then print (msg ^? key "content" . _String) else do
    let reply c = object [ "role" .= ("tool" :: String), "tool_call_id" .= (c ^? key "id" . _String)
                         , "content" .= ("It is 24°C and sunny." :: String) ]
    loop (n - 1) (msgs ++ [msg] ++ map reply calls)

main = loop 5 [object ["role" .= ("user" :: String), "content" .= ("Should I take an umbrella in Lahore today?" :: String)]]

Add the packages http-conduit, aeson, lens-aeson, lens and vector to your Cabal file.

Run it locallyInstall Ollama, run ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.
Part IV · Your path

Haskell learning roadmap

Tick items off as you go. Your progress is saved in this browser.

Beginner

Intermediate

Advanced

Part IV · Your path

Projects to build

CalculatorParse and evaluate maths expressions.
Word counterCount words in a file with Data.Map.
Typed chatbotCall a local LLM with strongly typed messages.
Reference

Haskell cheat sheet

TaskSyntax
Functionadd x y = x + y
Type signatureadd :: Int -> Int -> Int
Lambda\x -> x * 2
List comprehension[x*x | x <- [1..5]]
Pattern matchcase m of Just x -> x; Nothing -> 0
Compose(f . g) x
Reference

Common mistakes

  • Laziness can build up huge unevaluated expressions; use strict folds like foldl'.
  • head [] crashes; prefer pattern matching or Maybe.
  • Indentation matters inside do, let and where blocks.
Reference

Where to go next

On TensorCore.si: Rust volume, Scala volume.

Keyboard: [ previous language · ] next language · / search the library