No history yet

Study Guide

📖 Core Concepts

Haskell Fundamentals Haskell is a pure functional language where functions are mathematical mappings without side effects, and code execution is deferred through lazy evaluation until a result is needed.

Type System Haskell’s strong, static type system uses algebraic data types (Sums and Products) to model data with mathematical precision, ensuring correctness at compile-time.

Type Classes Type classes define sets of operations for types, analogous to algebraic structures like groups or rings, enabling ad-hoc polymorphism that is verified by the compiler.

Monads & Applicatives Monads provide a powerful abstraction for sequencing computations within a context, such as state or error handling, turning complex operations into manageable, composable pipelines.

Side Effects & State The IO and State monads isolate real-world interactions and mutable state from pure logic, describing actions to be performed rather than executing them directly.

Advanced Haskell Advanced features like GADTs and Type Families allow for type-level programming, enabling the creation of highly expressive, type-safe Domain Specific Languages (DSLs) for complex modeling.

📌 Must Remember

Haskell Fundamentals

  1. Purity: Functions in Haskell have no side effects. Given the same input, a function will always return the same output, a property known as referential transparency.
  2. Everything is an Expression: Haskell has no statements. if-then-else, case, and let constructs all evaluate to a value.
  3. Lazy Evaluation: Expressions are not evaluated when they are bound to a variable, but only when their result is demanded. Unevaluated expressions are stored as “thunks.”
  4. Immutability: Once a value is defined, it cannot be changed. New values are created instead of modifying existing ones.
  5. Currying is Default: Every function in Haskell officially takes only one argument. Functions of multiple arguments are implemented as functions that return functions.
  6. Function Application: Function application is done by simple juxtaposition (e.g., f x), not with parentheses like f(x).

Type System

  1. Strong & Static: Types are checked at compile time, not runtime. If a program compiles, it is guaranteed to be free of a large class of runtime errors.
  2. Type Inference: You rarely need to write explicit type signatures for top-level functions, as the compiler can infer them, though it is good practice to do so.
  3. Algebraic Data Types (ADTs): Data is modeled using Sum Types (logical OR, like data Bool = True | False) and Product Types (logical AND, like data Point = Point Float Float).
  4. Pattern Matching: This is the primary way to deconstruct data. It is a powerful form of control flow that checks for structural equality and binds variables.
  5. Parametric Polymorphism: Functions can be written to operate on any type by using type variables (e.g., id :: a -> a). This is similar to generics in other languages.
  6. Maybe and Either: These are standard Sum Types used for safe error handling. Maybe a represents an optional value, and Either a b represents a value that can be one of two types, often used for success/failure cases.

Type Classes

  1. Not Object-Oriented Classes: Type classes are more like interfaces or mathematical structures (e.g., a Group). They define a set of functions that a type must implement to be an “instance” of that class.
  2. Ad-Hoc Polymorphism: A function can operate on any type that is an instance of a particular type class. For example, (+) works on any type in the Num class.
  3. Key Standard Classes: Eq (equality), Ord (ordering), Show (string representation), and Read (parsing from a string) are fundamental.
  4. Functor: A type class for types that can be mapped over (e.g., lists, Maybe). It abstracts the action of applying a function to every element inside a structure.
  5. Derived Instances: The compiler can automatically generate instances for common type classes like Eq, Ord, and Show for many data types.

Monads & Applicatives

  1. Contextual Computation: Monads are a design pattern for building pipelines of computations that are wrapped in a context (e.g., potential absence of a value with Maybe, I/O actions with IO).
  2. Applicative is a Superset of Functor: All Applicatives are Functors. Applicatives allow you to apply a function wrapped in a context to a value wrapped in a context.
  3. Monad is a Superset of Applicative: All Monads are Applicatives. Monads add the ability to sequence computations where the next computation depends on the result of the previous one.
  4. do-notation is Syntactic Sugar: It is a cleaner syntax for chaining monadic operations. Every do block can be rewritten using the bind operator (>>=).
  5. The Monad Laws: All valid Monad instances must obey three laws: Left Identity, Right Identity, and Associativity. These ensure predictable, chainable behavior.

