Useless Tips for Dart you probably never need. (Part 1)

schultek

Kilian Schulte

Posted on January 13, 2022

Useless Tips for Dart you probably never need. (Part 1)

There exist a lot of great articles highlighting the most useful tips and tricks for Flutter and Dart. They provide great value for both beginners and advanced developers. 

This article is not one of them.

This is about the useless tips, the weird quirks, the boring facts and not needed tricks. They are either so absurd you won't ever need them, or so abstruse you don't wish to need them. But they deserve some love, too.


Symbols

Symbols in Dart are a neat way to represent a name. You can create a Symbol using its constructor: var mySymbol = Symbol('someName');. They are great when normal strings are too mainstream for you.

When you want to flex in front of your coworkers, use the symbol literal. It is another way to construct Symbols without using the boring constructor. Just prefix your name with a hip hashtag (#) and you are good to go: var myFancySymbol = #someName;. You also can use the dot syntax (#some.name) or any standard operator like #+.

Since two Symbols with the same name are identical regardless of whether you used the constructor or literal syntax, they make for some fancy comparisons. So next time when you have some static String comparison, just use Symbols (or don't).

void fancySwitch(String value) {
  switch(Symbol(value)) {
    case #foo: print('Found foo');
    case #bar.baz: print('Found bar.baz');
  }
}
Enter fullscreen mode Exit fullscreen mode

Never

Wanna know another useless tip? I would Never tell you!

Never in Dart is used as a function return type that safely tells the compiler, that this function will never return. It is most notably used in the exit() function from dart:io, which will just exit the program and therefore never returns. It is great when you want to just randomly quit your application and leave the user confused. It is also very effective when you want to reduce battery usage of your app.

Now you are probably like 'Woah, how awesome is that. How can I use this in my own method?'. And fear not, I got you covered. Two great ways to use Never is to either have an infinite loop that never returns or breaks in your method, or a method that just always throws.

Never roundRoundBaby() {
  while(true) {
    print('You spin me round round baby.');
    sleep(Duration(seconds: 1));
  }
}
Never thisIsFine() {
  print('This is fine.');
  throw '🔥🔥🔥 (╯°□°)╯︵ ┻━┻ 🔥🔥🔥';
}
Enter fullscreen mode Exit fullscreen mode

Function.apply()

Do you know how you can call a function in Dart? Probably. But did you know there are more sophisticated ways you can achieve this?

And I'm not talking about the call() method, because that is actually very useful.

I'm talking about Function.apply(). Instead of boringly calling a function directly, you can use this handy helper to completely ignore any parameter constraints and backstab the compiler. It works by providing it a Function, a List of positional parameters, and a Map of named parameters:

// instead of
foo(1, 2, 3, f: 4, g: 5)
// do this
Function.apply(foo, [1, 2, 3], {#f: 4, #g: 5});
Enter fullscreen mode Exit fullscreen mode

Notice how we use symbol literals for the named parameters. How ironic.

Now when you want to make absolutely sure that your function is properly applied (or you hate your coworkers), you can pass the whole invocation to Function.apply a second time. Then it would look like this:

Function.apply(Function.apply, [foo, [1, 2, 3], {#f: 4, #g: 5}]);
Enter fullscreen mode Exit fullscreen mode

Next are two very interesting use-cases that I could come up with. They are particularly designed to solve quite common problems.

Currying (kind of)

Currying is a fancy functional programming term. With currying, instead of providing all parameters at once, we take it real slow and provide one argument after another. This gives you the time to get some coffee or reflect on your past life choices.

Say we have a simple function greet:

void greet(String name, String msg) {
  print('Hi $name, $msg');
}
Enter fullscreen mode Exit fullscreen mode

We might want to call this function multiple times with the same name, but a different message. Instead of behaving like a normal person and just calling the function with the appropriate parameters, we can write ourselves an overcomplicated currying helper function. Also let's use an extension on Function just because we can.

Since we want to partially provide parameters, we need to keep track of the already provided parameters on each curry call. We then want to repeatedly provide a single parameter in each subsequent method call and add this to our list of provided parameters. To signal the end of parameters, we do another last call with no parameters. The resulting extension looks like this:

extension CurryFunction on Function {
  // starting point, empty parameters
  Function get curry => _curry([]);
  // creates the curry function
  Function _curry(List params) {
    // returns a function with one optional parameter p
    // when there is a next parameter (p != null), add to list
    // else invoke original function using Function.apply
    return ([p]) => p != null 
      ? _curry([...params, p]) 
      : Function.apply(this, params);
  }
}
Enter fullscreen mode Exit fullscreen mode

We can now call our greet function like this:

void main() {

  // get the currying function
  var greetWho = greet.curry;

  // provide the first parameter
  // this will return another function
  var greetTom = greetWho('Tom');

  // provide the second parameter
  // and call again to terminate currying
  // prints 'Hi Tom, how are you?'
  greetTom('how are you?')();

  // provide a different second parameter
  // prints 'Hi Tom, want some curry?'
  greetTom('want some curry?')();

  // also works with more parameters
  // prints 'a-b-c-d'
  concat.curry('a')('b')('c')('d')();
}

// another function with more parameters
void concat(String p1, String p2, String p3, String p4) {
  print('$p1-$p2-$p3-$p4');
}
Enter fullscreen mode Exit fullscreen mode

Double-Braces

Now the last example was all about increasing your usage of these shiny little emoji smiles ( and ) . But what if those keys are broken on your keyboard, or you just don't like parentheses. This seems like a reasonable problem we should look into.

When we discovered Function.apply a few sections back, we had this neat package of our function and parameters we could use: [foo, [1, 2, 3], {#f: 4, #g: 5}]. A great starting point, since there are no parentheses in sight. However we still needed them to pass this information to the apply method. So we need to come up with another way to pass some information to a method, without using ( or )

We are rescued by operator overloading. Any Dart class can define custom methods that are called when we use simple operators like + or -. Another operator we can overload is [] used i.e. get or set values in a Map. We then just need to write a class that overloads the [] operator to receive a function and passes it to Function.apply:

class CallProxy {
  const CallProxy();

  // overload the [] operator to receive a function package
  // passes all data to Function.apply and returns the result
  dynamic operator [](List list) {
    return Function.apply(
      // the function
      list[0] as Function, 
      // positional parameters
      list[1] as List, 
      // named parameters, optional
      list.length == 3 ? list[2] as Map<Symbol, String> : null,
    );
  }
}

// a static instance of this class we can use later
const call = CallProxy();
Enter fullscreen mode Exit fullscreen mode

Now we can simply call any function like this:

call[[foo, [1, 2, 3], {#f: 4, #g: 5}]];
Enter fullscreen mode Exit fullscreen mode

The outer [] are from the operator overloading, and the inner [] are from the list literal.

With the new Dart 2.15 constructor tear-offs we can even call constructors with this syntax:

var bananas = call[[List.filled, [10, 'banana']]];
Enter fullscreen mode Exit fullscreen mode

Lastly, the concat currying from above would look really edgy with all those brackets:

call[[call[[call[[call[[call[[concat.curry,['a']]],['b']]],['c']]],['d']]],[]]];
Enter fullscreen mode Exit fullscreen mode

If you are paid by lines of code, this is for you!

Yes, this is actually works. Try it out here. 



Do you know any more useless or abstruse tricks? Please leave a comment.

Also I wanted to experiment with something you won't read everyday in contrast to the thousands of 'Tips & Tricks' articles out there. So please show some love in case you liked it or give some feedback.

💖 💪 🙅 🚩
schultek
Kilian Schulte

Posted on January 13, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related