The VikiD Truth

Knowledge base / Computing / Functional programming

Haskell | Haskell Kata |

Haskell Kata

Here is the truth about learning Haskell: you do not learn it by reading. You learn it by typing small things into GHCi until the type errors stop surprising you. A kata is a martial-arts drill repeated until it becomes reflex. This page is a set of drills.

What it is, in plain words

Haskell is a purely functional, lazily evaluated, statically typed language. Each of those words shows up in these katas:

The katas below cover functions, if expressions, lists, ranges and list comprehensions: the vocabulary for everything else.

Why it matters

These few operations (map, filter, folds, comprehensions, ranges) are the core of functional programming in every language. Python’s list comprehensions came from Haskell. JavaScript’s map and filter, Rust’s iterators and Lisp’s mapcar are the same ideas. Drilling them in Haskell, where there is no loop to fall back on, makes them stick.

An everyday analogy: scales on a piano

Nobody performs scales at a concert. But a pianist who has played them a thousand times can sight-read anything, because their fingers already know the shapes. take, drop, zip, foldr and comprehensions are the scales of functional code.

Getting set up in 2026

The recommended installer is GHCup, which manages GHC (the 9.x series), cabal and the Haskell Language Server. On Vikix you can also use the distribution package: xbps-install -S ghc cabal-install. Then start the REPL:

ghci

Useful GHCi commands while you drill:

:t expr        -- show the type of an expression
:i name        -- info about a function or type class
:l file.hs     -- load a file
:r             -- reload after editing
:set +s        -- show time and memory for each evaluation

How to drill

For each block below: type it in, predict the result before pressing Enter, then ask for the type with :t. If you were wrong, change one thing and try again. Some examples are deliberate traps, for instance head [ ] throws an exception (and GHC 9.8+ warns that head is partial), which is the lesson: prefer pattern matching or Data.List.uncons for lists that may be empty.

A few extra katas to finish each session:

-- Write these yourself, then check with the answers
myLength :: [a] -> Int
myLength []     = 0
myLength (_:xs) = 1 + myLength xs

myMap :: (a -> b) -> [a] -> [b]
myMap _ []     = []
myMap f (x:xs) = f x : myMap f xs

-- FizzBuzz with guards
fizz :: Int -> String
fizz n | n `mod` 15 == 0 = "FizzBuzz"
       | n `mod` 3  == 0 = "Fizz"
       | n `mod` 5  == 0 = "Buzz"
       | otherwise       = show n

main :: IO ()
main = mapM_ (putStrLn . fizz) [1..15]

Compare myMap with the PicoLisp equivalent, (de myMap (F L) (when L (cons (F (car L)) (myMap F (cdr L))))). Same recursion over the same list shape, a cons cell at a time (see Find Boxes in Lists).

Common misconceptions

Connections

Getting Started

min 1 2 max 1 2 }

dbl x = x + x
dbls x y = dbl x + dbl y

dsn x = if x > 100
  then x
  else x*2
dsn' x = (if x > 100 then x else x*3) + 1
nL = [0,1,2,3,4,5,6,7,8,9]
nL ++ [10,11,12]
0:nL
nL!!3
nLL= [nL,nL,nL]
nLL ++ [[0,2]]
[10,11,12]:nLL
length nL
null nL
take 5 nL
drop 5 nL
head [ ]
head nL
tail nL
last nL
init nL
reverse nL
maximum nL
minimum nL
sum nL
product nL
5 `elem` nL
elem 5 nL
[1..10]
['a'..'l']
[3, 4..12]
take 5 [40, 45..]
take 20 (cycle nL)
take 10 (repeat 5)
replicate 4 10
[ dsn x | x <- nL, x > 5 ]
beepBop xs = [ if x < 5 then "beep" else "bop" | x <- xs, odd x]
[ x*y | x <- [2,5,10], y <- [8,10,11]]

Last edited · 2 revisions

Tags: #Computing