Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offering my switchCase Extension #80

Open
Skquark opened this issue May 9, 2020 · 2 comments
Open

Offering my switchCase Extension #80

Skquark opened this issue May 9, 2020 · 2 comments

Comments

@Skquark
Copy link

Skquark commented May 9, 2020

I just adapted a little utility I've been using to work as extension, and I think it'd fit in well with your library. It does the equivalent of switch(string) { case "txt1": etc, only cleaner and easier inside a widget structure, or wherever you'd use a standard switch case break default statement.. Here it is:

extension switchString on String {
  /// Performs a switch-case statement on String using a map for cases & conditions, optional default value 
  /// ```dart
  /// child: roomType.switchCase({
  ///   "public": Text("It's public"),
  ///   "private": Text("It's private"),
  ///   "custom": () { ... },
  /// }, Text("Default type")),
  /// ```
  TValue switchCase<String, TValue>(Map<String, TValue> cases, [TValue defaultValue]) {
    if (cases.containsKey(this)) return cases[this];
    return defaultValue;
  }
}

extension switchInt on int {
  /// Performs a switch-case statement on int using a map for cases & conditions, optional default value 
  /// ```dart
  /// child: typeNumber.switchCase({
  ///   1: Text("It's one"),
  ///   2: () { ... },
  ///   3: Text("It's three"),
  /// }, Text("Other number")),
  /// ```
  TValue switchCase<int, TValue>(Map<int, TValue> cases, [TValue defaultValue]) {
    if (cases.containsKey(this)) return cases[this];
    return defaultValue;
  }
}

Simple and fairly elegant code with the optional default: parameter, I could give you the original generic TOptionType version as well (I didn't write it), but it's nicer as extension on String and int and maybe another type too. Feel free to add it in, or if you prefer, I can add it in myself with a PR with doc updates. I'm just loving the flexibility of Dart now, so many new toys to play with...

@passsy
Copy link
Collaborator

passsy commented Jul 9, 2020

I found myself using this a few times. Although I think pattern matching or at least a switch case with return type will land in Dart in the next 2 years, this can work as a neat temporary solution.

I'd like to hear your opinion on a slightly different API. When I use this pattern I use Functions as my cases. The trailing braces () in your API don't look nice. I'm also not 100% satisfied with the default argument. I wish it would be a named argument but as default is a reserved word, that's kind of impossible.

Original

  • dartfmt with ?? is strange
  • When using functions - to prevent unnecessary constructor calls - on has to add () to the end
  • with defaultValue param better formatted. But using ?? has the same effect with less allocations
  • defaultValue isn't named, hard to understand
// Original Proposal
_counter.switchCase({
      1: Text("It's one"),
      2: Text("It's two"),
      3: Text("It's three"),
    }) ??
    Text("Other number"),

// Original Proposal with default
_counter.switchCase(
  {
    1: Text("It's one"),
    2: Text("It's two"),
    3: Text("It's three"),
  },
  Text("Other number"),
),

// Original Proposal with functions
_counter.switchCase(
  {
    1: () => Text("It's one"),
    2: () => Text("It's two"),
    3: () => Text("It's three"),
  },
  () => Text("Other number"),
)(),

Proposal 2

  • forces users to use functions
  • no trailing ()
  • doesn't improve ?? dartformat handling
  • fallback case is clear
// Proposal 2 - forced functions
_counter.switchCase2({
      1: () => Text("It's one"),
      2: () => Text("It's two"),
      3: () => Text("It's three"),
    }) ??
    Text("Other number"),

// Proposal 2 with default
_counter.switchCase2(
  {
    1: () => Text("It's one"),
    2: () => Text("It's two"),
    3: () => Text("It's three"),
  },
  fallback: () => Text("Other number"),
),

Proposal 3

  • dense code with defaultTo
  • match() is boilerplate compared to Original solution
  • I have to admit that I like the closing match() for some reason. Maybe because of Pascals begin/end? 🤷
  • Doesn't solve the ?? formatting, but users don't have to use it.
// Proposal 3 - match or error
_counter.switchCase3({
  0: () => Text("zero"),
  1: () => Text("It's one"),
  2: () => Text("It's two"),
  3: () => Text("It's three"),
}).match(),

// Proposal 3 - no default, but null (dartfmt 😖)
_counter.switchCase3({
      1: () => Text("It's one"),
      2: () => Text("It's two"),
      3: () => Text("It's three"),
    }).matchOrNull() ??
    Text("Other number"),

// Proposal 3 with explicit default
_counter.switchCase3({
  1: () => Text("It's one"),
  2: () => Text("It's two"),
  3: () => Text("It's three"),
}).defaultTo(() => Text("Other number")),

Implementation

Not that interesting, and very likely to change.

extension switchInt on int {
  T switchCase<int, T>(Map<int, T> cases, [T defaultValue]) {
    if (cases.containsKey(this)) return cases[this];
    return defaultValue;
  }

  T switchCase2<int, T>(Map<int, T Function()> cases, {T Function() fallback}) {
    if (cases.containsKey(this)) return cases[this]();
    if (fallback == null) return null;
    return fallback();
  }

  SwitchCase<T> switchCase3<int, T>(Map<int, T Function()> cases) {
    if (cases.containsKey(this)) return SwitchCase.match(cases[this]());
    return SwitchCase.noMatch();
  }
}

class SwitchCase<T> {
  SwitchCase.match(this.value) : matched = true;
  SwitchCase.noMatch()
      : matched = false,
        value = null;

  final T value;
  final bool matched;

  T match() {
    if (matched) return value;
    throw "no default argument";
  }

  T matchOrNull() {
    if (matched) return value;
    return null;
  }

  T defaultTo(T Function() fallback) {
    if (!matched) return fallback();
    return value;
  }
}

Does someone have more ideas?

@Skquark
Copy link
Author

Skquark commented Jul 23, 2020

Hmm, I like some of your implementations, but not sure if those proposals really improve it's ease of use over the original. The goal would be as little typing as possible, without extra fields needed, and adding the additional syntax for the optional defaultValue seems to complicate it visually. I agree about there being another way to do the switch case more integrated into the Dart language in the way that the if's are like isTrue ? true : false with an inline method and some creative syntax (I've tried to visualize the best format of that for years), but until then this is a nice simple shortcut method.
You could include more than one implementation method so users can pick which switch they prefer for their case. If I had to choose, I liked Proposal 2 with default Maybe there's another simple option too, I'm thinking about it but your call what goes in your package..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants