Safety makes missing cases and invalid assumptions visible early, ideally with compiler support. It is one dimension of expressive code alongside cognitive complexity, conciseness, readability, and closeness to the problem. section 5.1 section 5.3
A readable solution with a remaining gap
Kotlin's if/else solution avoids manual casts and fall-through bugs, but it cannot guarantee exhaustive handling. Adding a new Book subtype leaves the final else branch responsible for throwing an exception, so safety is useful but incomplete. section 5.1.3
Listing 5.3 Selecting a promotional message with Kotlin if/else
fun getPromotionalMessage(book: Book): String {
return if (book is DigitalBook) {
book.downloadLink
} else if (book is PocketBook) {
"Same book, smaller format!"
} else if (book is HardcoverBook) {
"Old-fashioned book…"
} else {
throw UnsupportedBookFormatException()
}
}
Inspect the Kotlin implementation to see how smart casts remove manual casts while the fallback exception exposes the unchecked case. section 5.1.3
Closed variants and compiler-checked cases
Haskell models Book as explicit algebraic data type variants and selects the message through pattern matching. Because the variants are closed, the compiler can verify that every case is handled, making the domain structure and its safety guarantee visible together. section 5.1.5
Listing 5.5 Selecting a promotional message with Haskell pattern matching
data Book = DigitalBook String String | PocketBook String | HardcoverBook String deriving (Show, Eq) getPromotionalMessage :: Book -> String getPromotionalMessage (DigitalBook _ downloadLink) = downloadLink getPromotionalMessage (PocketBook _) = "Same book, smaller format!" getPromotionalMessage (HardcoverBook _) = "Old-fashioned book..."
Inspect the Haskell implementation to see how algebraic data types and pattern matching align the function with the complete domain model. section 5.1.5







