Home > front end >  Adding a void callback with a generic parameter to a function in Dart
Adding a void callback with a generic parameter to a function in Dart

Time:09-17

What I'm trying to do

Given the following Node:

class Node<T> {
  Node(this.value);
  T value;
  Node? child;

  // TODO: add `visit` method

  @override
  String toString() => value.toString();
}

I'd like to add a visit method that will perform some action on the value of each node and its child recursively. Then I could do something like this:

void main() {
  final root = Node(1);
  root.child = Node(2);
  root.child!.child = Node(3);

  // one of these
  root.visit(print);
  root.visit((value) => print(value));

  // 1
  // 2
  // 3
}

Naive solution

If I do the following, it works:

void visit(Function action) {
  action(value);
  child?.visit(action);
}

Problems with the naive solution

However, the value in this statement is inferred to be dynamic:

root.visit((value) => print(value));

I'd like to infer it to be the same type as the Node's generic T type.

Additionally, the compiler allows the following, which causes a runtime crash:

root.visit(() => 42);

I'd like that to be a compile-time error.

Attempted solution 1

If I change visit to the following:

void visit(Function(T value) action) {
  action(value);
  child?.visit(action(value));
}

Everything looks good at compiletime:

root.visit(print);                    // OK
root.visit((value) => print(value));  // OK
root.visit(() => 42);                 // error

But if I comment out that last one and run the code on either of the first two then I'll get the following runtime error:

Unhandled exception:
type 'Null' is not a subtype of type '(dynamic) => dynamic'

I'm not exactly sure what that means.

Attempted solution 2

Added void:

void visit(void Function(T value) action) {
  action(value);
  child?.visit(action(value)); // error
}

This expression has a type of 'void' so its value can't be used. Try checking to see if you're using the correct API; there might be a function or call that returns void you didn't expect. Also check type parameters and variables which might also be void. (dartuse_of_void_result)

Attempted solution 3

This one was just a stab in the dark:

void visit(void Function<T>(T value) action) {
  action(value);
  child?.visit(action); 
}

The visit method seems to compile but calling it as before gives compile time errors:

root.visit(print);                    // error
root.visit((value) => print(value));  // error

The errors read:

The argument type 'void Function(Object?)' can't be assigned to the parameter type 'void Function(T)'. (dartargument_type_not_assignable)

Related questions

These questions seem related but I couldn't figure out how to extract a solution from them:

How can I solve the problem?

CodePudding user response:

Thank you to @jamesdlin in the comments for solving the problem.

You need to set the generic type for the child as Node<T>. Then you can specify the method signature as void visit(Function(T value) action) and pass action itself on to the child.

Here is the full example:

void main() {
  final root = Node(1);
  root.child = Node(2);
  root.child!.child = Node(3);

  // one of these
  root.visit(print);
  root.visit((value) => print(value)); // value inferred as int
  // root.visit(() => 42); // compile-time error

  // 1
  // 2
  // 3
}

class Node<T> {
  Node(this.value);
  T value;
  Node<T>? child;

  void visit(Function(T value) action) {
    action(value);
    child?.visit(action);
  }

  @override
  String toString() => value.toString();
}
  • Related