I know that const is compile-time constant in dart, but I don't understand mechanism behind const [F0, F1, F2] in the following code:
class Foo {
static const F0 = 'F0';
static const F1 = 'F1';
static const F2 = 'F2';
// const list of const values I guess...
static const CONST_LIST = const [F0, F1, F2]; // please explain this line
static final String FOO = CONST_LIST[0]; // ok
// compile error: 'const' varaibles must be constant value
// static const String BAR = CONST_LIST[1];
}
main() {
// is CONST_LIST const or not?
// below line it's ok for dartanalyzer but
// in runtime: Cannot change the content of an unmodifiable List
Foo.CONST_LIST[1] = 'new value';
}
I noticed that const is required by dart analyzer in const [F0, F1, F2]; but it does make list more like final (runtime immutable list) rather than compile time const.
UPDATE:
Another question is why CONST_LIST[1] is not "constant value". See commented declaration of Foo.BAR.
Günter has answered the second part of your question. Here is some more information about const.
Const means that the object's entire deep state can be determined entirely at compile time and that the object will be frozen and completely immutable.
More information in this article. Also see the following question.
Regarding the second part of your question, consider the following:
const int foo = 10 * 10;
The expression "10 * 10" can be evaluated at compile time, so it is a "constant expression". The types of things you can do in a constant expression need to be quite limited (otherwise you could run arbitrary Dart code in the compiler!). But some of these limitations are being relaxed as dart matures, as you can see in the bug which Günter linked to.
In contrast, consider the following:
final int bar = 10;
final int foo = bar * bar;
Since "bar * bar" is not a constant expression it is evaluated at runtime.
There is an open bug for this: see https://github.com/dart-lang/sdk/issues/3059
Related
Consider the following class:
class MyClass<T extends num> {
const MyClass();
void instanceFunction() {}
static void staticFunction<T extends num>() {}
}
The following expressions are all true:
identical(const MyClass(), const MyClass<num>());
identical(const MyClass.new(), const MyClass<num>.new());
identical(const MyClass().instanceFunction, const MyClass<num>().instanceFunction);
identical(MyClass.new, MyClass.new);
identical(MyClass<num>.new, MyClass<num>.new);
identical(MyClass.staticFunction, MyClass.staticFunction);
identical(MyClass.staticFunction<num>, MyClass.staticFunction<num>);
But when the base type, num, is included in one tear-off, such as in the expressions below, they are false:
identical(MyClass.new, MyClass<num>.new);
identical(MyClass.staticFunction, MyClass.staticFunction<num>);
Why is this the case?
The true cases:
identical(const MyClass(), const MyClass<num>());
You instantiate an MyClass object. Since MyClass is declared with MyClass<T extends num>, MyClass() with no explicit type specified is shorthand for MyClass<num>(). This expression therefore is the same as identical(const MyClass<num>, const MyClass<num>), which should obviously be true.
identical(const MyClass.new(), const MyClass<num>.new());
This is just a more roundabout version of the previous case.
identical(const MyClass().instanceFunction, const MyClass<num>().instanceFunction);
This is true for the same reason as the first case. const MyClass() is shorthand for const MyClass<num>(), const instances are canonicalized, so you're comparing .instanceFunction members of the exact same object.
identical(MyClass.new, MyClass.new);
Both arguments are the same and both are statically-known at compile-time, so there's no reason for this to be false.
identical(MyClass<num>.new, MyClass<num>.new);
This would be true for the same reason as the previous case.
identical(MyClass.staticFunction, MyClass.staticFunction);`
identical(MyClass.staticFunction<num>, MyClass.staticFunction<num>);
Both arguments to identical are the same, so that these evaluate to true shouldn't be surprising.
The false cases:
identical(MyClass.new, MyClass<num>.new);
identical(MyClass.staticFunction, MyClass.staticFunction<num>);
MyClass.new and MyClass.staticFunction are not shorthand for MyClass<num>.new and MyClass.staticFunction<num> respectively; they evaluate to generic functions:
var c1 = MyClass.new;
var c2 = MyClass<num>.new;
var f1 = MyClass.staticFunction;
var f2 = MyClass.staticFunction<num>;
then the type of c1 is MyClass<T> Function<T extends num>() and the type of c2 is MyClass<num> Function(). Similarly, the type of f1 is void Function<T extends num>() but the type of f2 is void Function(). c1 and f1 are still generic; you can do c1<num>() or f2<double>() later.
c2 and f2 have the type parameter statically bound already, so they have a different type and clearly should not be the same as c1 and f1 respectively.
A more interesting case is:
identical(f1<num>, MyClass.staticFunction<num>))
which evaluates to false. Why isn't this the same as the identical(MyClass.staticFunction<num>, MyClass.staticFunction<num>) case? I'm not sure exactly why, but I would presume that it's an implementation detail from the Dart compiler being able to trivially canonicalize MyClass.staticFunction<num> at compilation-time but not f1<num> due to f1 being potentially variable.
Consider the following Dart code:
class Vec2 {
num x, y;
Vec2(this.x, this.y);
Vec2 operator*(num rhs) => Vec2(x * rhs, y * rhs);
String toString() => "<$x, $y>";
}
void main() => print(Vec2(1, 2) * 3);
The output is as expected:
<3, 6>
However, this only works when the left-hand side of the expression is a Vec2 and the right-hand side is a num. In this case, I want the multiplication operator to be commutative, so I write the following extension:
extension Vec2Util on num {
Vec2 operator*(Vec2 rhs) => Vec2(rhs.x * this, rhs.y * this);
}
One might naturally expect the following code to produce identical output to the first snippet:
void main() {
num x = 3;
print("${x * Vec2(1, 2)}");
}
However, the compiler is instead reporting that The argument type 'Vec2' can't be assigned to the parameter type 'num'. It looks as though the compiler is resolving the multiplication to num operator*(num rhs) in this case and then complaining that my Vec2 can't be passed in as a num operand. Why does the compiler apparently disregard my extension? What is the correct way to create custom commutative operators like this?
You cannot do what you want.
Dart user-definable binary operators are all methods on the first operand. Doing e1 + e2 is kind-of like doing e1.+(e2) where + is the name of the method, except you can't normally call a method +.
In order to be able to do 3 * vector, you need the method to be on 3.
You can't add methods to other people's classes, not without fiddling with their source code, and int is a system class so even that is not possible.
You cannot use extension methods because extension methods do not apply when the receiver type already has an instance method with the same name.
And int defines all the operators.
(It's like, and definitely not coincidentally, that the user-definable operators were chosen from exactly what int needs. That's not new in Dart, the operators go back to C and Java.)
So, you can define an extension method on int, but you can't call it anyway, not without an override:
extension MyInt on int {
Vector operator *(Vector other) => other.scalerMultiplication(this);
}
... MyInt(3) * vector ...
That's more complication than just swapping the operands.
class Foo {
final int x;
Foo([this.x = defValue]); // Compile-time error
static get defValue => 10;
}
Error:
The default value of an optional parameter must be a constant.
defValue is a compile time constant, so I should be able to pass its value to the constructor.
The expression defValue is not a compile-time constant expression. Evaluating it requires executing the getter to get a value, and constant evaluation cannot execute getters or methods (except a very specific list of allowed platform methods, like int.operator+). It might be that the expression returned by executing the defValue getter is itself a compile-time constant expression, but it's being returned through a non-constant operation.
Change the defValue definition to
static const defValue = 10;
then it should work. Reading constant declarations is a compile-time constant operation.
You refer to the docs saying that you can use a static method as a compile-time constant. That is correct, but that's the method itself which is the constant, you are still not allowed to call it.
That is:
static int foo(int x) => x;
static const fooRef = foo; // valid!
works because referencing the foo function value is a constant expression.
A getter is not a method, and you cannot do a "tear-off" of the getter. When you refer to it, you need to execute its body, and that's not allowed in a constant context.
My guess is that defValue is not known at compile time (since it's a getter and not a constant), so you should either use a constant variable like static const int defValue = 10 or initialize the class like this:
class Foo {
final int x;
Foo([int x]) : this.x = x ?? defValue;
static get defValue => 10;
}
class X extends Y {
X(int a, int b) : super(a,b);
}
Can someone give me an explanation about the syntax meaning of the colon :?
This feature in Dart is called "initializer list".
It allows you to initialize fields of your class, make assertions and call the super constructor.
This means that it is not the same as the constructor body. As I said, you can only initialize variables and only access static members. You cannot call any (non-static) methods.
The benefit is that you can also initialize final variables, which you cannot do in the constructor body. You also have access to all parameters that are passed to the constructor, which you do not have when initializing the parameters directly in the parentheses.
Additionally, you can use class fields on the left-hand of an assignment with the same name as a parameter on the right-hand side that refers to a parameter. Dart will automatically use the class field on the left-hand side.
Here is an example:
class X {
final int number;
X(number) : number = number ?? 0;
}
The code above assigns the parameter named number to the final field this.number if it is non-null and otherwise it assigns 0. This means that the left-hand number of the assignment actually refers to this.number. Now, you can even make an assertion that will never fail (and is redundant because of that, but I want to explain how everything works together):
class X {
final int number;
X(number): number = number ?? 0, assert(number != null);
}
Learn more.
It's ok to access non static member in initializer list.
class Point {
num x, y;
Point(this.x, this.y);
Point.origin(): this.x = 10, this.y = 10;
}
main() {
Point p = Point.origin();
print(p.x); // 10
}
Type Aliases do not provide compile-time type checking
Type aliases are very handy for shortening long type names. Type alias' are just syntatic sugar and are compiled into the aliased type at run-time, meaning two different aliases representing the same type can be used interchangeably without error:
type foo = int
type bar = int
let x : foo = 5
let y : bar = x
type foo = int
type bar = int
val x : foo = 5
val y : bar = 5
I understand why you wouldn't want to treat them as regular types with compile-time type enforcement. Still, in some scenarios, it would be very handy.
Sometimes, I need to differentiate between ints which represent different things
For example, I am importing some functions from a COM library that has several different kinds of values, but they are all represented as ints in my code:
[<DllImport(#"C:\API\COMAPI.dll", EntryPoint="Foobar")>]
extern int Foobar ( int hPool, int hServer )
In C, hPool is of type APIP and hServer and the function return type are both APIV:
typedef unsigned long API_INDEX; // index
typedef API_INDEX * APIV; // value handle
typedef void * APIP; // pool handle
I would like to represent these different types, representing different things, with different types that provide some compile-time type enforcement. That way, I can't accidentally pass an APIP to a function expecting an APIV.
Measures get me half-way there, but require a LOT of extra fluff
So far, the only solution I've found is to use measurements:
type [<Measure>] APIP
type [<Measure>] APIV
Unfortunately, it seems that measures cannot be added directly to externs:
[<DllImport(#"C:\API\COMAPI.dll", EntryPoint="Foobar")>]
extern int<APIV> _Foobar ( int<APIP> hPool, int<APIV> hServer )
error FS0010: Unexpected type application in extern declaration. Expected identifier or other token.
So I am having to write a wrapper function, resulting in a LOT of extra code:
[<DllImport(#"C:\API\COMAPI.dll", EntryPoint="Foobar")>]
extern int private _Foobar ( int hPool, int hServer )
let Foobar ( hPool : int<APIP> ) ( hServer : int<APIV> ) : APIV =
_Foobar( int hPool, int hServer ) |> LanguagePrimitives.Int32WithMeasure
Multiplied by dozens and dozens of imported functions, this is getting really bloated and tiresome.
Is there a more intuitive way to handle this, or am I stuck deciding between compile-time type checking and reasonably readable code?