Scala - exists, forall, contains, find
• The `exists`, `forall`, `contains`, and `find` methods provide efficient ways to query collections without manual iteration, with `exists` and `forall` short-circuiting as soon as the result is...
Key Insights
• The exists, forall, contains, and find methods provide efficient ways to query collections without manual iteration, with exists and forall short-circuiting as soon as the result is determined
• contains checks for exact element equality while exists allows custom predicates, making exists more flexible for complex matching scenarios
• find returns the first matching element wrapped in an Option, enabling safe handling of potentially missing values and supporting functional composition patterns
Understanding Collection Query Methods
Scala collections provide four fundamental methods for querying elements: exists, forall, contains, and find. These methods eliminate the need for explicit loops and provide declarative, readable code. Each serves a distinct purpose in collection interrogation.
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val empty = List.empty[Int]
val names = List("Alice", "Bob", "Charlie", "David")
exists: Testing for At Least One Match
The exists method returns true if at least one element satisfies the given predicate. It short-circuits, stopping iteration immediately upon finding a matching element.
// Check if any number is even
val hasEven = numbers.exists(n => n % 2 == 0)
// hasEven: Boolean = true
// Check if any number exceeds 100
val hasLarge = numbers.exists(_ > 100)
// hasLarge: Boolean = false
// Empty collections always return false
val emptyHasEven = empty.exists(_ % 2 == 0)
// emptyHasEven: Boolean = false
// Complex predicate with multiple conditions
val hasEvenGreaterThanFive = numbers.exists(n => n % 2 == 0 && n > 5)
// hasEvenGreaterThanFive: Boolean = true (6, 8, 10 match)
Real-world example with case classes:
case class User(id: Int, name: String, age: Int, active: Boolean)
val users = List(
User(1, "Alice", 28, true),
User(2, "Bob", 35, false),
User(3, "Charlie", 42, true)
)
// Check if any active user is over 40
val hasActiveOver40 = users.exists(u => u.active && u.age > 40)
// hasActiveOver40: Boolean = true (Charlie matches)
// Check if any inactive user exists
val hasInactive = users.exists(!_.active)
// hasInactive: Boolean = true
forall: Universal Quantification
The forall method returns true only if all elements satisfy the predicate. Like exists, it short-circuits but stops when finding the first non-matching element.
// Check if all numbers are positive
val allPositive = numbers.forall(_ > 0)
// allPositive: Boolean = true
// Check if all numbers are even
val allEven = numbers.forall(_ % 2 == 0)
// allEven: Boolean = false (stops at 1)
// Empty collections always return true (vacuous truth)
val emptyAllEven = empty.forall(_ % 2 == 0)
// emptyAllEven: Boolean = true
The vacuous truth property of forall on empty collections is mathematically sound but can be counterintuitive:
val emptyList = List.empty[Int]
// All of these return true for empty collections
emptyList.forall(_ > 1000) // true
emptyList.forall(_ < 0) // true
emptyList.forall(_ => false) // true
Practical validation example:
case class Order(id: Int, amount: Double, isPaid: Boolean)
val orders = List(
Order(1, 100.0, true),
Order(2, 250.0, true),
Order(3, 75.0, true)
)
// Validate all orders are paid
val allPaid = orders.forall(_.isPaid)
// allPaid: Boolean = true
// Validate all orders meet minimum amount
val allMeetMinimum = orders.forall(_.amount >= 50.0)
// allMeetMinimum: Boolean = true
// Combined validation
def validateOrders(orders: List[Order]): Boolean = {
orders.forall(o => o.isPaid && o.amount > 0)
}
contains: Direct Element Equality
The contains method checks if a collection contains a specific element using equality (==). It’s simpler than exists but less flexible.
val hasThree = numbers.contains(3)
// hasThree: Boolean = true
val hasTwenty = numbers.contains(20)
// hasTwenty: Boolean = false
val hasAlice = names.contains("Alice")
// hasAlice: Boolean = true
val hasBob = names.contains("bob") // Case-sensitive
// hasBob: Boolean = false
With custom objects, contains uses the equals method:
case class Point(x: Int, y: Int)
val points = List(Point(0, 0), Point(1, 1), Point(2, 2))
val hasOrigin = points.contains(Point(0, 0))
// hasOrigin: Boolean = true
// Case classes generate equals automatically
val hasPoint = points.contains(Point(1, 1))
// hasPoint: Boolean = true
When to use contains vs exists:
// Use contains for exact matches
val hasSpecificValue = numbers.contains(5)
// Use exists for conditional logic
val hasEvenValue = numbers.exists(_ % 2 == 0)
// contains cannot handle predicates
// This won't compile: numbers.contains(_ > 5)
// exists is more flexible
val hasValueGreaterThanFive = numbers.exists(_ > 5)
find: Retrieving the First Match
The find method returns an Option containing the first element that matches the predicate. It returns None if no match is found.
// Find first even number
val firstEven = numbers.find(_ % 2 == 0)
// firstEven: Option[Int] = Some(2)
// Find first number greater than 100
val firstLarge = numbers.find(_ > 100)
// firstLarge: Option[Int] = None
// Pattern matching on result
numbers.find(_ > 5) match {
case Some(n) => println(s"Found: $n")
case None => println("Not found")
}
// Output: Found: 6
Working with Option results:
// Using getOrElse for default values
val firstEvenOrZero = numbers.find(_ % 2 == 0).getOrElse(0)
// firstEvenOrZero: Int = 2
// Chaining operations with map
val doubled = numbers.find(_ > 7).map(_ * 2)
// doubled: Option[Int] = Some(16)
// Using filter on Option
val evenAndLarge = numbers.find(_ > 5).filter(_ % 2 == 0)
// evenAndLarge: Option[Int] = Some(6)
Practical example with user lookup:
case class User(id: Int, email: String, verified: Boolean)
val users = List(
User(1, "alice@example.com", true),
User(2, "bob@example.com", false),
User(3, "charlie@example.com", true)
)
// Find first verified user
val firstVerified = users.find(_.verified)
// firstVerified: Option[User] = Some(User(1, "alice@example.com", true))
// Find user by email
def findByEmail(email: String): Option[User] = {
users.find(_.email == email)
}
// Safe extraction with for-comprehension
val userEmail = for {
user <- users.find(_.id == 2)
if user.verified
} yield user.email
// userEmail: Option[String] = None (Bob is not verified)
Performance Characteristics
All four methods short-circuit appropriately:
val largeList = (1 to 1000000).toList
// exists stops at first match (element 2)
val hasEven = largeList.exists(_ % 2 == 0)
// forall stops at first non-match (element 2)
val allOdd = largeList.forall(_ % 2 != 0)
// find stops at first match
val firstHundred = largeList.find(_ == 100)
// contains stops when element is found
val hasValue = largeList.contains(500)
Combining Query Methods
These methods compose well for complex queries:
case class Product(name: String, price: Double, inStock: Boolean)
val products = List(
Product("Laptop", 999.99, true),
Product("Mouse", 29.99, true),
Product("Keyboard", 79.99, false),
Product("Monitor", 299.99, true)
)
// Check if all in-stock items are affordable
val affordableStock = products
.filter(_.inStock)
.forall(_.price < 1000)
// affordableStock: Boolean = true
// Find cheapest in-stock item
val cheapestInStock = products
.filter(_.inStock)
.find(p => products.filter(_.inStock).forall(_.price >= p.price))
// Better approach:
val cheapest = products.filter(_.inStock).minByOption(_.price)
These four methods form the foundation of declarative collection querying in Scala. Choose exists for existential checks, forall for universal validation, contains for simple equality tests, and find when you need the actual matching element.