Dart is a versatile programming language and has introduced new features that enhance code expressiveness and development productivity. Among these features, pattern matching and destructuring stand out as powerful tools. In this article, we'll delve into the world of patterns in Dart, exploring their various use cases and showcasing real-world examples.
Patterns in Dart: Matching Values and Destructuring Objects
Patterns are a syntactic category in Dart, representing the shape of a set of values that can be matched against actual values. They serve two main purposes: pattern matching and pattern destructuring.
Pattern Matching
Pattern matching enables developers to test whether a value meets specific criteria, such as having a particular shape, being a constant, equaling something else, or having a specific type. Let's look at some real-world examples:
Matching a Shape:
import 'dart:math'; class Rectangle { final double width; final double height; Rectangle({required this.width, required this.height}); } class Hexagon { final double sideLength; Hexagon({required this.sideLength}); } void calculateArea(dynamic shape) { switch(shape) { case Rectangle(width: double w, height: double h): final area = w * h; print('Area: $area'); break; case Hexagon(sideLength: double l): final area = (3 * sqrt(3) * pow(l, 2)) / 2; print('Area: $area'); break; } } void main() { calculateArea(Rectangle(width: 5, height: 10)); // Area: 50 calculateArea(Hexagon(sideLength: 6.9)); // Area: 123.6944084225314 }
In this example, we use pattern matching to check if the
shape
parameter is an instance of theRectangle
orHexagon
class, allowing us to calculate its area. If we'd tried this in Dart before v3.0, we'd get the error: "Case expressions must be constant", and "The argument type 'Type' can't be assigned to the parameter type 'double'."Matching a Constant:
const String example = 'example.com'; const String gmail = 'gmail.com'; void validateEmail(String email) { final pattern = RegExp(r'^[\w-]+@([\w-]+\.)+[\w-]+$'); final match = pattern.firstMatch(email); if (match != null) { final domain = match.group(1); switch (domain) { case example: print('Valid email from example.com'); break; case gmail: print('Valid email from gmail.com'); break; default: print('Valid email from another domain'); } } else { print('Invalid email format'); } } void main() { validateEmail('john.doe@example.com'); // Output: Valid email from example.com validateEmail('jane.smith@gmail.com'); // Output: Valid email from gmail.com validateEmail('foo@bar.com'); // Output: Valid email from another domain validateEmail('invalid_email'); // Output: Invalid email format }
In this example, we're validating email addresses by matching the domain part of the email against different patterns. Depending on the domain, we print different messages indicating the validity of the email.
Pattern Destructuring
Pattern destructuring provides a convenient way to break down an object into its constituent parts, extracting values for further use. Let's explore some practical scenarios:
Destructuring a List:
void processCoordinates(List<double> coordinates) { var [x, y, z] = coordinates; print('x: $x, y: $y, z: $z'); } void main() { processCoordinates([10.5, 20.3, 5.1]); // Output: x: 10.5, y: 20.3, z: 5.1 }
In this example, we destructure a list of coordinates into individual variables, enabling easy access and manipulation.
Destructuring a Map Entry:
void processUser(MapEntry<String, int> userEntry) { var {key: username, value: age} = userEntry; print('Username: $username, Age: $age'); } void main() { var user = MapEntry('JohnDoe', 25); processUser(user); // Output: Username: JohnDoe, Age: 25 }
Here, we destructure a
MapEntry
object into separate variables representing the key and value. This allows us to access and utilize the user's username and age individually.
Common Use Cases for Patterns in Dart
Simplifying Data Validation: Patterns prove valuable when validating complex data structures like JSON. They allow us to check if the structure conforms to our expectations and extract specific values effortlessly.
Enhancing Switch Statements: Switch statements become more powerful with pattern matching, enabling multiple cases to share a body or matching against various patterns to execute specific code blocks based on input conditions.
Efficient Variable Assignment: Patterns provide a concise and expressive way to declare and assign variables simultaneously, making code more readable and reducing redundancy.
What have we learned?
With pattern matching and destructuring, Dart empowers developers to write more expressive and concise code. Whether you're validating data, manipulating objects, or enhancing control flow, patterns offer a powerful and flexible toolset. By leveraging these features, you can improve your Dart programming skills and boost productivity in real-world scenarios.
Other information
I used another free credit at STOCKIMG.AI to generate a horizontal poster using disco diffusion for the article poster image. This time I used the image from my first post as the primer, with some words about pattern matching and destructuring. The image above is the generated result. This time I was expecting something similar and the result is interesting. Given the two article topics are similar, I thought I’d go ahead and use it.