askesis

Haskell essentials: 50 cards on types, type classes, laziness and monads

Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.

ProgrammingIntermediate50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
How do you read add :: Int -> Int -> Int?

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    How do you read add :: Int -> Int -> Int?
    add takes an Int and returns a function of type Int -> Int: -> associates to the right, so the type means Int -> (Int -> Int)
    This is currying: add 2 3 means (add 2) 3, and add 2 on its own is a function of type Int -> Int.
  2. 2
    Operator sections
    An operator with one side filled in, giving a function: (+ 1) adds one, (2 ^) raises 2 to a power, (/ 2) halves
    (- 1) is the number negative one, not a section; write subtract 1 instead.
  3. 3
    What does Eq a => mean in lookup :: Eq a => a -> [(a, b)] -> Maybe b?
    A class constraint: a can be any type with an Eq instance, so keys can be compared with ==
  4. 4
    type vs newtype vs data
    type gives an existing type another name; newtype makes a distinct type around exactly one field, with no runtime cost; data defines a new algebraic data type with any number of constructors and fields
  5. 5
    Define a sum type for shapes and an area function
    haskell
    data Shape = Circle Double | Rect Double Double
    
    area :: Shape -> Double
    area (Circle r) = pi * r * r
    area (Rect w h) = w * h
  6. 6
    What does record syntax give you?
    Named fields with an accessor function for each, and update syntax that copies a value with some fields changed
    haskell
    data User = User { name :: String, age :: Int }
    
    birthday :: User -> User
    birthday u = u { age = age u + 1 }
  7. 7
    Get show, == and ordering for a type without writing instances
    haskell
    data Suit = Hearts | Spades deriving (Show, Eq, Ord)
    Derived Ord orders constructors in the order they are declared, so Hearts < Spades.
  8. 8
    Write a Show instance by hand
    haskell
    data Color = Red | Green
    
    instance Show Color where
      show Red = "red"
      show Green = "green"
  9. 9
    Declare a type class with a default method
    haskell
    class Describable a where
      describe :: a -> String
      describe _ = "a thing"
    An instance can override describe or keep the default with an empty body: instance Describable Bool.
  10. 10
    Int vs Integer
    Int is a fixed-size machine integer (64 bits with GHC on 64-bit platforms) that silently overflows; Integer has arbitrary precision
  11. 11
    What is String in Haskell?
    A type synonym for [Char]: a linked list of characters
    For serious text processing, Text from the text package is far more efficient.
  12. 12
    What is the kind of Maybe?
    * -> * (also written Type -> Type): it takes a type such as Int to make a concrete type
    That’s why Maybe and [] can have Functor instances but Int can’t. Check kinds in GHCi with :k.
  13. 13
    Sum a list with pattern matching
    haskell
    total :: [Int] -> Int
    total [] = 0
    total (x:xs) = x + total xs
  14. 14
    What happens when no pattern matches?
    A runtime exception (non-exhaustive patterns); compile with -Wall to get a warning about missing cases
  15. 15
    What does xs@(x:_) bind?
    xs to the whole list and x to its first element: @ makes an as-pattern, and _ ignores the rest
  16. 16
    Guards
    Conditions tried from top to bottom, each with its own result; otherwise is simply True
    haskell
    sign :: Int -> String
    sign n
      | n < 0 = "negative"
      | n == 0 = "zero"
      | otherwise = "positive"
  17. 17
    where vs let … in
    where attaches bindings to a whole definition, visible in all its guards; let … in is an expression that can appear anywhere
  18. 18
    Match on a value in the middle of an expression
    haskell
    describe xs = case xs of
      [] -> "empty"
      [_] -> "one"
      _ -> "many"
  19. 19
    What does x = x + 1 mean in Haskell?
    A recursive definition of x in terms of itself, which loops forever when evaluated; Haskell has no reassignment
  20. 20
    What does take 3 (cycle [1, 2]) return?
    [1,2,1]: cycle builds an infinite list, and laziness computes only the elements that are needed
  21. 21
    What is a thunk?
    A suspended computation: an unevaluated expression stored until its value is needed
  22. 22
    Weak head normal form (WHNF)
    Evaluated only as far as the outermost constructor or lambda; Just (1 + 1) and (undefined, 2) are both in WHNF
  23. 23
    What does fst (1, undefined) return?
    1: the second component is never evaluated, so undefined never throws
  24. 24
    What does seq a b do?
    Evaluates a to weak head normal form, then returns b
    f $! x uses seq to force x before applying f.
  25. 25
    Why prefer foldl' to foldl for summing a long list?
    foldl is lazy in its accumulator and can build a long chain of thunks; foldl' (from Data.List) forces the accumulator at each step
  26. 26
    foldr (-) 0 [1, 2, 3] vs foldl (-) 0 [1, 2, 3]
    foldr computes 1 - (2 - (3 - 0)), which is 2; foldl computes ((0 - 1) - 2) - 3, which is -6
  27. 27
    Why can foldr stop early on an infinite list?
    It passes the rest of the fold to the function as a lazy argument, so a function that doesn’t use it ends the fold
    haskell
    foldr (\x acc -> x > 10 || acc) False [1 ..]  -- True
  28. 28
    What does [x * 2 | x <- [1 .. 10], even x] produce?
    [4,8,12,16,20]: a list comprehension with a generator and a filter
  29. 29
    The definition of Maybe
    haskell
    data Maybe a = Nothing | Just a
    It models a value that may be missing, where other languages use null.
  30. 30
    Which side of an Either holds the error?
    Left, by convention; Right holds the success value, and fmap and >>= only act on Right
  31. 31
    Get the value out of a Maybe, with a default
    fromMaybe 0 m (from Data.Maybe), or maybe 0 f m to also transform a Just value with f
  32. 32
    Why avoid head?
    It’s partial: head [] throws an exception. Pattern match instead, or use listToMaybe or a NonEmpty list
    GHC 9.8 and later warn when you use head or tail (-Wx-partial).
  33. 33
    What does fmap do?
    Applies a function inside a structure without changing its shape: fmap (+ 1) (Just 2) is Just 3, and fmap (+ 1) Nothing is Nothing
    <$> is fmap as an operator: (+ 1) <$> Just 2.
  34. 34
    What is fmap (+ 1) (1, 2)?
    (1,3): the Functor instance for pairs maps only the second component
    For the same reason, length (1, 2) is 1.
  35. 35
    What does (+) <$> Just 3 <*> Just 4 return?
    Just 7: (+) <$> Just 3 is Just (3 +), and <*> applies that wrapped function to Just 4
    If either argument is Nothing, the result is Nothing.
  36. 36
    What does [(+ 1), (* 2)] <*> [10, 20] return?
    [11,21,20,40]: the list Applicative applies every function to every value
  37. 37
    How are Functor, Applicative and Monad related?
    Every Monad is an Applicative, and every Applicative is a Functor: each class is a superclass of the next
  38. 38
    The type of >>=
    (>>=) :: Monad m => m a -> (a -> m b) -> m b
    It feeds the result of one action into a function that chooses the next action.
  39. 39
    Does return end a function early?
    No. return x (the same as pure x) only wraps x in the monad; the do block carries on with the next line
  40. 40
    How does a do block desugar?
    do { x <- m; rest } becomes m >>= \x -> do { rest }, and do { m; rest } becomes m >> do { rest }
  41. 41
    x <- action vs let x = value in a do block
    <- runs a monadic action and binds its result; let binds a plain value, with no in
    haskell
    main = do
      name <- getLine
      let greeting = "Hi, " ++ name
      putStrLn greeting
  42. 42
    What does this return?
    haskell
    do
      a <- Just 1
      b <- Nothing
      pure (a + b)
    Nothing: in the Maybe monad, the first Nothing short-circuits the rest of the block
  43. 43
    Parse a list of strings, failing if any one fails
    haskell
    traverse readMaybe ["1", "2", "x"] :: Maybe [Int]  -- Nothing
    readMaybe comes from Text.Read. With ["1", "2"] the result is Just [1,2].
  44. 44
    mapM vs mapM_
    mapM runs an action for each element and collects the results; mapM_ runs them only for their effects and returns ()
    forM_ (from Control.Monad) is mapM_ with its arguments flipped: forM_ [1 .. 3] print.
  45. 45
    How do you use the String inside an IO String?
    Bind it inside another IO action with <- (or >>=) and pass it to pure functions from there; pure code can’t run IO actions
  46. 46
    What does $ do in print $ sum [1, 2, 3]?
    It is function application with the lowest precedence, so it replaces parentheses: print (sum [1, 2, 3])
  47. 47
    Function composition with .
    (f . g) x is f (g x), so countWords = length . words defines a function without naming its argument
  48. 48
    What does zipWith (+) [1, 2, 3] [10, 20] return?
    [11,22]: zipWith stops at the end of the shorter list
  49. 49
    Sort a list of pairs by their second element
    sortOn snd pairs (from Data.List)
    sortBy (comparing snd) pairs does the same, with comparing from Data.Ord.
  50. 50
    Look up a key in a Data.Map
    haskell
    import qualified Data.Map.Strict as Map
    
    Map.lookup "ada" (Map.fromList [("ada", 36)])  -- Just 36
    A missing key gives Nothing.

