Freezed #Default list parameter creates unmodifiable list - dart

I am trying out the example given in the Freezed page at https://pub.dev/packages/freezed.
This is the example I am testing replacing int with List<int> adding a [40] as default.
#Freezed(makeCollectionsUnmodifiable: false)
class Example with _$Example {
factory Example([#Default([40]) List<int> list]) = _Example;
factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
using the Example class:
final e = Example(); //default parameter [40]
//e.list.add(42); //error producing line
print('e $e');
final f = Example([40]); //parameter same as default value
f.list.add(42);
print('f $f');
final g = Example([41]); //another parameter
g.list.add(42);
print('g $g');
The output is:
e Example(list: [40])
f Example(list: [40, 42])
g Example(list: [41, 42])
If I remove the comment in the default parameter case, I get the following error:
Unhandled exception:
Unsupported operation: Cannot add to an unmodifiable list
#0 UnmodifiableListMixin.add (dart:_internal/list.dart:114:5)
#1 main (file:///D:/Dart/darttest/bin/darttest.dart:27:10)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
Why the list becomes unmodifiable?

Dart annotations are used by code generators that parse the source code. As the expressions inside annotations do not get executed, they must be constant expressions that can be computed during compilation.
Objects constructed with the const keyword must be immutable. They cannot change, as is written in the Dart Language Tour:
You can’t change the value of a const variable:
baz = [42]; // Error: Constant variables can't be assigned a value.
Although a final object cannot be modified, its fields can be changed. In comparison, a const object and its fields cannot be changed: they’re immutable.
var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.
To provide a default list value in the annotation, it must be const and therefore unmodifiable.
A note on Freezed
On another note, modifying lists goes against the entire Freezed concept. Freezed is used to generate immutable data structures, that can be updated with the copyWith method. It's a mistake to modify any collections in Freezed classes.
A recent update does add mutability through the unfreezed annotation, which should be used for mutable classes.

Related

Type safety in Dart using Container

I found something strange in dart. If there is a list that contains instances of a base class (in this example Super), the list can be set with a list of inherited instances. It seems that this changes the list type at runtime.
Is this intended behavior or is this a bug in Dart?
abstract class Super {}
class A extends Super {}
class B extends Super {}
class Container {
List<Super> mylist = [];
Container(this.mylist);
}
void main() {
// 1. dont't works
final container = Container(<A>[A(), A()]);
// 2. works
final container = Container([A(), A()]);
print(container.mylist.runtimeType);
container.mylist.add(B());
print(container.mylist);
}
If case 1 is used in the code above I get the following error:
JSArray<A>
Uncaught Error: TypeError: Instance of 'B': type 'B' is not a subtype of type 'A'
The error is at the line where I try to add an instance of B:
container.mylist.add(B());
Dart has a system called type promotion, where it can promote the type of a variable, similar to type inference.
It works as a cast. On the first example you've explicit promoted the type of your list to be of type A, so there's nothing strange about this.
Take a look at the first article that explains this mechanism.
When you do:
final container = Container(<A>[A(), A()]);
you explicitly create a List<A> object. Although Container's constructor expects a List<Super>, it accepts a List<A> argument because Dart considers Generic<Derived> to be a subtype of Generic<Base> if Derived is a subtype of Base. Your later attempt to do container.mylist.add(B()); will fail because container.mylist is actually a List<A> and therefore cannot legally store any B elements.
When you instead do:
final container = Container([A(), A()]);
then, because the List literal is not given an explicit type, its type is inferred to be List<Super> from Container's expected construction parameter. container.mylist.add(B()); will succeed since container.mylist is actually a List<Super> and therefore can legally store B elements.

List loses methods when assigning as Map value

I am trying to assign a List as a value to a field in a Map.
However, if I vary the types in the Map, the methods seem to be no longer available on the List.
Example:
var list = [1,2];
var myMap = {
'list' : list,
//'commentOut' : 3,
};
print("${myMap['list'].runtimeType}");
print("${myMap['list']!.length}");
Output
JSArray<int>
2
However, if I uncomment the line 'commentOut' : '
I get the error
Error: The getter 'length' isn't defined for the class 'Object'.
Is there a way to get this to work, or am I misunderstanding something fundamental we can't do with Maps ?
When you do:
var list = [1,2];
var myMap = {
'list' : list,
};
then the inferred type of myMap is Map<String, List<int>>.
When you instead do:
var myMap = {
'list' : list,
'commentOut' : 3,
};
your Map's values are now heterogeneous, so Dart infers the value type as the nearest common base type. In this case, the common base type of List<int> and int is Object, so myMap's inferred type is Map<String, Object>.
Consequently, static analysis tools (the Dart analyzer and the Dart compiler) know only that the static (known at compile-time) type of myMap['list'] is Object? even though the actual runtime type is List<int>. It therefore will be a compile-time error to attempt to directly access List methods on it, because calling List methods on an arbitrary Object is potentially unsafe.
You either must explicitly cast the value (myMap['list'] as List<int>) or disable static type checking by explicitly declaring myMap to be of type Map<String, dynamic>.
3 is a value type, not an object type. So the property "length" is not defined on a value type. Even though you defined the element 'list' to get the length, it doesn't know how to handle it.

Why one dynamic value can be converted to other without casting and the other one fails?

final dList = <dynamic> [];
final List<String> sList1 = dList; // fails (can't implicitly cast)
final sList2 = dList.cast<String>(); // works (needs manual casting)
dynamic dString = '';
final String sString1 = dString; // works
final sString2 = dString as String; // works
You can see the comments in the code part what I am talking about, it is difficult to point out the piece of code here in writing part, so I added them in the code part.
List fails to convert but other types like bool, int, String works with internal casting.
The point is that dList is a List<dynamic>. The type dynamic is a top type (a supertype of all other types), and it's reified (so you can test it at run time, as opposed to Java where type arguments are erased at run time). With cast you are creating a new object, instance of List<String>, so it's allowed to be the value of a variable of that type.
With dString you already have an instance of type String (because '' evaluates to such an instance), so the cast just verifies that this is indeed a String.
You can never use a cast in Dart to obtain an object whose type is different from the starting point, it will only check the type of the existing object (and confirm that the type is as required, or throw).

In Dart2, what is the correct "anything" type to use for generics?

AngularDart has a class called AppView, i.e. abstract class AppView<T> {}.
One (at least) of these are generated for every class annotated with #Component:
// file.dart
#Component(...)
class DashboardComponent {}
// file.template.dart (Generated)
class ViewDashboardComponent extends AppView<DashboardComponent> {}
I have code elsewhere in the framework that doesn't care what this T type is. I'm a little confused with Dart 2 what the "right" "anything" type to use. For example, I could use:
AppView
AppView<dynamic>
AppView<Object>
AppView<Null>
AppView<void>
I think more than one of these will "work". But which is the "right" one to use in this case?
You should be fine to use AppView (or AppView<dynamic>) just about anywhere. I can think of two examples where this will get you into trouble though:
If you are instantiating an AppView, you definitely want that type parameter. See the following error when you don't:
$ cat a.dart
void main() {
List<dynamic> a = ["one", "two", "three"];
List<String> b = a;
}
$ dart --preview-dart-2 a.dart
Unhandled exception:
type 'List' is not a subtype of type 'List<String>' where
List is from dart:core
List is from dart:core
String is from dart:core
#0 main (file:///Users/sam/a.dart:3:20)
#1 _startIsolate.<anonymous closure> (dart:isolate/isolate_patch.dart:279:19)
#2 _RawReceivePortImpl._handleMessage (dart:isolate/isolate_patch.dart:165:12)
If you are ever assigning a closure to a site that expects a closure with one or more typed parameters that involve T, you will see a "uses dynamic as bottom" static error (from the analyzer), and probably a runtime error as well:
$ cat f.dart
void main() {
List a = <String>["one", "two", "three"];
a.map((String s) => s.toUpperCase());
List b = ["one", "two", "three"];
b.map((String s) => s.toUpperCase());
}
$ dart --preview-dart-2 f.dart
f.dart:3:9: Error: A value of type '(dart.core::String) → dart.core::String' can't be assigned to a variable of type '(dynamic) → dynamic'.
Try changing the type of the left hand side, or casting the right hand side to '(dynamic) → dynamic'.
a.map((String s) => s.toUpperCase());
^
f.dart:6:9: Error: A value of type '(dart.core::String) → dart.core::String' can't be assigned to a variable of type '(dynamic) → dynamic'.
Try changing the type of the left hand side, or casting the right hand side to '(dynamic) → dynamic'.
b.map((String s) => s.toUpperCase());
^
(I'm not certain any Dart tool yet has complete Dart 2 runtime and compile time semantics, so this might change slightly.)
In these cases, it is best to use generic classes, generic methods, and generic typedefs to encapsulate, for a given scope, what the values of an object's type parameters might be.
I suspect there is a difference between dynamic and Object in Dart 2, and I think Günter covered this in his response, though if your code "doesn't care what this T type is", then you're probably not calling any methods on the component.
Edit: void
AppView<void> might be a good choice in this case, as an actual check that you actually never touch the underlying component (Object would probably serve the same purpose). See how we are allowed to access properties of a List<void> but not properties of the elements:
$ cat g.dart
void main() {
var c = <String>["one", "two", "three"];
fn(c);
fn2(c);
}
int fn(List<void> list) => list.length;
int fn2(List<void> list) => list.first.length;
$ dart --preview-dart-2 g.dart
g.dart:9:40: Error: The getter 'length' isn't defined for the class 'void'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'length'.
int fn2(List<void> list) => list.first.length;
^
I assume you know better than me, but my attempt
AppView - works - same as AppView<dynamic>
AppView<dynamic> - works - really means any type
AppView<Object> - works - really means any type
AppView<Null> - won't work, only null and void values match for T
AppView<void> - won't work, only null and void values match for T
AppView<void> - works (see also comment below from lrn)
The difference between <dynamic> and <Object> would be that for values of type T with T == dynamic property or method access won't be checked statically, while for T == Object only methods and properties of the Object class can be accessed without a previous cast.

What is the true meaning of pass-by-reference in modern languages like Dart?

Working with Futures in Dart, I've come across an interesting issue.
import 'dart:async';
class Egg {
String style;
Egg(this.style);
}
Future cookEggs(List<Egg> list) =>
new Future(() =>
['omelette','over easy'].forEach((_) => list.add(new Egg(_)))
);
Future cookOne(Egg egg) => new Future(() => egg = new Egg('scrambled'));
void main() {
List<Egg> eggList = new List();
Egg single;
cookEggs(eggList).whenComplete(() => eggList.forEach((_) => print(_.style));
cookOne(single).whenComplete(() => print(single.style));
}
The expected output is:
omelette
over easy
scrambled
The cookEggs function that gets the List<Eggs> works fine, but accessing the style property of single is unsuccessful and throws a NoSuchMethodError.
I first thought that this might have something to do with pass-by-reference, but I don't see why Dart would pass a List by reference but not an Egg. Now I'm thinking that the discrepancy may have something to do with the assignment (=) operator and the List.add() method. I'm stuck in that train of thought, and I don't know how to test my hypothesis.
Any thoughts?
The program's output and stack trace is show below:
omelette
over easy
Uncaught Error: The null object does not have a getter 'style'.
NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
Stack Trace:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
#1 main.<anonymous closure> (file:///path/to/eggs.dart:20:51)
#2 _rootRun (dart:async/zone.dart:719)
#3 _RootZone.run (dart:async/zone.dart:862)
#4 _Future._propagateToListeners.handleWhenCompleteCallback (dart:async/future_impl.dart:540)
#5 _Future._propagateToListeners (dart:async/future_impl.dart:577)
#6 _Future._complete (dart:async/future_impl.dart:317)
#7 Future.Future.<anonymous closure> (dart:async/future.dart:118)
#8 _createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:11)
#9 _handleTimeout (dart:io/timer_impl.dart:292)
#10 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:124)
Unhandled exception:
The null object does not have a getter 'style'.
NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
#0 _rootHandleUncaughtError.<anonymous closure>.<anonymous closure> (dart:async/zone.dart:713)
#1 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
#2 _asyncRunCallback (dart:async/schedule_microtask.dart:32)
#3 _asyncRunCallback (dart:async/schedule_microtask.dart:36)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:128)
Quick answer: what gets passed to your functions cookEggs and cookOne are references to the objects, not to the variables (which would be real pass-by-reference).
The term pass-by-reference is often misused to mean pass-references-by-value: many languages only have pass-by-value semantics, where the values that are passed around are references (i.e. pointers, without the dangerous features). See Is Java "pass-by-reference" or "pass-by-value"?
In the case of cookEggs(eggList)…
…the variable eggList contains a reference to a list of eggs. That reference was initially given to you by the expression new List(), and you're passing it to cookEggs after storing meanwhile in your variable eggList. Inside cookEggs, adding to the list works, because you passed a reference to an actual, existing list object.
In the case of cookOne(single)…
…the variable single has only been declared, so it was implicitly initialized by the language runtime to the special reference null. Inside cookOne, you're replacing which reference is contained in egg; since single is a different variable, it still contains null, therefore the code fails when you try to use that.
To clarify
The pass-references-by-value behavior is common to a lot of modern languages (Smalltalk, Java, Ruby, Python…). When you pass an object, you're actually passing-by-value (therefore copying) the contents of your variable, which is a pointer to the object. You never control where objects really exist.
Those pointers are named references rather than pointers, because they are restricted to abstract away the memory layout: you can't know the address of an object, you can't peek at the bytes around an object, you can't even be sure that an object is stored at a fixed place in memory, or that it's stored in memory at all (one could implement object references as UUIDs or keys in a persistent database, as in Gemstone).
In contrast, with pass-by-reference, you'd conceptually pass the variable itself, not its contents. To implement pass-by-reference in a pass-by-value language, you would need to reify variables as ValueHolder objects that can be passed around and whose contents can be changed.
That's because, like Java, Dart is always pass-by-value, and never pass-by-reference. The semantics of passing and assigning in Dart is the same as in Java. Look anywhere on StackOverflow about Java and pass by value to see why Java is described as always pass-by-value. Terms like pass-by-value and pass-by-reference should be used consistently across languages. So Dart should be described as always pass-by-value.
In actual "pass-by-reference", e.g. when a parameter is marked with & in C++ or PHP, or when a parameter is marked with ref or out in C#, simple assignment (i.e. with =) to that parameter variable inside the function will have the same effect as simple assignment (i.e. with =) to the original passed variable outside the function. This is not possible in Dart.
A popular misconception is that Dart is pass-by-reference. However, in a true pass-by-reference system (supported by languages such as C++), a function can set (and not just mutate) local variables in the caller.
Dart, like many other languages (e.g. Java, Python), is technically pass-by-value where the "value" of an object is a reference to it. This is why functions can mutate arguments passed by the caller.
However, in these languages where everything is an object and where there are not separate pointer types, "pass-by-value" and "pass-by-reference" can be confusing terms and are not particularly meaningful. A lot of Python programmers instead use the term pass-by-assignment.
Pass-by-assignment means that arguments are passed in way that's equivalent to normal variable assignment. For example, if I had:
final int x = 42;
final String s = "hello world!";
void foo(int intArgument, String stringArgument) {
...
}
void main() {
foo(x, s);
}
then the behavior would be equivalent to:
final int x = 42;
final String s = "hello world!";
void foo() {
int intArgument = x;
String stringArgument = s;
...
}
void main() {
foo();
}
When you do String stringArgument = s;, stringArgument and s are two separate variables referring to the same object. Reassigning stringArgument to something else won't change what s refers to. However, if you mutate that object in place, both stringArgument and s will refer to the now modified object.

Resources