Class inheritance 2026-10-24
Class inheritance defines a software class using another software class's implementation and may add or override behavior. When it also establishes subtyping, the derived software class must satisfy the base software class's behavioral contract for clients to substitute it safely. Code reuse alone is not sufficient justification for an inheritance relationship. Object composition often better represents a software object containing a replaceable component.
Function subtyping variance Created 2026-10-24 Updated 2026-11-05
If and , a function of type safely supplies a contract of type . Every input permitted by the new contract is already accepted, and every possible returned value meets the weakened result promise. The input constraint reverses subtyping, while the output constraint preserves subtyping. Reversing either direction can respectively supply an unsupported input or promise a result more specific than the implementation returns. A language's generic type type constructor may nevertheless be invariant unless its declaration exposes the appropriate type variance.
Lower type bound 2026-10-31
A lower bound requires an admissible type argument to be a supertype of a specified type. In Scala,
[V >: U] means . For an immutable software class covariant in U, a method accepting a new output value cannot consume U directly in a contravariant method-parameter position. Instead, add[V >: U](x: V) accepts a possibly wider value and returns a fresh instance parameterized by V. The original narrower instance remains unchanged. The variance-position rule and subtyping justify this widening; a bound alone does not authorize mutating an existing instance through a widened reference.[ref]Scala 2.13 variance annotations specification. Variance Annotations, lower-bound widening in the Sequence.append example
Numeric widening 2026-10-31
A language-defined conversion from a numeric type to another admitted numeric type, such as Scala's conversion of an
Int argument to Double. It can make an overloaded method applicable even when the argument's static type differs from the parameter type. It does not imply a subtyping relationship between every pair of numeric types or exact representation of every source value; sufficiently large integers can lose precision in a floating-point target. [ref]Scala 2.11 expressions specification. Numeric Widening
Class inheritance defines a software class by extending another software class, inheriting its available operations and optionally adding members or providing method overriding. In languages supporting it, traits and mixins can contribute reusable behavior as well.
Use inheritance for a sound specialization whose instances can satisfy the superclass's contract wherever its instances are expected. For example, every specific gate in part (d) is usable through the common gate object interface. Subtyping supports this substitution and dynamic dispatch selects the specialized calculation. Merely noticing shared code does not establish the required behavioral relationship; object composition is often better for a replaceable component or policy that an object contains. A subclass must preserve the superclass's promised behavior, rather than just sharing its field names.
Programming language polymorphism means that one interface or program definition can be used with values of multiple types. In object-oriented programming, subtype polymorphism allows references with a common static type to denote instances of different concrete subclasses or implementers of a trait. An object method call uses dynamic dispatch to select the overriding implementation belonging to the receiver's runtime type. For example, different implementations of a playlist interface can respond to the same
insert call without the client testing their concrete classes.Parametric polymorphism uses type parameters, as in
Sequence[A], so the same definition works uniformly for songs, integers, or other elements. Ad hoc polymorphism includes method overloading, where different parameter signatures share a name and the static types determine overload selection. These mechanisms are distinct: selecting an overloaded signature is not the same step as dispatching an override of that selected signature. Class inheritance can supply implementations, but the reusable interface and legal subtyping relationship determine what a polymorphic client may do.A covariant type parameter preserves subtyping: if , then
PartialFn[T,A] <: PartialFn[T,B] when its second parameter is covariant. In Scala the class annotation is +U. A mapping that produces a narrower output can be used where a broader output is expected. Covariance describes a type relationship; it does not convert stored values or make mutation through a widened reference safe.[ref]Scala 2.12 types specification. Relations between types: conformance of parameterized types and variance
The plus marks a covariant type parameter: if , then
Iterator[S] <: Iterator[T]. An iterator producing a subtype's values can be used where an iterator of the supertype is required. For example, a string iterator can be viewed as an iterator of AnyRef, because strings are reference values. The direction is appropriate for a producer: calling next() gives a value valid at the wider type. It would not be safe to expose an ordinary insertion method accepting arbitrary wider-type values into a subtype-only collection. This is a subtyping relationship, not a request to transform each produced value.[ref]Scala 2.12 types specification. Conformance of parameterized types with covariant parameters
Class inheritance defines a subclass by extending another class's behavior, often with method overriding and subtyping. An editor might have an abstract
Command type with execute, and subclasses for insertion and deletion. The controller keeps commands through the common type and dynamic dispatch chooses each command's actual operation. Alternatively, a specialized document can extend a base document's documented behavior. An inheritance relationship used for substitution must preserve the superclass contract; borrowing its code alone does not justify exposing incompatible behavior. Commands exemplify the Command pattern without exposing document storage to the controller.Fun[Base, Derived] can safely supply a Fun[Base, Base] contract; Fun[Base, Base] cannot in general supply a Fun[Base, Derived] contract. The input obligation is unchanged, while a derived result satisfies every base-result promise. This is the covariant output rule of function subtyping.With the invariant printed trait, implement the safe view explicitly:
def widenResult(f: Fun[Base, Derived]): Fun[Base, Base] =
new Fun[Base, Base] {
def apply(x: Base): Base = f(x)
}Using the illustrative classes from part i, an unsafe substitution would let a client assume a derived-only operation on a result that is merely a base software object:
val broadResult = new Fun[Base, Base] {
def apply(x: Base): Base = new Base
}
def derivedResultClient(f: Fun[Base, Derived]): Int =
f(new Base).special()
// derivedResultClient(broadResult) must be rejected.The direct, variance-aware object interface that would express both safe directions is
trait Fun[-S, +T] { def apply(x: S): T }. Its object method consumes the contravariant parameter and produces the covariant parameter. This is an explicitly proposed variant of the printed invariant declaration, not an assumption that its annotations were already present.A function accepting every
Base can safely be used where only Derived inputs will be supplied. The reverse direction is unsafe. A Derived argument is already a valid Base argument; restricting the possible callers introduces no new obligation for the function. This is the contravariant input rule of function subtyping.The printed trait has generic type invariance, so Scala does not directly permit either parameter-changing assignment. The safe behavior can be exposed using an explicit adapter without changing that trait:
def restrictInput(f: Fun[Base, Base]): Fun[Derived, Base] =
new Fun[Derived, Base] {
def apply(x: Derived): Base = f(x)
}For the unsafe direction, take illustrative classes in which a derived software object has an extra operation:
class Base
class Derived extends Base { def special(): Int = 1 }
val narrow = new Fun[Derived, Base] {
def apply(x: Derived): Base = { x.special(); x }
}
def broadClient(f: Fun[Base, Base]): Base = f(new Base)
// broadClient(narrow) must be rejected.If that substitution were accepted, the client could pass an ordinary base software object to code that calls
special, which that software object does not support. The rejected call is intentional counterexample code, not a promised compilable application. Variance annotations are needed to make sound direct conversions part of the generic trait's declared subtyping relation.[ref]Scala 2.13 variance annotations specification. Variance Annotations: missing annotation is invariant; method-input positions reverse variance
The maximally variant safe declarations are:
trait Rel1[+X, +Y] { val pairs: List[(X, Y)] }
trait Rel2[-X, +Y] { def relatives(x: X): List[Y] }
trait Rel3[-X, -Y] { def related(x: X, y: Y): Boolean }Leaving one or both parameters invariant is also legal but admits fewer subtyping conversions. These are type variance annotations.
For
Rel1, both parameters occur only as outputs. Scala's immutable List and Tuple2 are covariant, so a list of pairs of narrower types can safely be read as pairs of broader types. Thus both may be covariant type parameters. A contravariant declaration would permit, for example, treating a Rel1[Any, Int] containing (7, 0) as a Rel1[String, Int], then reading its first component as a String. That would be unsound; the compiler rejects the negative annotation at this output position.For
Rel2, X occurs as a method argument and therefore may be a contravariant type parameter; Y is returned in an immutable list and may be a covariant type parameter. A function that accepts every Any is safe to use where only String queries are issued. The opposite direction would be unsafe: if X were covariant, the following hypothetical conversion could call a string-only implementation with an integer:val strings: Rel2[String, Int] = new Rel2[String, Int] {
def relatives(x: String): List[Int] = List(x.length)
}
// Unsafe if Rel2 were incorrectly covariant in X:
// val arbitrary: Rel2[Any, Int] = strings
// arbitrary.relatives(7)Making
Y contravariant would instead allow an integer-valued Rel2[String, Any] to be read as a string-valued Rel2[String, String], recreating the output error.For
Rel3, both generic values are consumed and only a Boolean function result is produced, so both may be contravariant type parameters. A predicate accepting all values of broad types can answer queries restricted to narrower types. Covariance in either parameter would allow passing an unsupported value to a specialized implementation. For example, treating a string-only first-argument predicate as accepting Any would permit related(7, y) even though its body might call x.length. These are applications of the variance-position rule: method inputs reverse variance, whereas immutable outputs preserve it.[ref]Scala 2.13 variance annotations specification. Variance annotations and the change of position in method parameter types
Scala 2026-10-24
A programming language supporting object-oriented programming and functional programming. Its traits support reusable mixin behavior, while type variance controls how parameterized types participate in subtyping. The Scala 2 and Scala 3 language versions differ in some syntax and typing details; examples should state the version relevant to a particular claim. Official documentation: docs.scala-lang.org
Supertype 2026-10-31
A type is a supertype of when is a subtype of . A lower type bound requires an admissible type argument to lie above a specified type in this subtyping relation.
Type parameter Created 2026-10-24 Updated 2026-10-31
A type parameter is a named variable ranging over admissible types in a generic definition. Instantiation substitutes a particular type argument, allowing one declaration to retain type information across uses. An upper type bound restricts the admissible arguments; a variance annotation instead describes how instantiated types participate in subtyping.
Type variance 2026-10-24
How subtyping between parameter types affects subtyping between instances of a parameterized type. A covariant type parameter preserves the direction, a contravariant type parameter reverses it, and generic type invariance permits neither conversion solely from the parameter relationship. Safe variance depends on how a type is consumed, produced and stored.
Universal typing axiom 2026-10-31
An extension of simple type assignment that assigns a distinguished atomic type variable to every lambda term in the empty typing context. The recorded assumptions therefore need not contain all free variables of the subject. This is a rule admitting a typing judgment, rather than an implicit conversion to every simple type or a general subtyping rule.
Upper type bound Created 2026-10-25 Updated 2026-10-31
An upper type bound requires the type argument assigned to to be a subtype of . Two independently bounded parameters can retain different precise component types while sharing the same upper bound. Bounds and type variance answer different questions: admissible arguments versus subtyping between instantiations.