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

Constant function literals #1048

Open
lrhn opened this issue Aug 20, 2012 · 67 comments
Open

Constant function literals #1048

lrhn opened this issue Aug 20, 2012 · 67 comments
Labels
enhanced-const Requests or proposals about enhanced constant expressions feature Proposed language feature that solves one or more problems

Comments

@lrhn
Copy link
Member

lrhn commented Aug 20, 2012

Static functions are compile time constants, but function literals are not.
It would be great if it was possible to write compile time constant function literals. They would necessarily have static scope (no access to "this") even if written inside a class declaration.

Constant function literals would create only one function object per function expression (no unification of different function expressions, even if they happen to have the same body).

This is particularly useful for default function parameters.

I suggest const <functionLiteral> as syntax. That would allow you to write, e.g.,:

class Map<K,V> {
  V getIfPresent(K key, [V onNotPresent() = const () => null]);
}

The current alternative is to declare the function as static:

class Map<K,V> {
  V getIfPresent(K key, [V onNotPresent() = _returnNull]);
  static _returnNull() => null;
}

You can use it to ensure that you don't create too many closures if you want to create a function inside a loop:

for (var x in xs) {
  x.somethingOrError(const (x) => throw new Failure(x));
}

which would otherwise create a new closure per iteration of the loop. The alternative is again to define a static function somewhere and use that, but it moves the logic away from where it's most readable.

The syntax shouldn't collide with any current use of "const", since we currently don't allow that syntax in expression position. It does collide with a constant constructor declaration, but the uses are different enough that I don't think it's a problem. If we want parity, we could allow const foo() => xx as a top-level declaration too, meaning the same as static, but that would make static redundant.

@DartBot
Copy link

DartBot commented Aug 21, 2012

This comment was originally written by @seaneagan


That syntax could potentially be useful for something else: declaring a function's return value to be constant (at least when it receives constants as input).

const lengthSquared(dx, dy) => dx*dx + dy*dy;

The calls would need to be marked with "const" as well, just as with "const" constructor calls:

var ls = const lengthSquared(1, 1);

For example, most of the methods in dart:math could use this.

@gbracha
Copy link

gbracha commented Nov 6, 2012

Added this to the Later milestone.
Added Accepted label.

@jtmcdole
Copy link

jtmcdole commented Dec 6, 2013

Hit this today as well and it would be a nice feature. Figured its been > 12 months though and little chance in the near future.

@DartBot
Copy link

DartBot commented Apr 30, 2014

This comment was originally written by pi...@gmail.com


Big +1 for this to happen.

It would give a lot of new power to annotations, e.g.:
  class MyVO {
    @­Validator((value) => value is String && value.length > 0)
    String name;
  }

And why not something like this too:
  class MyVO {
    @­() => value is String && value.length > 0;
    String name;
  }
(Ok, this goes too far)

This doesn't seem quite straight-forward to implement though.
Constant function literals could not perform "variable capture".
I guess it brings complications to the parser and I'm sure there are other implications.

This would be big nonetheless (IMO).

@kasperl
Copy link

kasperl commented Jul 10, 2014

Removed this from the Later milestone.
Added Oldschool-Milestone-Later label.

@kasperl
Copy link

kasperl commented Aug 4, 2014

Removed Oldschool-Milestone-Later label.

@DartBot
Copy link

DartBot commented Mar 1, 2015

This comment was originally written by steffen.haup...@gmail.com


Just found out that not even the [length] of a const String literal is a constant. I would really appreciate this to be changed. It would allow to move String based list initializations into fixed length lists. That in case would allow for more optimized code than variable length lists.

@DartBot
Copy link

DartBot commented Mar 2, 2015

This comment was originally written by @seaneagan


Since function literals are anonymous, the only way to expose a constant one would be via a separate constant declaration of some kind. So there should be no way to "accidentally expose" a constant function literal. The fact an expression uses a an anyonymous function literal versus a constant function declaration is just an implementation detail. So why should the "const " prefix be required when it can just be inferred?

@DartBot
Copy link

DartBot commented Mar 3, 2015

This comment was originally written by @Emasoft


I agree with seaneagan here. There is no need for this. All methods should get this optimization automatically from the compiler if the user passes a constant as a parameter. This is a non issue for the language, but only a compiler optimization. The compiler should automatically infer when to convert a function literal to a constant. The C# compiler does this very well, for example. Usually I don't need to use the const keyword at all.

@DartBot
Copy link

DartBot commented Mar 3, 2015

This comment was originally written by @seaneagan


@Fmaud: It is useful to mark declarations as "const" to avoid someone referencing the declaration in a const-only context, and then you wanting to change the declaration to something non-const and breaking them.

But function literals are just expressions, so there is no danger of that.

@DartBot
Copy link

DartBot commented Mar 3, 2015

