Inside a polymorphic function `reverse : ∀α. List α → List α`, what operations on the elements of type α can the function legally perform?
AAny operation, because the function will be type-checked at each call site with a concrete type
BOnly operations defined for all types — the function can rearrange elements but cannot inspect, compare, or compute with their values
CAny operation that the most common concrete type (e.g., Int) supports
DOperations determined at runtime by inspecting the actual type of α
Parametric polymorphism means the type variable α is universally quantified: the function must work correctly for every possible type substitution. Inside the function, the only thing you can do with an element of type α is treat it as an opaque value — you can pass it around, put it in a list, return it, but you cannot add it, compare it, or call type-specific methods on it, because those operations are not defined for all types. This restriction is what makes the function genuinely generic. Option A describes ad-hoc polymorphism (overloading), which is a different mechanism.
Question 2 Multiple Choice
A team is choosing between Java-style type erasure and C++/Rust-style monomorphization for compiling a generic container library. Which tradeoff is correct?
AMonomorphization produces smaller binaries because it avoids storing type information at runtime
BType erasure produces faster code because it avoids boxing overhead, while monomorphization uses more memory
CMonomorphization produces faster, specialized code but larger binaries; type erasure produces smaller binaries but may incur boxing overhead
DBoth approaches produce identical machine code; the difference is only in compile-time checking
Monomorphization generates a separate specialized copy of each function for each concrete type it is called with (e.g., List_int, List_string). Each copy is highly optimized for its specific type with no boxing — faster at runtime. But the binary grows with each new instantiation. Type erasure compiles a single version of the code using a uniform representation (typically boxed pointers), keeping the binary small but requiring indirection at runtime. Neither approach is universally better; the tradeoff is speed vs. binary size and compile time.
Question 3 True / False
Parametric polymorphism can be fully enforced at compile time — no runtime type inspection is required to maintain type safety.
TTrue
FFalse
Answer: True
This is one of the key distinguishing features of parametric polymorphism. Because the type variable is universally quantified and the function is restricted to operations valid for all types, the compiler can check type safety once at definition time and at each instantiation call site — all statically. The Hindley-Milner type inference system demonstrates this: it infers polymorphic types and all instantiations without any runtime type tags. This contrasts with Java's pre-generics approach (using Object with runtime casts) or Python's duck typing, which rely on runtime type information.
Question 4 True / False
A type variable like `α` in `identity : ∀α. α → α` is essentially just a placeholder for 'any type' — the function can be specialized to perform type-specific operations when called with a concrete type.
TTrue
FFalse
Answer: False
Type variables in parametric polymorphism are universal constraints, not merely labels. The function body must be written without assuming anything about α — it cannot perform any α-specific operation. 'Specialization' in parametric polymorphism (whether via type erasure or monomorphization) does not add new operations; it only fills in the type at the call site for the same generic behavior. If you want type-specific behavior, you need ad-hoc polymorphism (overloading) or type classes/traits, which are different mechanisms that explicitly list which operations each type supports.
Question 5 Short Answer
Why can a parametrically polymorphic function `identity : ∀α. α → α` not simply add 1 to its argument, even if every actual call site passes an integer?
Think about your answer, then reveal below.
Model answer: The type checker evaluates the function body under the assumption that α is an arbitrary, unknown type — not an integer. Addition is not defined for all types (you cannot add two strings, two booleans, or two functions), so the type checker rejects it as invalid for type α. The function must be written to work for every possible α simultaneously, not just for the types it happens to be called with today. If the programmer wants an integer-specific increment, they must give the function a monomorphic type `Int → Int`.
This is the key restriction that makes parametric polymorphism safe and predictable. Because the function is universally quantified, the type checker enforces that every operation in the body is valid for all types — including types the programmer hasn't thought of yet. The payoff is that a caller can safely instantiate the function with any type, confident the body cannot do anything unexpected with their value. Allowing type-specific operations inside a supposedly polymorphic function would break this guarantee and require runtime type inspection — converting parametric into ad-hoc polymorphism.