Side Effects & State

  1. IO a is a Description: An IO a value does not contain a value of type a. It is a description of a computation that, when executed, will produce a value of type a and may have side effects.
  2. Purity is Maintained: Side effects are isolated within the IO monad. Code outside of IO remains pure and referentially transparent.
  3. main is the Entry Point: The entire Haskell program is the evaluation of main :: IO (). The runtime system executes the I/O actions described by this value.
  4. The State Monad: State s a allows for the simulation of mutable state purely. It represents a computation that takes an initial state s and produces a result a along with a new state s.
  5. **No

escaping” a Monad in Pure Code:** You cannot extract a value from an IO or State context and use it in pure code. You must use monadic functions to work within the context.

Advanced Haskell

  1. Monad Transformers: Libraries like mtl provide transformers (e.g., StateT, ReaderT) to combine the effects of multiple monads into a single monad stack.
  2. GADTs (Generalized Algebraic Data Types): GADTs allow the return type of a data constructor to be more specific than the data type itself, enabling you to encode more complex invariants at the type level.
  3. Type Families: These are functions that operate on types, allowing for type-level programming. They are a way to compute a type from another type.
  4. DSLs (Domain Specific Languages): Haskell's strong type system and flexible syntax make it ideal for creating embedded DSLs, which allow you to write programs for a specific domain (like quantum circuits or linear algebra) that are type-checked by the Haskell compiler.
  5. Kinds: Kinds are the “types of types.” For example, Int has kind *, while Maybe has kind * -> * because it takes a type to produce a type.

📚 Key Terms

Purity: A property of a function that guarantees it has no observable side effects (like I/O or modifying global state) and always returns the same output for the same input.

  • Used in context: The (+) function is pure; it only calculates a sum and doesn't change anything in the outside world.
  • Topic: Haskell Fundamentals

Lazy Evaluation: An evaluation strategy where expressions are not computed until their results are actually needed, allowing for the definition of infinite data structures.

  • Used in context: take 5 [1..] works because lazy evaluation only computes the first five elements of the infinite list [1..].
  • Topic: Haskell Fundamentals

Currying: The technique of translating a function that takes multiple arguments into a sequence of functions, each with a single argument.

  • Used in context: add x y is syntactic sugar for (add x) y; add x is a curried function that returns a new function waiting for y.
  • Don't confuse with: Tupled arguments, where a function takes a single tuple like add (x, y).
  • Topic: Haskell Fundamentals

Sum Type: An algebraic data type that can have a value from a set of possible options (a logical OR), where each option is a distinct data constructor.

  • Used in context: data Shape = Circle Float | Rectangle Float Float is a Sum Type; a Shape can be either a Circle or a Rectangle.
  • Topic: Type System

Product Type: An algebraic data type that combines several values into one (a logical AND), typically using a single data constructor.

  • Used in context: data Person = Person String Int is a Product Type; a Person value contains both a String and an Int.
  • Topic: Type System

Type Class: A construct that defines a set of functions and properties that a type must support to be a member of that class, enabling ad-hoc polymorphism.

  • Used in context: To use the == operator on our Person type, we must make it an instance of the Eq type class.
  • Don't confuse with: Classes in object-oriented programming.
  • Topic: Type Classes

Functor: A type class representing data structures that can be mapped over, defined by the function fmap :: (a -> b) -> f a -> f b.

  • Used in context: List, Maybe, and IO are all Functors because you can use fmap to apply a function to the value(s) inside them.
  • Topic: Type Classes

Monad: A design pattern and type class that provides a structure for sequencing computations within a context, defined by the return function and the bind operator (>>=).

  • Used in context: The Maybe monad sequences computations that might fail, automatically propagating Nothing values.
  • Topic: Monads & Applicatives