This comment was originally written by @seaneagan


I guess the only strange thing about auto-determining the constness would be that canonicalization would be dependent on whether a function literal happened to be a constant. I can't think of any obvious negative consequences of that, but nonetheless I imagine it would need to be considered a breaking change and wait for dart 2.0.

@isaias-b
Copy link

On stackoverflow.com apeared a question concerning this issue. I just wanted to place a callback, so that the question's answers can be updated when this is done.
CYA

@stijnvanbael
Copy link

+1 on this feature, would be great for adding behaviour to metadata

@zoechi
Copy link

zoechi commented Jul 1, 2016

@stijnvanbael you can reference functions in metadata (top-level functions and static methods) , you can just not define them inline

@stijnvanbael
Copy link

@zoechi I noticed, it would still be nice though to define one-liners inline.

@munificent munificent changed the title Constant function literals. Constant function literals Dec 17, 2016
@trinarytree
Copy link

This proposal could save devs from creating boilerplate functions when using Angular dependency injection:

// Wouldn't this be nice?
final myProvider = const Provider(
    Foo, useFactory: const (Dep dep) => new Foo(dep.bar), deps: const [Dep]);

Instead we must do this:

final myProvider = const Provider(Foo, useFactory: _fooFactory);

@Injectable()
Foo _fooFactory(Dep dep) => new Foo(dep.bar); // Ughh..

Admittedly, in the 1st version you have to repeat deps, but often there aren't any deps or writing the few deps twice is an acceptable tradeoff vs inventing the name _fooFactory and writing it twice in 2 places that are far from each other.

@matanlurey
Copy link
Contributor

+1

We also wouldn't need @Injectable at all for a class, as the only reason it will exists (soon) is to generate a factory function once per library versus once per use-site. If we could use const then we'd know the functions will be canoncalized by the compiler and remove the need entirely.

@matanlurey
Copy link
Contributor

Or it would be nice if you could just infer, but I understand if we cannot

@lrhn
Copy link
Member Author

lrhn commented Jan 20, 2017

About inferring that a function needs to be const, it would be handled by dart-lang/sdk#4046 as well (well, as long as the default value expression needs to be const).

@chalin
Copy link

chalin commented Mar 4, 2018

Any progress on this? With the new guidelines for writing providers in Angular 5, it would be great to be able to write:

final mockRouter = new MockRouter();
...
    const FactoryProvider(Router, () => mockRouter)
...

cc @matanlurey @kwalrath

@tvolkert
Copy link

tvolkert commented Oct 3, 2018

Any update on this?

@eernstg
Copy link
Member

eernstg commented Oct 3, 2018

No. Not a bad idea, but also not near the top of the list at this point. Could happen!

@ConsoleTVs
Copy link

7 years old issue over here and I still need this :c

@AirborneEagle
Copy link

+1 could very much use this. or at least a decent work around.

@akarabach
Copy link

Any updates on it ?

@jerryzhoujw
Copy link

jerryzhoujw commented Mar 17, 2020

In flutter's UiKitView and AndroidView, their parameter onPlatformViewCreated can be const method, but because of the current state, UiKitView and AndroidView can't define as const.

@natebosch
Copy link
Member

but because of the current state, UiKitView and AndroidView can't define as const.

This issue should not be blocking - there is always the workaround of making a top level method instead. This issue is about better ergonomics.

@DeedleFake
Copy link

DeedleFake commented Dec 28, 2021

I was under the impression that this proposal was to make the actual function reference itself constant, not the body of the function. Calling a function would still be a runtime operation, I assume. Honestly, it might be possible to just make all function literals constant by default. No, it isn't, at least not without breaking backwards compatibility for some odd edge cases. See below.

Functions that can be called as a constant expression would be neat as well, but I think that would be more like async than something specific to function literals. For example,

doSomething(
  onSomething: const (v) {
    // The body of the function is completely normal. In other words, there's no difference
    // between this function and a top-level or static function because under the hood it
    // actually is one.
    print(v);
  },
);

vs.

// The body of the function is restricted to constant expressions.
int add(int a, int b) const => a + b;

const thisWouldThusBeValid = add(1, 2);

Edit: Thinking about it some more, making function literals default to const could cause breakage in their usage in sets, maps, and anything else than needs to equate them. For example, the following is how it works currently:

int test() => 1;

void main() {
  print(test == test); // true
  print(identical(test, test)); // true
  
  final literals = <int Function()>[];
  for (var i = 0; i < 2; i++) {
    literals.add(() => 1);
  }
  print(literals[0] == literals[1]); // false
  print(identical(literals[0], literals[1])); // false
}

With this change, however, the following should also work:

final literals = <int Function()>[];
for (var i = 0; i < 2; i++) {
  literals.add(const () => 1);
}
print(literals[0] == literals[1]); // true
print(identical(literals[0], literals[1])); // true

