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

Extend example code for "lexical closures" on 'Functions' page #5735

Open
1 task
dtonhofer opened this issue Apr 19, 2024 · 1 comment
Open
1 task

Extend example code for "lexical closures" on 'Functions' page #5735

dtonhofer opened this issue Apr 19, 2024 · 1 comment
Labels
a.language Relates to the Dart language tour e2-days Can complete in < 5 days of normal, not dedicated, work from.page-issue Reported in a reader-filed concern p2-medium Necessary but not urgent concern. Resolve when possible. st.triage.ltw Indicates Lead Tech Writer has triaged

Comments

@dtonhofer
Copy link

Page URL

https://dart.dev/language/functions/

Page source

https://github.com/dart-lang/site-www/tree/main/src/content/language/functions.md

Describe the problem

At

https://dart.dev/language/functions#lexical-closures

we are shown a code snippet showing a higher-order function creating an "adder function".

I feel as if the typing part is getting a bit of a short shrift as the higher-order function makeAdder() has return type Function, which is a bit general. (I discovered it can be modified to return dynamic with good results, or else to return Object but then I can't do anything with the object, but that's just by the by).

Suggesting to show an alternate makeAdder2() which further specifies what is being returned. Like this:

/// Higher-order function that returns a function that adds [addBy] 
/// to the function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

/// Higher-order function that returns a function that adds [addBy] 
/// to the function's argument.
/// This one declares what is being returned in detail!
/// We also show two ways of definition the function that is returned.
int Function(int) makeAdder2(int addBy) {
  // var f = (int i) => (addBy + i);  // Ok, and you can reassign f.
  f(int i) => (addBy + i); // Preferred by linter; f is not re-assignable.
  print("The type of the returned function is ${f.runtimeType}");
  return f;
}

void main() {
  {
    // Create a function that adds 2.
    final add2 = makeAdder(2);

    // Create a function that adds 4.
    final add4 = makeAdder(4);

    assert(add2(3) == 5);
    assert(add4(3) == 7);
  }
  // Alternatively, call the returned function directly.
  assert(makeAdder(2)(3) == 5);
  assert(makeAdder(4)(3) == 7);
  // This prints "The type of the function to be returned is (int) => int".
  // Note that (int) => int cannot, however, be used to declare the return type
  // of the function,
  assert(makeAdder2(2)(3) == 5);
}

Expected fix

No response

Additional context

No response

I would like to fix this problem.

  • I will try and fix this problem on dart.dev.
@dtonhofer dtonhofer added the from.page-issue Reported in a reader-filed concern label Apr 19, 2024
@dtonhofer
Copy link
Author

More generally, there might be room for a section on "how to declare parameters that are typed as functions", as it is not immediately clear how to do that. Here is some example code:

// A procedure taking a function taking no arguments and returning anything

void bar1(dynamic Function() body) {
  print('bar1() called with ${body.runtimeType}');
  body();
}

// The same as above but with deprecated syntax. The linter will warn!

void bar2(dynamic body()) {
  print('bar2() called with ${body.runtimeType}');
  body();
}

// This also works: instead of "dynamic" return a nullable Object.
// However, in the case of "dynamic" the compiler will do type inference,
// but if you return "Object?" you actually fix the type of the returned 
// value.

void bar3(Object? Function() body) {
  print('bar3() called with ${body.runtimeType}');
  body();
}

void callingBar() {
  bar1(() => 6); // () => int
  bar2(() => 6); // () => int
  bar3(() => 6); // () => int
  bar1(() => null); // () => Null (null is the single inhabitant of type Null)
  bar1(() {}); // () => Null (even if the body does not return anything)
  bar1(() {
    return;
  }); // () => Null (even if the body does not return anything)
}

// ---------------

// A procedure taking a function taking no arguments and returning void.

void foo1(void Function() body) {
  print('foo1() called with ${body.runtimeType}');
  body();
}

// The same as above but with deprecated syntax. The linter will warn.

void foo2(void body()) {
  print('foo2() called with ${body.runtimeType}');
  body();
}

void callingFoo() {
  foo1(() => 6); // () => void
  foo2(() => 6); // () => void
  foo1(() {}); // () => void
  foo1(() {
    return;
  }); // () => void
}

// linter warns: "don't return null from a function returning void"

void callingFooDubiously() {
  foo1(() => null); // () => void
}

// ---------------

// A procedure taking a function taking no arguments and returning bool.

void baz1(bool Function() body) {
  print('baz1() called with ${body.runtimeType}');
  body();
}

// The same as above but with deprecated syntax. The linter will warn.

void baz2(bool body()) {
  print('baz2() called with ${body.runtimeType}');
  body();
}

void callingBaz() {
  baz1(() => true); // () => bool
  baz2(() => true); // () => bool
  baz1(() {
    return true;
  }); // () => bool
}

// ---------------

// A procedure taking a function taking an int and returning bool.

void quux1(bool Function(int x) body) {
  print('quux1() called with ${body.runtimeType}');
  body(12);
}

// Same as above

void quux2(bool Function(int) body) {
  print('quux2() called with ${body.runtimeType}');
  body(12);
}

// The same as above but with deprecated syntax. The linter will warn.

void quux3(bool body(int x)) {
  print('quux3() called with ${body.runtimeType}');
  body(12);
}

// Not the same as above!! The token 'int' is not interpreted
// as a type: "body" takes a "dynamic", not an "int"!

void quux4(bool body(int)) {
  print('quux4() called with ${body.runtimeType}');
  body(12);
}

void callingQuux() {
  quux1((x) => true); // (int) => bool
  quux1((int x) => true); // (int) => bool
  quux1((int x) {
    // (int) => bool
    return true;
  });
  quux2((x) => true); // (int) => bool
  quux2((int x) => true); // (int) => bool
  quux3((x) => true); // (int) => bool
  quux3((int x) => true); // (int) => bool
  quux4((x) => true); // (dynamic) => bool (UNEXPECTED!!)
  // quux4((int x) => true); // Does not compile
}

// ---------------

// A procedure taking a function taking an int and a named String
// and returning bool.

void alf1(bool Function(int, {String z}) body) {
  print('alf1() called with ${body.runtimeType}');
  body(12, z: "cat");
}

// The same as above but with deprecated syntax. The linter will warn.

void alf2(bool body(int i, {String z})) {
  print('alf2() called with ${body.runtimeType}');
  body(12, z: "cat");
}

// Not the same as above! The token 'int' is not interpreted
// as a type: "body" takes a "dynamic" and a name String.

void alf3(bool body(int, {String z})) {
  print('alf3() called with ${body.runtimeType}');
  body("hello", z: "cat");
}

void callingAlf() {
  alf1((x, {z = "hello"}) => true); // (int, {String z}) => bool
  alf2((x, {z = "hello"}) => true); // (int, {String z}) => bool
  alf3((x, {z = "hello"}) => true); // (dynamic, {String z}) => bool
}

// ---------------

// comparisons

void moo(bool Function(int x, {String z}) body1,
    bool Function(int x, {String z}) body2) {
  print('moo() called with ${body1.runtimeType} and ${body2.runtimeType}');
  print('Are the types equal? ${body1.runtimeType == body2.runtimeType}');
}

void callingMoo() {
  moo((x, {z = "hello"}) => true, (x, {z = "world"}) => false);
}

void woo(bool Function(int x, {String z}) body1,
    bool Function(double x, {String z}) body2) {
  print('woo() called with ${body1.runtimeType} and ${body2.runtimeType}');
  print('Are the types equal? ${body1.runtimeType == body2.runtimeType}');
}

void callingWoo() {
  woo((x, {z = "hello"}) => true, (x, {z = "world"}) => false);
}


void main() {
  callingBar();
  callingFoo();
  callingFooDubiously();
  callingBaz();
  callingQuux();
  callingAlf();
  callingMoo();
  callingWoo();
  makeAdder(12);
  print('${makeAdder(12)(11)}');
}

With the output:

bar1() called with () => int
bar2() called with () => int
bar3() called with () => int
bar1() called with () => Null
bar1() called with () => Null
bar1() called with () => Null
foo1() called with () => void
foo2() called with () => void
foo1() called with () => void
foo1() called with () => void
foo1() called with () => void
baz1() called with () => bool
baz2() called with () => bool
baz1() called with () => bool
quux1() called with (int) => bool
quux1() called with (int) => bool
quux1() called with (int) => bool
quux2() called with (int) => bool
quux2() called with (int) => bool
quux3() called with (int) => bool
quux3() called with (int) => bool
quux4() called with (dynamic) => bool
alf1() called with (int, {String z}) => bool
alf2() called with (int, {String z}) => bool
alf3() called with (dynamic, {String z}) => bool
moo() called with (int, {String z}) => bool and (int, {String z}) => bool
Are the types equal? true
woo() called with (int, {String z}) => bool and (double, {String z}) => bool
Are the types equal? false

@atsansone atsansone changed the title [PAGE ISSUE]: 'Functions': Suggesting extended example code for "lexical closures" Extend example code for "lexical closures" on 'Functions' page Apr 23, 2024
@atsansone atsansone added p2-medium Necessary but not urgent concern. Resolve when possible. e2-days Can complete in < 5 days of normal, not dedicated, work st.triage.ltw Indicates Lead Tech Writer has triaged a.language Relates to the Dart language tour labels Apr 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a.language Relates to the Dart language tour e2-days Can complete in < 5 days of normal, not dedicated, work from.page-issue Reported in a reader-filed concern p2-medium Necessary but not urgent concern. Resolve when possible. st.triage.ltw Indicates Lead Tech Writer has triaged
Projects
None yet
Development

No branches or pull requests

2 participants