Frequently asked questions

Do I need to know another functional language first?

No, but it helps to have worked through an introductory tutorial: the cards assume you can read a basic function definition.

Which version of Haskell does it use?

Standard Haskell as compiled by current GHC releases, with functions from base such as Prelude, Data.List, Data.Maybe and Text.Read. The Data.Map card uses the containers package.

Is this a monad tutorial?

Not quite. It drills what fmap, <*>, >>= and do-notation do with Maybe, Either, lists and IO, which is what you need to read and write everyday Haskell.

Intermediate

JavaScript

Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.

50 cards
Intermediate

Advanced JS

Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.

50 cards
Intermediate

TypeScript

Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.

50 cards
Beginner

Python

Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.

50 cards
Beginner

Git & terminal

The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.

50 cards
Beginner

Java

Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.

50 cards
Beginner

C#

C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.

50 cards
Beginner

Kotlin

Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.

50 cards
Beginner

Swift

Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.

50 cards
Beginner

Flutter

Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.

50 cards
Beginner

PHP

Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.

50 cards
Beginner

Ruby

Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.

50 cards
Beginner

Go

Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.

50 cards
Intermediate

Rust

Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.

50 cards
Intermediate

C++

Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.

50 cards
Intermediate

Elixir

Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.

50 cards
Intermediate

x86-64 assembly

x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.

50 cards
Beginner

Regex

Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.

50 cards