@lrhn
Copy link
Member Author

lrhn commented Jan 3, 2022

The body of the function need not be constant expressions, but it cannot refer to something that depends on runtime scopes.
Constant expressions evaluate to one value, every time they are evaluated. That function's body cannot refer to any variable in scope which isn't the same variable on every evaluation.

It can refer to a static variable, because there is only one instance of that variable.
It cannot refer to a local variable declared outside of the const function itself (it can refer to its own parameters and local variables just fine), and it cannot refer to this (if created in a scope where there might be a this), because there isn't one such variable, there is one variable per invocation of the surrounding code. There is only one constant function value.

I'm not particularly worried about canonicalizing function literals that can be constant. Being able to distinguish functions which do exactly the same isn't a big issue.

@lrhn
Copy link
Member Author

lrhn commented Jan 3, 2022

Yes, the functions themselves will effectively be static functions. The function literal expression will be a constant expression.
It's "constant (function literals)" that's being asked for, not "(constant function) literals".

(And the implementation plan, and existing workaround, is to hoist the function literal into a freshly-named static function declaration in the same static scope, then refer to it by name. That's a constant expression evaluating to a single canonicalized static function.)

@lrhn
Copy link
Member Author

lrhn commented Jan 3, 2022

Declaring a local function as const is actually reasonable. It'd be like const f = const (int x, int y) => x + y;, except that it can also call itself recursively.

If people start thinking they can call functions as part of constant evaluation, they'll figure out soon enough that it won't fly.
(Unless we introduce real "constant functions" too, which is possible, but ... probably not likely any time soon.)

But that's a marketing issue. The first question is whether we can, and want to, introduce constant function-literal expressions./

@lrhn
Copy link
Member Author

lrhn commented Jan 3, 2022

I'd be happy with static local declarations too. The const function literals are needed for the places where a constant literal is needed, which is, honestly, pretty much just default values.

@msbit
Copy link

msbit commented Jul 4, 2022

Ran up against this attempting to pass anonymous functions as arguments to an enum constructor as per #2241. Would be great to be able to do so.

@lucavenir
Copy link

lucavenir commented Dec 15, 2022

I was confused about Functions not being const. Indeed, functions must be defined at compile time, so having them not const is really confusing when learning Dart (at first). I guess there's a good reason they're not const.

This said, how will Dart 3 relate to this issue? Is it linked with static metaprogramming and/or other features? Is it going to be addressed by then or it's still a low priority?

Thanks for your hard work, Dart team 💙

@eernstg
Copy link
Member

eernstg commented Dec 15, 2022

@lucavenir, thanks for the kind words to the team!

functions must be defined at compile time

That's not quite true. For instance:

typedef F = int Function();

F g(int x) => () => x;

void main() {
  F f1 = g(2);
  F f2 = g(3);
  print(f1()); // '2'.
  print(f2()); // '3'.
}

In this example, we couldn't make () => x a constant expression. We don't even know how it should behave before we have chosen to consider a specific invocation of g, and there could be many invocations of g in the same program execution. So we cannot possibly create a single object representing the value of () => x at compile time, and then return that same object every time g is invoked.

That's the reason why proposals about constant function literals always have to put some constraints on the body of that function (e.g., that the body must not contain any references to any non-global declarations).

@jodinathan
Copy link

That's the reason why proposals about constant function literals always have to put some constraints on the body of that function (e.g., that the body must not contain any references to any non-global declarations).

isn't that how String and int works already?
by reading the compiled JS it seems that the compiler knows when a raw type variable is const even when not specified, ie:

final foo = 'foo';
final bar = '$foo bar';

print(bar);

compiles to something like

console.log('foo bar');

same with numbers:

final foo = 5;
final bar = 3 + foo;

print(bar); // compiles to console.log(8);

@eernstg
Copy link
Member

eernstg commented Dec 15, 2022

It would be possible to run the const-related checks on every function literal, and then consider each of them a constant expression if it satisfies the constraints. However, it might be easier to read the code, and to maintain it, if a function literal is only a constant expression when (1) there is a const modifier in front of it, or it occurs in a constant context, and (2) it satisfies those const-related constraints.

@lucavenir
Copy link

lucavenir commented Dec 15, 2022

That's not quite true. For instance:

That's embarassing, I totally forgot about higher order functions 😅

That's the reason why proposals about constant function literals always have to put some constraints on the body of that function (e.g., that the body must not contain any references to any non-global declarations).

So it's doable, as long as no parameters are giving, nor references to non-const global declarations are involved, right?

@eernstg
Copy link
Member

eernstg commented Dec 15, 2022

It's certainly doable. I think the main reason why it hasn't been given a very high priority is that when a function literal is const-able, it can also be lifted:

void main() {
  const f1 = (String s) => 2; // Could be allowed, if constant function literals were supported.
  const fs1 = [f1]; // Could then use `f1` in other constant expressions.

  const fs2 = [f2]; // But we can do this today---same thing.
}

int f2() => 2;

@GregoryConrad
Copy link

Would this feature also support compile-time evaluation/execution of these constant function literals at compile time (like Rust's const fn), assuming they are pure? Or is that a separate issue entirely?

It'd be nice to be able to use a const pure function to instantiate const objects when this is not possible through just a constructor.

@mraleph
Copy link
Member

mraleph commented Feb 22, 2023

@GregoryConrad what you are asking for is a larger feature which expands the set of expression / statements which compiler can evaluate in constant context. See all other issues tagged with enhanced-const

@shtse8
Copy link

shtse8 commented Mar 16, 2023

could it be possible to place const func in annotation?

@LewisHolliday
Copy link

LewisHolliday commented Nov 17, 2023

*Edited*. The original example wasn't clear.

I often find myself wanting to write this pattern:

// flutter_library.dart
class FlutterButton {
  const FlutterButton({this.onPressed});
  final void Function()? onPressed;
}

// my_code.dart
class MyButton {
  const MyButton(void Function()? f)
      : button = const FlutterButton(
            onPressed: f == null
                ? null
                : () {
                    // Do something here such as logging
                    f();
                  }); // Error: Invalid constant value

  final FlutterButton button;
}

Unfortunately, the above is invalid. I'm not sure if there's a practical workaround that doesn't burden the user who instantiates MyButton.

The above pattern isn't just blocked by this issue, but also another. The language disallows the following:

const MyButton(void Function()? f)
      : button = const FlutterButton(onPressed: f); // f is not a valid constant?

Below, TekExplorer posted the issue tracking this: #2000

@TekExplorer
Copy link

The workaround for that is to just have a method or getter in your widget that does the wrapping. let the onPressed be passed in directly.

class SomeWidget ... {
  const SomeWidget({this.onPressed})
  final VoidCallback? onPressed;

  VoidCallback? get _onPressed {
    if (onPressed == null) return null;
    // do whatever
    return () {
      onPressed();
    }
  }
  Widget build() {...}
} 

wrapping a function like that in the constructor doesnt really provide much benefit at all.

@LewisHolliday
Copy link

LewisHolliday commented Nov 18, 2023

The point is to allow using the constructor argument onPressed as a constant. I should have been more clear in my original example. I've edited my original comment.
The following is another pattern that is solely not* solely blocked by this issue (#1048 (comment))

import 'package:flutter/material.dart';

class LoggingInkWell extends InkWell {
  const LoggingInkWell({
    super.key,
    required Function()? onTap,
    super.child,
    super.borderRadius,
  }) : super(onTap: onTap == null ? null : () {
    // Do something such as logging here.
    onTap();
  }); // Error: Invalid constant value
}

@TekExplorer
Copy link

That still wouldnt work, because onTap is not const.
Now, yes. it would be if the const constructor was used, but currently that does not transfer.
see #2000

@renatoathaydes
Copy link

It would be nice to be able to take a const reference to a const's method:

class MyConst {
  const MyConst();
  int foo() => 1;
}

const myFoo = const MyConst().foo;

main() {
  print(myFoo());
}

This fails with Const variables must be initialized with a constant value..

I think this may be the same issue as this one?

@rrousselGit
Copy link

How would this work when applying hot-reload?

Say a user defines:

const a = () => 'hello';

And changes the function to const a = () => 'bar').

Would:

  • old a be identical to new a, and the function be updated to the new behavior (like functions)
  • or would a contain a new function, so old a != new a, like when changing const a = 42 to const a = 21

If the latter, what about:

const a = () => 'hello';
const b = () => a();

If we change the source of a and a new function instance is made, would b get a new instance too?

@Number-3434
Copy link

Number-3434 commented Jan 20, 2024

I think that const methods should only refer to final fields on the declaring class, if the class was created as const.

Also, they can refer to other const methods, and other constants, but cannot use any assignment operators

@lrhn
Copy link
Member Author

lrhn commented Jan 20, 2024

@Number-3434
That sounds more like C++ const functions.
Dart constant functions, which already exist since all top-level and static functions are constants, can do anything at runtime, but cannot close over any object instances, and cannot have a this reference.
They can refer to any static declaration directly, and to any other object through their parameters or through global variables, but they cannot refer directly to instance members of the surrounding class, since there is no insurance to access them through.
And there is no restriction on what the body code can do, including doing assignments.

It's just not that kind of const.

@Number-3434
Copy link

Dart constant functions, which already exist since all top-level and static functions are constants

Does that mean that you could (in theory) stick the const keyword in front of the method?

Would this be rather simple to achieve?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhanced-const Requests or proposals about enhanced constant expressions feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests