Functional-ish: How Dart 3.7 Makes Writing Safer, More Declarative Code Easier Than Ever

If you’ve ever opened an FP tutorial and closed it five minutes later when you hit the word monad, you’re not alone.

Functional-ish: How Dart 3.7 Makes Writing Safer, More Declarative Code Easier Than Ever
Image created by author

If you’ve ever opened an FP tutorial and closed it five minutes later when you hit the word monad, you’re not alone.

Functional programming often feels like a faraway land, beautiful, mathematical, and honestly, kind of intimidating.

But here’s the twist: Dart 3.7 quietly embraces functional patterns, and it does so in a way that’s both approachable and powerful, especially if you value clean, predictable code.

Let’s look at how Dart 3.7 helps you write more declarative, safer code, without converting to full-on Haskell.

Pattern Matching + Exhaustiveness = Safety

Dart 3.7 introduces enhanced pattern matching through switch expressions, destructuring, and when clauses. Paired with sealed classes, this unlocks compiler-verified logic, a core FP idea.

sealed class Result {} 
 
class Success extends Result { 
  final String data; 
  Success(this.data); 
} 
 
class Failure extends Result { 
  final String message; 
  Failure(this.message); 
} 
 
String handle(Result result) => switch (result) { 
  Success(:var data) => 'Got: $data', 
  Failure(:var message) => 'Error: $message', 
};

No default branch. No fall-through. If you forget a case, the compiler tells you.

Record Destructuring = Lightweight Tuples

Records make it easy to group data without writing classes. Now with destructuring, you can write expressive, tuple-like code:

(String name, int age) = getUser();

You’re thinking in values, not objects. A very functional vibe!

Pattern Guards: Smarter Matching

Pattern guards let you match and filter in one move. Great for keeping logic readable and declarative.

switch (user) { 
  case User(:var name, :var age) when age > 18: 
    print('$name is an adult'); 
  case User(:var name, :_): 
    print('$name is not an adult'); 
}

No if blocks, no nesting. Just crisp, composable conditions.

Sealed Classes: Exhaustiveness FTW

Sealed classes define a fixed set of subclasses. The compiler enforces that all possibilities are handled. A classic FP strategy for safe, complete logic.

sealed class PaymentStatus {} 
 
class Paid extends PaymentStatus {} 
class Failed extends PaymentStatus {} 
class Pending extends PaymentStatus {}

With sealed types + switch, Dart ensures that every state is accounted for. Less guessing, more confidence.

You’re Already Using FP-Like Collections

Dart’s map, where, fold, and friends have always supported functional patterns:

final names = ['Ann', 'Ben', 'Clara']; 
 
final filtered = names 
    .where((n) => n.startsWith('B')) 
    .map((n) => n.toUpperCase());

Chaining pure functions = fewer side effects, more composable logic.

Functional Patterns in Dart 3.7 (At a Glance)

A table depicting dart features that address functional concepts.

Real-World Example: Declarative Validation

Here’s a small but expressive example: email validation with a sealed result type.

sealed class ValidationResult {} 
 
class Valid extends ValidationResult {} 
 
class Invalid extends ValidationResult { 
  final String reason; 
  Invalid(this.reason); 
} 
 
ValidationResult validateEmail(String email) { 
  if (!email.contains('@')) { 
    return Invalid('Missing "@"'); 
  } 
  return Valid(); 
} 
 
void handleValidation(String email) { 
  final result = validateEmail(email); 
 
  switch (result) { 
    case Valid(): 
      print('Email is valid!'); 
    case Invalid(:var reason): 
      print('Invalid email: $reason'); 
  } 
}

No nulls. No loose booleans. Just clear, enforced states.


Final Thoughts

You don’t need to be a functional programming purist to enjoy the benefits of safety, predictability, and declarative code.

With Dart 3.7, you can:

  • Use pattern matching with exhaustiveness checks
  • Lean into immutability
  • Write expressive, branch-safe logic
  • Build sealed models with guaranteed coverage

You’re already halfway there — Dart just gave you the rest of the map.