I've noticed it's possible to create a const constructor in Dart. In the documentation, it says that const word is used to denote something a compile time constant.
I was wondering what happens when I use a const constructor to create an object. Is this like an immutable object which is always the same and available at compile time? How does the concept of const constructor actually work? How is a const constructor different from a regular constructor?
Const constructor creates a "canonicalized" instance.
That is, all constant expressions begin canonicalized, and later these "canonicalized" symbols are used to recognize equivalence of these constants.
Canonicalization:
A process for converting data that has more than one possible representation into a "standard" canonical representation. This can be done to compare different representations for equivalence, to count the number of distinct data structures, to improve the efficiency of various algorithms by eliminating repeated calculations, or to make it possible to impose a meaningful sorting order.
This means that const expressions like const Foo(1, 1) can represent any usable form that is useful for comparison in virtual machine.
The VM only needs to take into account the value type and arguments in the order in which they occur in this const expression. And, of course, they are reduced for optimization.
Constants with the same canonicalized values:
var foo1 = const Foo(1, 1); // #Foo#int#1#int#1
var foo2 = const Foo(1, 1); // #Foo#int#1#int#1
Constants with different canonicalized values (because signatures differ):
var foo3 = const Foo(1, 2); // $Foo$int$1$int$2
var foo4 = const Foo(1, 3); // $Foo$int$1$int$3
var baz1 = const Baz(const Foo(1, 1), "hello"); // $Baz$Foo$int$1$int$1$String$hello
var baz2 = const Baz(const Foo(1, 2), "hello"); // $Baz$Foo$int$1$int$2$String$hello
Constants are not recreated each time. They are canonicalized at compile time and stored in special lookup tables (where they are hashed by their canonical signatures) from which they are later reused.
P.S.
The form #Foo#int#1#int#1 used in these samples is only used for comparison purposes and it is not a real form of canonicalization (representation) in Dart VM;
But the real canonicalization form must be "standard" canonical representation.
I find Lasse's answer on Chris Storms blog a great explanation.
Dart Constant Constructors
I hope they don't mind that I copy the content.
This is a fine explanation of final fields, but it doesn't really
explain const constructors. Nothing in these examples actually use
that the constructors are const constructors. Any class can have final
fields, const constructors or not.
A field in Dart is really an anonymous storage location combined with
an automatically created getter and setter that reads and updates the
storage, and it can also be initialized in a constructor's initializer
list.
A final field is the same, just without the setter, so the only way to
set its value is in the constructor initializer list, and there is no
way to change the value after that - hence the "final".
The point of const constructors is not to initialize final fields, any
generative constructor can do that. The point is to create
compile-time constant values: Objects where the all field values are
known already at compile time, without executing any statements.
That puts some restrictions on the class and constructor. A const
constructor can't have a body (no statements executed!) and its class
must not have any non-final fields (the value we "know" at compile
time must not be able to change later). The initializer list must also
only initialize fields to other compile-time constants, so the
right-hand sides are limited to "compile-time constant
expressions"[1]. And it must be prefixed with "const" - otherwise you
just get a normal constructor that happens to satisfy those
requirements. That is perfectly fine, it's just not a const
constructor.
In order to use a const constructor to actually create a compile-time
constant object, you then replace "new" with "const" in a
"new"-expression. You can still use "new" with a const-constructor,
and it will still create an object, but it will just be a normal new
object, not a compile-time constant value. That is: A const
constructor can also be used as a normal constructor to create objects
at runtime, as well as creating compile-time constant objects at
compilation time.
So, as an example:
class Point {
static final Point ORIGIN = const Point(0, 0);
final int x;
final int y;
const Point(this.x, this.y);
Point.clone(Point other): x = other.x, y = other.y; //[2]
}
main() {
// Assign compile-time constant to p0.
Point p0 = Point.ORIGIN;
// Create new point using const constructor.
Point p1 = new Point(0, 0);
// Create new point using non-const constructor.
Point p2 = new Point.clone(p0);
// Assign (the same) compile-time constant to p3.
Point p3 = const Point(0, 0);
print(identical(p0, p1)); // false
print(identical(p0, p2)); // false
print(identical(p0, p3)); // true!
}
Compile-time constants are canonicalized. That means the no matter how
many times you write "const Point(0,0)", you only create one object.
That may be useful - but not as much as it would seem, since you can
just make a const variable to hold the value and use the variable
instead.
So, what are compile-time constants good for anyway?
They are useful for enums.
You can use compile-time constant values in switch cases.
They are used as annotations.
Compile-time constants used to be more important before Dart switched
to lazily initializing variables. Before that, you could only declare
an initialized global variable like "var x = foo;" if "foo" was a
compile-time constant. Without that requirement, most programs can be
written without using any const objects
So, short summary: Const constructors are just for creating
compile-time constant values.
/L
[1] Or really: "Potentially compile-time constant expressions"
because it may also refer to the constructor parameters.
[2] So yes, a class can have both const and non-const constructors at the same time.
This topic was also discussed in https://github.com/dart-lang/sdk/issues/36079 with some interesting comments.
Very well explained in detail but for the users who are actually looking for the usage of a const constructor
It is used to Increase Flutter Performance as it helps Flutter to
rebuild only widgets that should be updated.Means while Using
setState() in StateFulWidgets, only those components will be rebuild
that are non const constructor
Can be explained with example->
class _MyWidgetState extends State<MyWidget> {
String title = "Title";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: <Widget>[
const Text("Text 1"),
const Padding(
padding: const EdgeInsets.all(8.0),
child: const Text("Another Text widget"),
),
const Text("Text 3"),
],
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
setState(() => title = 'New Title');
},
),
);
}
}
As In this example, only Text title should get changed, so only this widget should get rebuild, so making all the others widgets as const constructor will help flutter to do the same for increasing performance.
in This video, You will know why we need it. https://www.youtube.com/watch?v=B1fIqdqwWw8&t=558s From 09:18 to 16:09
In Documentation: https://dart.dev/guides/language/language-tour
constant constructors. To create a compile-time constant using a
constant constructor, put the const keyword before the constructor
name:
> var p = const ImmutablePoint(2, 2);
Constructing two identical
compile-time constants results in a single, canonical instance:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1,1);
assert(identical(a, b)); // They are the same instance!
Within a
constant context, you can omit the const before a constructor or
literal. For example, look at this code, which creates a const map:
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
You can omit
all but the first use of the const keyword:
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], };
If a constant
constructor is outside of a constant context and is invoked without
const, it creates a non-constant object:
> var a = const ImmutablePoint(1, 1); // Creates a constant
> var b = ImmutablePoint(1, 1); // Does NOT create a constant
>
> assert(!identical(a, b));// NOT the same instance!
For more information you can check these 2 Answers below:
1- https://stackoverflow.com/a/21746692/14409491
2- https://stackoverflow.com/a/21745617/14409491
An example demo that const instance really decide by final field.
And in this case, it cannot be predict in compile-time.
import 'dart:async';
class Foo {
final int i;
final int j = new DateTime.now().millisecond;
const Foo(i) : this.i = i ~/ 10;
toString() => "Foo($i, $j)";
}
void main() {
var f2 = const Foo(2);
var f3 = const Foo(3);
print("f2 == f3 : ${f2 == f3}"); // true
print("f2 : $f2"); // f2 : Foo(0, 598)
print("f3 : $f3"); // f3 : Foo(0, 598)
new Future.value().then((_) {
var f2i = const Foo(2);
print("f2 == f2i : ${f2 == f2i}"); // false
print("f2i : $f2i"); // f2i : Foo(0, 608)
});
}
Now dart will check it.
Dart Analysis:
[dart] Can't define the 'const' constructor because the field 'j' is initialized with a non-constant value
Runtime Error:
/main.dart': error: line 5 pos 17: expression is not a valid compile-time constant
final int j = new DateTime.now().millisecond;
Related
I was looking for codes to understand the difference between final and const in dart. After I found some code blocks and changed a bit, the outputs were surprising for me. How to explain these outputs? What causes this difference?
void main() {
final list1 = [1, 2];
final list2 = [1, 2];
print(list1);
print(list2);
print(list1 == list2); //false
const list3 = [1, 2];
const list4 = [1, 2];
print(list3);
print(list4);
print(list3 == list4); //true
}
const means the object is created at compile time instead of when the program is running. Dart does also guarantee that if you create two const objects with the same arguments, it will both points to the same compile time object. This optimization is possible since const objects MUST be immutable and we can therefore safely just share the same instance multiple times.
The == operator will by default (and such also on List) check if two objects are the same as in the same physical object in memory.
Since your two const created lists are created with the same objects, the two lists variables, list3 and list4, will end up pointing to the same exact object created by the compiler and therefore list3 == list4 is going to be true.
I'm currently using the pedantic package for my dart/flutter linting.
My analysis_options.yaml look like
include: package:pedantic/analysis_options.yaml
linter:
rules:
always_specify_types: true
omit_local_variable_types: false
I like having to specify types when I declare a variable line. But with this, I have to also specify the type on the created list/map.
The issues I have are the parts within **
const int number = 5;
const bool isTrue = false;
const String myName = 'Dan';
const List<int> numbers = **<int>**[1, 2, 3, 4];
const Map<String, int> ages = **<String, int>**{'Dan': 1};
It's redundent. How can I set my analysis_options.yaml to fix this.
Thanks!
I am getting an error on constant evaluation.
please take a look at this code:
class A {
final num a;
const A(this.a);
}
class B {
final A a;
const B(this.a);
}
main() {
const a = A(12);
const b = B(a); // this works fine
// I believe everything inside a const List is considered constant,
// please correct me if that is wrong
const aL = [ A(12), A(13) ]; // [ a, A(13) ] will not work either
const b2 = B(
aL[0], // here the error is happening
);
}
Error:
lib/logic/dartTest.dart:22:14: Error: Constant evaluation error:
const b2 = B(
^
lib/logic/dartTest.dart:23:7: Context: The method '[]' can't be invoked on '<A>[A {a: 12}, A {a: 13}]' in a constant expression. - 'A' is from 'package:t_angband/logic/dartTest.dart' ('lib/logic/dartTest.dart').
aL[0],
^
lib/logic/dartTest.dart:22:9: Context: While analyzing:
const b2 = B(
^
The list contains constant Object, then why the constant evaluation is failing? Shouldn't this be an analyzer issue? Am I missing something?
Thankyou.
Constant expressions can only build data, it cannot deconstruct it. You cannot call any methods on the constant objects except a handful of operations on numbers (and String.length, which also creates a number).
So, aL[0] is simply not a valid compile-time constant expression.
A possible fix may be to make b2 not constant!
Functions in Dart are first-class objects, allowing you to pass them to other objects or functions.
void main() {
var shout = (msg) => ' ${msg.toUpperCase()} ';
print(shout("yo"));
}
This made me wonder if there was a way to modify a function a run time, just like an object, prior to passing it to something else. For example:
Function add(int input) {
return add + 2;
}
If I wanted to make the function a generic addition function, then I would do:
Function add(int input, int increment) {
return add + increment;
}
But then the problem would be that the object I am passing the function to would need to specify the increment. I would like to pass the add function to another object, with the increment specified at run time, and declared within the function body so that the increment cannot be changed by the recipient of the function object.
The answer seems to be to use a lexical closure.
From here: https://dart.dev/guides/language/language-tour#built-in-types
A closure is a function object that has access to variables in its
lexical scope, even when the function is used outside of its original
scope.
Functions can close over variables defined in surrounding scopes. In
the following example, makeAdder() captures the variable addBy.
Wherever the returned function goes, it remembers addBy.
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
In the above cases, we pass 2 or 4 into the makeAdder function. The makeAdder function uses the parameter to create and return a function object that can be passed to other objects.
You most likely don't need to modify a closure, just the ability to create customized closures.
The latter is simple:
int Function(int) makeAdder(int increment) => (int value) => value + increment;
...
foo(makeAdder(1)); // Adds 1.
foo(makeAdder(4)); // Adds 2.
You can't change which variables a closure is referencing, but you can change their values ... if you an access the variable. For local variables, that's actually hard.
Mutating state which makes an existing closure change behavior can sometimes be appropriate, but those functions should be very precise about how they change and where they are being used. For a function like add which is used for its behavior, changing the behavior is rarely a good idea. It's better to replace the closure in the specific places that need to change behavior, and not risk changing the behavior in other places which happen to depend on the same closure. Otherwise it becomes very important to control where the closure actually flows.
If you still want to change the behavior of an existing global, you need to change a variable that it depends on.
Globals are easy:
int increment = 1;
int globalAdder(int value) => value + increment;
...
foo(globalAdd); // Adds 1.
increment = 2;
foo(globalAdd); // Adds 2.
I really can't recommend mutating global variables. It scales rather badly. You have no control over anything.
Another option is to use an instance variable to hold the modifiable value.
class MakeAdder {
int increment = 1;
int instanceAdd(int value) => value + increment;
}
...
var makeAdder = MakeAdder();
var adder = makeAdder.instanceAdd;
...
foo(adder); // Adds 1.
makeAdder.increment = 2;
foo(adder); // Adds 2.
That gives you much more control over who can access the increment variable. You can create multiple independent mutaable adders without them stepping on each other's toes.
To modify a local variable, you need someone to give you access to it, from inside the function where the variable is visible.
int Function(int) makeAdder(void Function(void Function(int)) setIncrementCallback) {
var increment = 1;
setIncrementCallback((v) {
increment = v;
});
return (value) => value + increment;
}
...
void Function(int) setIncrement;
int Function(int) localAdd = makeAdder((inc) { setIncrement = inc; });
...
foo(localAdd); // Adds 1.
setIncrement(2);
foo(localAdd); // Adds 2.
This is one way of passing back a way to modify the local increment variable.
It's almost always far too complicated an approach for what it gives you, I'd go with the instance variable instead.
Often, the instance variable will actually represent something in your model, some state which can meaningfully change, and then it becomes predictable and understandable when and how the state of the entire model changes, including the functions referring to that model.
Using partial function application
You can use a partial function application to bind arguments to functions.
If you have something like:
int add(int input, int increment) => input + increment;
and want to pass it to another function that expects to supply fewer arguments:
int foo(int Function(int input) applyIncrement) => applyIncrement(10);
then you could do:
foo((input) => add(input, 2); // `increment` is fixed to 2
foo((input) => add(input, 4); // `increment` is fixed to 4
Using callable objects
Another approach would be to make a callable object:
class Adder {
int increment = 0;
int call(int input) => input + increment;
}
which could be used with the same foo function above:
var adder = Adder()..increment = 2;
print(foo(adder)); // Prints: 12
adder.increment = 4;
print(foo(adder)); // Prints: 14
In Dart Object() constructor is declared as const so:
identical(const Object(), const Object()); //true
I know that in Dart 2 the keyword const is optional and I thought that the previous statement was equivalent to:
identical(Object(), Object()); //false
But actually it seems to be equivalent to:
identical(new Object(), new Object()); //false
Now my doubts are:
1) When is const keyword optional?
2) Is there any way to ensure instances of my classes to be always constant without const keyword? So that I can obtain:
indentical(MyClass(), MyClass()); //true (is it possible?)
Dart 2 allows you to omit new everywhere. Anywhere you used to write new, you can now omit it.
Dart 2 also allows you to omit const in positions where it's implied by the context. Those positions are:
Inside a const object creations, map or list literal (const [1, [2, 3]]).
Inside a const object creation in metadata (#Foo(Bar()))
In the initializer expression of a const variable (const x = [1];).
In a switch case expression (case Foo(2):...).
There are two other locations where the language requires constant expressions, but which are not automatically made constant (for various reasons):
Optional parameter default values
initializer expressions of final fields in classes with const constructors
1 is not made const because we want to keep the option of making those expressions not need to be const in the future. 2 is because it's a non-local constraint - there is nothing around the expression that signifies that it must be const, so it's too easy to, e.g., remove the const from the constructor without noticing that it changes the behavior of the field initializer.
const is optional in a const context. Basically a const context is created when the expression has to be const to avoid compilation error.
In the following snippet you can see some place where const is optional:
class A {
const A(o);
}
main(){
// parameters of const constructors have to be const
var a = const A(const A());
var a = const A(A());
// using const to create local variable
const b = const A();
const b = A();
// elements of const lists have to be const
var c = const [const A()];
var c = const [A()];
// elements of const maps have to be const
var d = const {'a': const A()};
var d = const {'a': A()};
}
// metadatas are const
#A(const A())
#A(A())
class B {}
You can find more details in Optional new/const and Implicit Creation.