Thunk: An unevaluated expression stored in memory, representing a computation that will be forced (evaluated) when its result is needed.

  • Used in context: In let x = 1 + 2, x is initially bound to a thunk representing 1 + 2, not the value 3.
  • Topic: Haskell Fundamentals

IO Monad: A specific monad that encapsulates actions with side effects (I/O), isolating them from the pure parts of the program.

  • Used in context: readFile "file.txt" doesn't return a String, but an IO String—a description of an action that will produce a string.
  • Topic: Side Effects & State

GADT (Generalized Algebraic Data Type): An extension to Haskell's data types that allows data constructors to explicitly specify their return type, enabling more expressive and safe type-level constraints.

  • Used in context: Using a GADT, we can define a type for expressions that guarantees type safety, e.g., an Expr a where a is the type of the evaluated expression.
  • Topic: Advanced Haskell

🔍 Key Comparisons

FeatureImperative (Python/Node.js)Functional (Haskell)
ExecutionEager evaluation of statements in a sequence.Lazy evaluation of expressions based on demand.
StateRelies on mutable state and variables.Avoids mutable state; data is immutable.
FunctionsProcedures that can have side effects.Mathematical mappings from inputs to outputs.
Control FlowLoops (for, while), if statements.Recursion, pattern matching, function composition.
Error HandlingExceptions, try-catch blocks.Type-based (Maybe, Either), monadic control flow.

Memory trick: Imperative programming is a list of instructions for the computer to follow. Functional programming is a description of what the result is.

Topic: Haskell Fundamentals


FeatureFunctorApplicativeMonad
PurposeApplying a pure function to a value in a context.Applying a function in a context to a value in a context.Sequencing computations where one depends on the last.
Key Functionfmap :: (a -> b) -> f a -> f b<*> :: f (a -> b) -> f a -> f b>>= :: m a -> (a -> m b) -> m b
AnalogyA conveyor belt applying one tool to each item.Two conveyor belts merging items with tools.A conveyor belt where the item's properties decide the next tool.
Use Casefmap (+1) (Just 2)(+) <$> Just 2 <*> Just 3Just 3 >>= \x -> Just (x + 1)

Memory trick: Functor maps, Applicative applies, Monad binds (chains). Each is a superset of the one before it: Monad > Applicative > Functor.

Topic: Monads & Applicatives


FeatureMaybe aEither a b
PurposeRepresents a value that may or may not exist.Represents a value that can be one of two distinct types.
ConstructorsJust a (value exists) or Nothing (value is absent).Left a (often an error) or Right b (often a success).
Error InfoNothing carries no information about why it failed.Left a can hold detailed information about the failure.
Common UseOptional values, safe dictionary lookups.Functions that can fail with a specific error type.
Examplelookup :: Eq a => a -> [(a, b)] -> Maybe bparseNumber :: String -> Either String Int

Memory trick: Maybe it's there, Maybe it's not. With Either, you get either this or that.

Topic: Type System

⚠️ Common Mistakes

MISTAKE: Thinking IO String is a String.

  • Why it happens: In imperative languages, a function like readFile() returns a string directly. In Haskell, it returns a description of an action.
  • Instead: Treat IO String as a program that, when run, will produce a String. Use do-notation or (>>=) to work with the value inside the IO context.
  • Topic: Side Effects & State

MISTAKE: Confusing Type Classes with object-oriented classes.

  • Why it happens: The name “class” is overloaded. OO classes bundle data and methods together into objects.
  • Instead: Think of Type Classes as contracts or mathematical structures. They define a set of functions that a type must support, but they don't hold data or state.
  • Topic: Type Classes

MISTAKE: Trying to “get a value out” of a Monad in pure code.

  • Why it happens: The goal is often to use a value from a Maybe or IO in a pure function, leading to attempts to “unwrap” it unsafely.
  • Instead: Bring the pure function into the Monad's context. Use fmap for simple function application, or bind (>>=) to chain operations that stay within the context.
  • Topic: Monads & Applicatives

