Coding · Programming languages
Haskell tutorial for beginners
Short answer
Haskell is a purely functional, statically typed language with type inference and lazy evaluation. Programs are built from pure functions, algebraic data types and pattern matching, and side effects are tracked in types such as IO. This page covers installing GHC, types, functions and guards, lists and higher-order functions, data types, type classes, Maybe and Either, and a complete example.
What Haskell is and what it is used for
Haskell is a purely functional programming language with a strong static type system, type inference and lazy evaluation. It was designed by a committee of researchers and first defined in 1990; the current standard is Haskell 2010, and GHC (the Glasgow Haskell Compiler) is the main implementation. In Haskell, functions have no side effects unless their type says so, which makes programs easier to reason about and test.
Haskell is used in finance and fintech, compilers and programming-language tools (Pandoc and the ShellCheck linter are written in Haskell), blockchain projects, web backends and research. Many people also learn it because its ideas, such as algebraic data types, type classes and pure functions, have influenced Rust, Swift, Scala, Kotlin and TypeScript.
Install GHC and run Hello, world
The recommended installer is GHCup, which installs GHC, the build tools Cabal and Stack, and the Haskell Language Server for editors. Every program has a main action of type IO ().
-- hello.hs
main :: IO ()
main = putStrLn "Hello, world!"runghc hello.hs # run without compiling
# Hello, world!
ghc hello.hs && ./hello # compile to a native executable
ghci # interactive REPL; load a file with :load hello.hsComments start with --. In GHCi, :type expression (or :t) shows the type of any expression, which is one of the best ways to learn.
Values, types and type inference
Names are bound once and never change: there are no mutable variables. Type signatures are written with :: and are optional, because the compiler infers them, but writing them for top-level definitions is good practice.
name :: String
name = "Haskell"
year :: Int
year = 1990
ratio :: Double
ratio = fromIntegral year / 1000 -- convert Int to Double explicitly
main :: IO ()
main = do
putStrLn (name ++ " " ++ show year) -- Haskell 1990
print ratio -- 1.99
print (7 `div` 2, 7 `mod` 2) -- (3,1)
print (2 ^ 64 :: Integer) -- 18446744073709551616| Type | Example | Notes |
|---|---|---|
Int | 42 | fixed-size integer (64-bit with GHC on common platforms) |
Integer | 2 ^ 100 | arbitrary-precision integer |
Double | 3.14 | 64-bit floating point |
Bool | True, False | capitalized constructors |
Char | 'h' | a Unicode character |
String | "text" | a list of Char, [Char]; Data.Text is faster |
show converts a value to a String, and print is putStrLn . show. Numbers are never converted implicitly; use fromIntegral, round or truncate.
Functions, guards and pattern matching
Function application is written with spaces, not parentheses: add 2 3. Every function takes one argument and returns another function for the rest, a feature called currying, so add 2 is itself a function. Pattern matching and guards replace most if chains.
add :: Int -> Int -> Int
add x y = x + y
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
grade :: Int -> Char
grade score
| score >= 90 = 'A'
| score >= 70 = 'B'
| otherwise = 'C'
describe :: [a] -> String
describe [] = "empty"
describe [_] = "one item"
describe _ = "many items"
main :: IO ()
main = do
print (add 2 3) -- 5
print (map (add 10) [1, 2, 3]) -- [11,12,13] (partial application)
print (factorial 20) -- 2432902008176640000
print (grade 75) -- 'B'
putStrLn (describe "hi") -- many items
print (if even 4 then "even" else "odd") -- "even"if is an expression and always needs an else. There are no loops: repetition is done with recursion or with higher-order functions such as map, filter and foldr.
Lists, tuples and higher-order functions
Lists hold values of one type and are the most common collection. Tuples group a fixed number of values of possibly different types. List comprehensions, ranges and the function composition operator . make data processing concise.
import Data.List (sortOn)
main :: IO ()
main = do
let nums = [5, 3, 8, 1] :: [Int]
print (head nums, length nums, sum nums) -- (5,4,17)
print (0 : nums) -- [0,5,3,8,1] (prepend)
print (filter even nums) -- [8]
print (foldr (+) 0 [1 .. 10]) -- 55
print [x * x | x <- [1 .. 5], odd x] -- [1,9,25]
print (take 5 [1, 3 ..]) -- [1,3,5,7,9] (lazy infinite list)
let people = [("Ann", 31), ("Bo", 25)] :: [(String, Int)]
print (map fst (sortOn snd people)) -- ["Bo","Ann"]
print ((length . filter (> 2)) nums) -- 3
print (zip [1 :: Int ..] "abc") -- [(1,'a'),(2,'b'),(3,'c')]Because Haskell is lazy, values are computed only when needed. That is why take 5 [1, 3 ..] works on an infinite list. Laziness can also build up large unevaluated expressions; prefer foldl' from Data.List over foldl for sums of long lists.
Algebraic data types and type classes
You define your own types with data. A type can have several constructors (a sum type), each with fields (a product type). Type classes such as Show, Eq, Ord and Functor describe shared behavior, similar to interfaces; deriving generates common instances automatically.
data Shape = Circle Double | Rect Double Double
deriving (Show, Eq)
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rect w h) = w * h
data User = User { userName :: String, userAge :: Int }
deriving Show
class Describable a where
describe :: a -> String
instance Describable Shape where
describe (Circle _) = "a circle"
describe (Rect _ _) = "a rectangle"
main :: IO ()
main = do
print (map area [Circle 1, Rect 2 3]) -- [3.141592653589793,6.0]
putStrLn (describe (Rect 1 1)) -- a rectangle
let u = User { userName = "Ann", userAge = 31 }
print (u { userAge = 32 }) -- User {userName = "Ann", userAge = 32}Maybe, Either and IO
Haskell has no null and no unchecked exceptions in pure code. A value that may be missing has type Maybe a (Just x or Nothing), and a computation that may fail with a reason has type Either e a (Left err or Right x). Side effects such as reading input live in the IO type, and do notation sequences them.
import Text.Read (readMaybe)
safeDiv :: Int -> Int -> Either String Int
safeDiv _ 0 = Left "division by zero"
safeDiv x y = Right (x `div` y)
parseAge :: String -> Maybe Int
parseAge s = case readMaybe s of
Just n | n >= 0 -> Just n
_ -> Nothing
main :: IO ()
main = do
print (safeDiv 10 2) -- Right 5
print (safeDiv 1 0) -- Left "division by zero"
print (parseAge "42") -- Just 42
print (parseAge "abc") -- Nothing
print (fmap (+ 1) (Just 41)) -- Just 42
putStrLn "Your name?"
n <- getLine -- an IO action that returns a String
putStrLn ("Hi, " ++ n)A small complete program: word frequency
This program reads text from standard input, counts word frequencies with a Map and prints the three most common words. It uses function composition, Data.Map from the containers package that ships with GHC, and sorting.
import Data.Char (isAlpha, toLower)
import Data.List (sortBy)
import Data.Ord (comparing, Down (..))
import qualified Data.Map.Strict as Map
wordFreq :: String -> [(String, Int)]
wordFreq =
sortBy (comparing (Down . snd) <> comparing fst)
. Map.toList
. Map.fromListWith (+)
. map (\w -> (w, 1))
. words
. map (\c -> if isAlpha c then toLower c else ' ')
main :: IO ()
main = do
text <- getContents
mapM_ (\(w, n) -> putStrLn (w ++ ": " ++ show n)) (take 3 (wordFreq text))echo "The cat and the dog. The dog sat, and the cat ran." | runghc wordfreq.hs
# the: 4
# and: 2
# cat: 2Where to go next
- The documentation page at haskell.org, which links to tutorials, books and the GHC User's Guide.
- Hackage, the package repository, and Hoogle, a search engine that finds functions by name or by type.
- Learn You a Haskell for Great Good! and Real World Haskell, two freely readable books.
- The Haskell 2010 Language Report for the precise language definition.
FAQ
Is Haskell hard to learn?
It feels different because it has no mutable variables or loops and uses an expressive type system. Beginners who learn recursion, pattern matching and types step by step usually find the core language small and consistent.
What does purely functional mean?
Functions always return the same result for the same input and do not change state or perform input and output. Effects are still possible, but they are expressed in types such as IO.
What is a monad in Haskell?
A monad is a type class for types such as Maybe, Either and IO that support chaining computations with >>=. do notation is syntax for that chaining.
What is lazy evaluation?
Expressions are evaluated only when their value is needed. This allows infinite lists and avoids unnecessary work, but it can make memory usage harder to predict.
Should I use Stack or Cabal?
Both build Haskell projects and both are installed by GHCup. Cabal is the standard build tool; Stack adds curated package snapshots. Either is fine for beginners.