Destructuring
In Java, we get at the fields of a compound data type by adding accessors or making the fields public. Some languages provide an alternative means of accessing fields via a mechanism that we have already met: destructuring via pattern matching.
Haskell
Recall that destructuring may happen in Haskell in a case
expression, in the list of formals in a function or lambda header, or in a let
or where
binding. A destructuring pattern for a tagged union names the variant to be matched and then gives names to the variant's fields in serial order.
Suppose we have a union storing a user's preferred contact method:
data ContactMethod =
Email String | -- email address
Phone String String -- cell number, work number
To see if a value of type ContactMethod
is an email address, we match it against the pattern Email address
. If the value is an email address, the field within is assigned to the local variable address
. To see if it's a phone number, we match it against the pattern Phone cell work
. If the value is a phone number, the fields within are assigned to the variables cell
and work
. The names given to the fields are entirely at the discretion of the programmer writing the pattern. The definition of ContactMethod
does not name them.
This function uses a case
expression to respond to values matching either pattern:
connect :: ContactMethod -> String
connect method =
case method of
Email address -> "Email me at " ++ address
Phone cell _ -> "Call me at " ++ cell
The work number is discarded using the _
wildcard. This alternative definition uses pattern matching in the formal parameters:
connect :: ContactMethod -> String
connect (Email address) = "Email me at " ++ address
connect (Phone cell _) = "Call me at " ++ cell
Parentheses are needed to separate the pattern from the surrounding tokens.
Java
Java 17 has pattern matching that looks a bit like Haskell's. We can use it to write conditional logic that walks through a sequence of type checks to decide what action to take or what value to produce based on the type of the value. Here pattern matching is used in a switch
expression to turn an arbitrary object into a Double
:
double x = switch (object) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0.0;
}
Java's pattern matching doesn't currently support destructuring, so we can't use it to break an object up into its fields. That may change in the future. Java has been acquiring features from other languages rather rapidly in recent versions, including lambdas, multiline string literals, type inference, and syntactic shortcuts that make it easier to pass code to higher-order functions.
JavaScript
Objects in JavaScript are destructured with a pattern that selects out the fields we wish to bind to local variables:
const date = {year: 1865, month: 6, day: 19};
const {year, month, day} = date;
We can also collect up all the unnamed fields into a “rest” object, unpack nested objects, provide default values when keys are missing, or bind values to names other than their keys. The expressive reach of JavaScript's destructuring assignments is immense.