MISTAKE: Using parentheses for function application, like f(x, y).

  • Why it happens: This syntax is standard in nearly all C-style languages (Python, Java, JavaScript, etc.).
  • Instead: Remember that function application in Haskell is just juxtaposition: f x y. Parentheses are only used for grouping expressions, like f (x + y).
  • Topic: Haskell Fundamentals

MISTAKE: Writing complex chains of (>>=) instead of using do-notation.

  • Why it happens: Beginners often learn (>>=) first and try to write everything with it, leading to deeply nested and hard-to-read lambda expressions (the “pyramid of doom”).
  • Instead: Use do-notation for any monadic sequence with more than two steps. It is functionally equivalent but vastly more readable.
  • Topic: Monads & Applicatives

🔄 Key Processes

Lazy Evaluation with Thunks

Step 1: Binding

  • What happens: An expression is bound to a variable. The expression is NOT evaluated.
  • Key indicator: A let or where clause is used, e.g., let x = someComplexCalculation 10.
  • In memory: A “thunk” is created. This is a data structure containing the function (someComplexCalculation) and its arguments (10).

Step 2: Demanding the Value

  • What happens: Another part of the code requires the value of the variable (e.g., trying to print it).
  • Key indicator: The variable is used in a context that needs a concrete value, like an argument to print or an if condition.

Step 3: Forcing the Thunk

  • What happens: The runtime system executes the code stored in the thunk.
  • Key indicator: The CPU performs the actual computation.

Step 4: Updating the Thunk

  • What happens: The thunk is overwritten in memory with the result of the computation (its Normal Form).
  • Key indicator: The thunk is replaced by the value (e.g., 12345).
  • Why it matters: The next time the variable x is accessed, the value is returned immediately without re-computation. This is call-by-need.

Topic: Haskell Fundamentals


Pattern Matching Execution Flow

Step 1: Function Call

  • What happens: A function with multiple clauses (patterns) is called with arguments.
  • Example: safeHead [1,2,3] where safeHead is defined as:
    safeHead :: [a] -> Maybe a
    safeHead [] = Nothing      -- Clause 1
    safeHead (x:xs) = Just x -- Clause 2
    

Step 2: Top-to-Bottom Pattern Check

  • What happens: The runtime checks the input [1,2,3] against the patterns of each clause in order.
  • Clause 1: Does [] match [1,2,3]? No.
  • Clause 2: Does (x:xs) match [1,2,3]? Yes.

Step 3: Variable Binding

  • What happens: Upon a successful match, variables in the pattern are bound to the corresponding parts of the input data.
  • Key indicator: In our example, x is bound to 1 and xs is bound to [2,3].

Step 4: Right-Hand Side Evaluation

  • What happens: The expression on the right-hand side of the matching clause is evaluated using the newly bound variables.
  • In our example: The expression Just x is evaluated. Since x is 1, the result is Just 1.
  • Common error: Forgetting a catch-all pattern (_), which can lead to a runtime exception if no patterns match.

Topic: Type System

📐 Key Formulas

Functor Law: Identity

fmap id=idfmap\ id = id
  • Variables:
    • fmap: The mapping function for any Functor.
    • id: The identity function (id x = x).
  • When to use: This is a property that every valid Functor instance must satisfy. It's used to reason about code and ensure predictable behavior.
  • Common error: Creating a Functor instance that modifies the structure of the container, thus violating this law.

Topic: Type Classes


Monad Law: Left Identity

return a >>=k=k areturn\ a\ >>= k = k\ a
  • Variables:
    • return: Lifts a pure value into a monad.
    • a: A pure value.
    • >>=: The bind operator.
    • k: A function from a pure value to a monadic value (a -> m b).
  • When to use: This law must hold for any valid Monad instance. It ensures that return doesn't add any extra effects.
  • Common error: Defining return in a way that introduces unexpected side effects or contextual changes.

Topic: Monads & Applicatives