Last active
February 25, 2025 14:52
-
-
Save eernstg/efd05f8c257e33c10cb413d0ac52c236 to your computer and use it in GitHub Desktop.
'Traits for Dart': How would we do it using 'more capable type objects'? See the first comment for some background.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This is the basic approach, showing how the examples in 'Traits for Dart' | |
can be written using 'More capable type objects'. The main differences are | |
that the 'traits' approach can add an implementation to an existing type | |
(say, `String`) whereas the 'type objects' approach supports late binding. | |
*/ | |
// --- Glue classes. | |
class Writer { | |
void writeString(String s) => print('"$s"'); | |
void writeList<X extends Serializable>(List<X> xs) { | |
for (final x in xs) x.writeTo(this); | |
} | |
} | |
class Reader { | |
final List<String> strings; | |
var _index = 0; | |
Reader(this.strings); | |
bool get hasNext => _index < strings.length; | |
String readString() => strings[_index++]; | |
} | |
// --- Example code from the document 'Traits in Dart'. | |
abstract class Serializable { | |
void writeTo(Writer w); | |
} | |
abstract class Deserializable<Self> { | |
Self readFrom(Reader r); | |
} | |
class B<T extends Serializable> implements Serializable | |
static implements Deserializable<B<T>> { | |
final T value; | |
B(this.value); | |
factory B.readFrom(Reader r) => B(T.readFrom(r)); | |
@override | |
void writeTo(Writer w) => value.writeTo(w); | |
@override | |
String toString() => 'B<$T>($value)'; | |
} | |
// Use a `Serializable` wrapper class to handle strings. | |
class String2 implements Serializable | |
/*static implements Deserializable<String2>*/ { | |
final String value; | |
const String2(this.value); | |
void writeTo(Writer w) => w.writeString('"$value"'); | |
String toString() => '"$value"'; | |
} | |
// Use a `Serializable` wrapper class to handle lists. | |
class List2<E extends Serializable> implements Serializable | |
/*static implements Deserializable<List2<E>>*/ { | |
final List<E> value; | |
const List2(this.value); | |
void writeTo(Writer w) { | |
for (final element in value) { | |
element.writeTo(w); | |
} | |
} | |
String toString() => value.toString(); | |
} | |
// --- Use it! | |
void main() { | |
final source1 = ['Hello, world!']; | |
final object1 = B<String2>.readFrom(Reader(source1)); | |
print('Source: "$source1"'); | |
print('Deserialized: $object1'); | |
print('Reserialized:'); | |
object1.writeTo(Writer()); | |
final source2 = ['one', 'two']; | |
final object2 = B<List2<String2>>.readFrom(Reader(source2)); | |
print('Source: "$source2"'); | |
print('Deserialized: $object2'); | |
print('Reserialized:'); | |
object2.writeTo(Writer()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Same as 'traits_as_capable_type_objects.dart', but desugared such that | |
the details of the semantics can be inspected. Running code. | |
*/ | |
// --- Glue classes. | |
class Writer { | |
void writeString(String s) => print('"$s"'); | |
void writeList<X extends Serializable>(List<X> xs) { | |
for (final x in xs) x.writeTo(this); | |
} | |
} | |
class Reader { | |
final List<String> strings; | |
var _index = 0; | |
Reader(this.strings); | |
bool get hasNext => _index < strings.length; | |
String readString() => strings[_index++]; | |
} | |
// --- Example code from the document 'Traits in Dart'. | |
// Single-line comments show code from the document, code which is | |
// not compilable uses /*...*/. | |
// trait Trait on Object { | |
// T0 method(T1 .. Tn) {...} // May use `Self`. | |
// } | |
/* | |
abstract class Trait<Self extends Object> { | |
T0 method(T1 .. Tn) {...} | |
} | |
late List<Trait> x; // OK. | |
late Trait t; // OK. | |
class B<T extends Trait> {...} // OK. | |
void foo<T extends Trait> {...} // OK. | |
*/ | |
// trait Serializable { | |
// void writeTo(Writer w); | |
// static Self readFrom(Reader r); | |
// } | |
abstract class Serializable { | |
void writeTo(Writer w); | |
} | |
abstract class Deserializable<Self> { | |
Self readFrom(Reader r); | |
} | |
// class B<T implements Serializable> { | |
// final T value; | |
// B(this.value); | |
// factory B.readFrom(Reader r) => B(T.readFrom(r)); | |
// void writeTo(Writer r) => value.writeTo(w); | |
// } | |
// // Provide implementation of Serializable for String | |
// impl Serializable for String { | |
// void writeTo(Writer w) => w.writeString(this); | |
// static String readFrom(Reader r) => r.readString(); | |
// } | |
// // Provide implementation of Serializable for any List<E>, | |
// // such that E has an implementation of Serializable. | |
// impl<E implements Serializable> Serializable for List<E> { | |
// void writeTo(Writer w) => w.writeList(this, (e) => e.writeTo(w)); | |
// } | |
class B<T extends Serializable> implements Serializable | |
/*static implements Deserializable<B<T>>*/ { | |
final T value; | |
B(this.value); | |
factory B.readFrom(Reader r) => B(emulateFeature<T>().readFrom(r)); | |
@override | |
void writeTo(Writer w) => value.writeTo(w); | |
@override | |
String toString() => 'B<$T>($value)'; | |
} | |
class ReifiedTypeForB<T extends Serializable> | |
implements Type, Deserializable<B<T>> { | |
B<T> readFrom(Reader r) => B.readFrom(r); | |
} | |
// Use a `Serializable` wrapper class to handle strings. | |
class String2 implements Serializable | |
/*static implements Deserializable<String2>*/ { | |
final String value; | |
const String2(this.value); | |
void writeTo(Writer w) => w.writeString('"$value"'); | |
String toString() => '"$value"'; | |
} | |
class ReifiedTypeForString2 implements Type, Deserializable<String2> { | |
String2 readFrom(Reader r) => String2(r.readString()); | |
} | |
// Use a `Serializable` wrapper class to handle lists. | |
class List2<E extends Serializable> implements Serializable | |
/*static implements Deserializable<List2<E>>*/ { | |
final List<E> value; | |
const List2(this.value); | |
void writeTo(Writer w) { | |
for (final element in value) { | |
element.writeTo(w); | |
} | |
} | |
String toString() => value.toString(); | |
} | |
class ReifiedTypeForList2<E extends Serializable> | |
implements Type, Deserializable<List2<E>> { | |
List2<E> readFrom(Reader r) { | |
final contents = <E>[]; | |
final result = List2(contents); | |
final reifiedType = emulateFeature<E>(); | |
while (r.hasNext) { | |
contents.add(reifiedType.readFrom(r)); | |
} | |
return result; | |
} | |
} | |
// Emulate the proposed feature by replacing the given reified type | |
// by an instance that has the members that the feature would add. | |
Deserializable<X> emulateFeature<X>() { | |
Object reifiedType = X; | |
reifiedType = switch (X) { | |
const (String2) => ReifiedTypeForString2(), | |
const (B<String2>) => ReifiedTypeForB<String2>(), | |
const (List2<String2>) => ReifiedTypeForList2<String2>(), | |
_ => throw "Unsupported type $reifiedType", | |
}; | |
return reifiedType as Deserializable<X>; | |
} | |
// --- Use it! | |
void main() { | |
final source1 = ['Hello, world!']; | |
final object1 = B<String2>.readFrom(Reader(source1)); | |
print('Source: "$source1"'); | |
print('Deserialized: $object1'); | |
print('Reserialized:'); | |
object1.writeTo(Writer()); | |
final source2 = ['one', 'two']; | |
final object2 = B<List2<String2>>.readFrom(Reader(source2)); | |
print('Source: "$source2"'); | |
print('Deserialized: $object2'); | |
print('Reserialized:'); | |
object2.writeTo(Writer()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Variant of 'traits_as_capable_type_objects.dart' that uses extensions to | |
provide support for `Deserializable<...>`, even with `String` and `List` | |
that we can't edit. | |
*/ | |
// --- Glue classes. | |
class Writer { | |
void writeString(String s) => print('"$s"'); | |
void writeList<X extends Serializable>(List<X> xs) { | |
for (final x in xs) x.writeTo(this); | |
} | |
} | |
class Reader { | |
final List<String> strings; | |
var _index = 0; | |
Reader(this.strings); | |
bool get hasNext => _index < strings.length; | |
String readString() => strings[_index++]; | |
} | |
abstract class Serializable { | |
void writeTo(Writer w); | |
} | |
abstract class Deserializable<Self> { | |
Self readFrom(Reader r); | |
} | |
class B<T> implements Serializable static implements Deserializable<B<T>> { | |
final T value; | |
B(this.value); | |
factory B.readFrom(Reader r) => | |
B(Type.reify<T, Deserializable<T>>()!.readFrom(r)); | |
@override | |
void writeTo(Writer w) => _doWriteTo(w, value); | |
@override | |
String toString() => 'B<$T>($value)'; | |
} | |
// Manually support some well-known types. | |
extension ListDeserialization<E> on List<E> { | |
static extends _DeserializableForList<E>; | |
} | |
class _DeserializableForList<E> implements Type, Deserializable<List<E>> { | |
List<E> readFrom(Reader r) { | |
final result = <E>[]; | |
final deserializable = Type.reify<E, Deserializable<E>>()!; | |
while (r.hasNext) result.add(deserializable.readFrom(r)); | |
return result; | |
} | |
} | |
extension StringDeserialization on String { | |
static extends _DeserializableForString; | |
} | |
class _DeserializableForString implements Type, Deserializable<String> { | |
String readFrom(Reader r) => r.readString(); | |
} | |
// Handle `writeTo` even with `String` and `List`. | |
void _doWriteTo(Writer w, Object? o) { | |
if (o is Serializable) { | |
o.writeTo(w); | |
} else if (o is String) { | |
w.writeString(o); | |
} else if (o is List) { | |
for (final element in o) { | |
_doWriteTo(w, element); | |
} | |
} else { | |
throw "The object $o of type ${o.runtimeType} is not Serializable"; | |
} | |
} | |
// --- Use it! | |
void main() { | |
final source1 = ['Hello, world!']; | |
final object1 = B<String>.readFrom(Reader(source1)); | |
print('Source: "$source1"'); | |
print('Deserialized: $object1'); | |
print('Reserialized:'); | |
object1.writeTo(Writer()); | |
final source2 = ['one', 'two']; | |
final object2 = B<List<String>>.readFrom(Reader(source2)); | |
print('Source: "$source2"'); | |
print('Deserialized: $object2'); | |
print('Reserialized:'); | |
object2.writeTo(Writer()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Same as 'traits_as_capable_type_objects_extension.dart', but desugared | |
such that the details of the semantics can be inspected. Running code. | |
*/ | |
// --- Glue classes. | |
class Writer { | |
void writeString(String s) => print('"$s"'); | |
void writeList<X extends Serializable>(List<X> xs) { | |
for (final x in xs) x.writeTo(this); | |
} | |
} | |
class Reader { | |
final List<String> strings; | |
var _index = 0; | |
Reader(this.strings); | |
bool get hasNext => _index < strings.length; | |
String readString() => strings[_index++]; | |
} | |
// --- Example code from the document 'Traits in Dart'. | |
// Single-line comments show code from the document, code which is | |
// not compilable uses /*...*/. | |
// trait Trait on Object { | |
// T0 method(T1 .. Tn) {...} // May use `Self`. | |
// } | |
/* | |
abstract class Trait<Self extends Object> { | |
T0 method(T1 .. Tn) {...} | |
} | |
late List<Trait> x; // OK. | |
late Trait t; // OK. | |
class B<T extends Trait> {...} // OK. | |
void foo<T extends Trait> {...} // OK. | |
*/ | |
// trait Serializable { | |
// void writeTo(Writer w); | |
// static Self readFrom(Reader r); | |
// } | |
abstract class Serializable { | |
void writeTo(Writer w); | |
} | |
abstract class Deserializable<Self> { | |
Self readFrom(Reader r); | |
} | |
// class B<T implements Serializable> { | |
// final T value; | |
// B(this.value); | |
// factory B.readFrom(Reader r) => B(T.readFrom(r)); | |
// void writeTo(Writer r) => value.writeTo(w); | |
// } | |
// // Provide implementation of Serializable for String | |
// impl Serializable for String { | |
// void writeTo(Writer w) => w.writeString(this); | |
// static String readFrom(Reader r) => r.readString(); | |
// } | |
// // Provide implementation of Serializable for any List<E>, | |
// // such that E has an implementation of Serializable. | |
// impl<E implements Serializable> Serializable for List<E> { | |
// void writeTo(Writer w) => w.writeList(this, (e) => e.writeTo(w)); | |
// } | |
class B<T> implements Serializable /*static implements Deserializable<B<T>>*/ { | |
final T value; | |
B(this.value); | |
factory B.readFrom(Reader r) => | |
B(emulateFeature<T, Deserializable<T>>().readFrom(r)); | |
@override | |
void writeTo(Writer w) => _doWriteTo(w, value); | |
@override | |
String toString() => 'B<$T>($value)'; | |
} | |
// Implicitly induced by the `static implements` clause on `B`. | |
class ReifiedTypeForB<T> implements Type, Deserializable<B<T>> { | |
B<T> readFrom(Reader r) => B.readFrom(r); | |
} | |
// Manually support some well-known types. | |
extension ListDeserialization<E> on List<E> { | |
/*static extends _DeserializableForList<E>>;*/ | |
} | |
class _DeserializableForList<E> implements Type, Deserializable<List<E>> { | |
List<E> readFrom(Reader r) { | |
final result = <E>[]; | |
final deserializable = emulateFeature<E, Deserializable<E>>(); | |
while (r.hasNext) result.add(deserializable.readFrom(r)); | |
return result; | |
} | |
} | |
// Implicitly induced by the `static extends` clause above. | |
class ListDeserialization_ReifiedTypeForList<E> | |
extends _DeserializableForList<E> {} | |
extension StringDeserialization on String { | |
/*static extends _DeserializableForString;*/ | |
} | |
class _DeserializableForString implements Type, Deserializable<String> { | |
String readFrom(Reader r) => r.readString(); | |
} | |
class StringDeserialization_ReifiedTypeForString | |
extends _DeserializableForString {} | |
// Handle `writeTo` even with `String` and `List`. | |
void _doWriteTo(Writer w, Object? o) { | |
if (o is Serializable) { | |
o.writeTo(w); | |
} else if (o is String) { | |
w.writeString(o); | |
} else if (o is List) { | |
for (final element in o) { | |
_doWriteTo(w, element); | |
} | |
} else { | |
throw "The object $o of type ${o.runtimeType} is not Serializable"; | |
} | |
} | |
// Emulate the proposed feature by replacing the given reified type | |
// by an instance that has the members that the feature would add. | |
// We're only covering the cases that actually occur, because the general | |
// case cannot be expressed in Dart. | |
R emulateFeature<X, R>() { | |
Never die() => throw "The type $X is not deserializable"; | |
final replacement = switch (X) { | |
const (B<String>) => ReifiedTypeForB<String>(), | |
const (B<List<String>>) => ReifiedTypeForB<List<String>>(), | |
const (String) => StringDeserialization_ReifiedTypeForString(), | |
const (List<String>) => ListDeserialization_ReifiedTypeForList<String>(), | |
_ => null, | |
} as R?; | |
// The feature not available so `X is Deserializable<X>` is false. | |
if (replacement != null) return replacement; else die(); | |
} | |
// --- Use it! | |
void main() { | |
final source1 = ['Hello, world!']; | |
final object1 = B<String>.readFrom(Reader(source1)); | |
print('Source: "$source1"'); | |
print('Deserialized: $object1'); | |
print('Reserialized:'); | |
object1.writeTo(Writer()); | |
final source2 = ['one', 'two']; | |
final object2 = B<List<String>>.readFrom(Reader(source2)); | |
print('Source: "$source2"'); | |
print('Deserialized: $object2'); | |
print('Reserialized:'); | |
object2.writeTo(Writer()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Variant of 'traits_as_capable_type_objects.dart' that special cases | |
certain well-known classes. This implies that the bounds `Serializable` | |
and `Deserializable<_>` are not used. Instead, it is detected at run | |
time whether a given object has those types (which allows for using the | |
members of that interface) or it is one of the special cased types (in | |
which case that particular type is again special cased). | |
*/ | |
// --- Glue classes. | |
class Writer { | |
void writeString(String s) => print('"$s"'); | |
void writeList<X extends Serializable>(List<X> xs) { | |
for (final x in xs) x.writeTo(this); | |
} | |
} | |
class Reader { | |
final List<String> strings; | |
var _index = 0; | |
Reader(this.strings); | |
bool get hasNext => _index < strings.length; | |
String readString() => strings[_index++]; | |
} | |
abstract class Serializable { | |
void writeTo(Writer w); | |
} | |
abstract class Deserializable<Self> { | |
Self readFrom(Reader r); | |
} | |
class B<T> implements Serializable static implements Deserializable<B<T>> { | |
final T value; | |
B(this.value); | |
factory B.readFrom(Reader r) => B(_getDeserializable<T>().readFrom(r)); | |
@override | |
void writeTo(Writer w) => _doWriteTo(w, value); | |
@override | |
String toString() => 'B<$T>($value)'; | |
} | |
class ReifiedTypeForB<T> implements Type, Deserializable<B<T>> { | |
B<T> readFrom(Reader r) => B.readFrom(r); | |
} | |
// Assume `List<E static extends CallWithOneTypeArgument<E>>`. | |
abstract class CallWithTypeArguments { | |
int get numberOfTypeArguments; | |
R callWithTypeArgument<R>(int number, R Function<X>() callback); | |
} | |
abstract class CallWithOneTypeArgument<E> implements CallWithTypeArguments { | |
int get numberOfTypeArguments => 1; | |
R callWithTypeArgument<R>(int index, R Function<X>() callback) { | |
if (index != 1) { | |
throw ArgumentError("Index 1 expected, got $index"); | |
} | |
return callback<E>(); | |
} | |
} | |
// Manually support some well-known types. | |
class DeserializableForString implements Deserializable<String> { | |
String readFrom(Reader r) => r.readString(); | |
} | |
class DeserializableForList<E> implements Deserializable<List<E>> { | |
List<E> readFrom(Reader r) { | |
final result = <E>[]; | |
final deserializable = _getDeserializable<E>(); | |
while (r.hasNext) result.add(deserializable.readFrom(r)); | |
return result; | |
} | |
} | |
typedef ListOfString = List<String>; // Can be used as an expression. | |
Deserializable<X> _getDeserializable<X>() { | |
Never die() => throw "The type $X is not deserializable"; | |
final Object reifiedType = X; | |
if (reifiedType is Deserializable<X>) return reifiedType; | |
if (X == String) { | |
return DeserializableForString() as Deserializable<X>; | |
} if (<X>[] is List<List>) { | |
return X.callWithTypeArgument(1, <E>() => DeserializableForList<E>()) | |
as Deserializable<X>; | |
} else { | |
die(); | |
} | |
} | |
// Handle `writeTo` even with `String` and `List`. | |
void _doWriteTo(Writer w, Object? o) { | |
if (o is Serializable) { | |
o.writeTo(w); | |
} else if (o is String) { | |
w.writeString(o); | |
} else if (o is List) { | |
for (final element in o) { | |
_doWriteTo(w, element); | |
} | |
} else { | |
throw "The object $o of type ${o.runtimeType} is not Serializable"; | |
} | |
} | |
// --- Use it! | |
void main() { | |
final source1 = ['Hello, world!']; | |
final object1 = B<String>.readFrom(Reader(source1)); | |
print('Source: "$source1"'); | |
print('Deserialized: $object1'); | |
print('Reserialized:'); | |
object1.writeTo(Writer()); | |
final source2 = ['one', 'two']; | |
final object2 = B<List<String>>.readFrom(Reader(source2)); | |
print('Source: "$source2"'); | |
print('Deserialized: $object2'); | |
print('Reserialized:'); | |
object2.writeTo(Writer()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Same as 'traits_as_capable_type_objects_pragmatic.dart', but desugared | |
such that the details of the semantics can be inspected. Running code. | |
*/ | |
// --- Glue classes. | |
class Writer { | |
void writeString(String s) => print('"$s"'); | |
void writeList<X extends Serializable>(List<X> xs) { | |
for (final x in xs) x.writeTo(this); | |
} | |
} | |
class Reader { | |
final List<String> strings; | |
var _index = 0; | |
Reader(this.strings); | |
bool get hasNext => _index < strings.length; | |
String readString() => strings[_index++]; | |
} | |
// --- Example code from the document 'Traits in Dart'. | |
// Single-line comments show code from the document, code which is | |
// not compilable uses /*...*/. | |
// trait Trait on Object { | |
// T0 method(T1 .. Tn) {...} // May use `Self`. | |
// } | |
/* | |
abstract class Trait<Self extends Object> { | |
T0 method(T1 .. Tn) {...} | |
} | |
late List<Trait> x; // OK. | |
late Trait t; // OK. | |
class B<T extends Trait> {...} // OK. | |
void foo<T extends Trait> {...} // OK. | |
*/ | |
// trait Serializable { | |
// void writeTo(Writer w); | |
// static Self readFrom(Reader r); | |
// } | |
abstract class Serializable { | |
void writeTo(Writer w); | |
} | |
abstract class Deserializable<Self> { | |
Self readFrom(Reader r); | |
} | |
// class B<T implements Serializable> { | |
// final T value; | |
// B(this.value); | |
// factory B.readFrom(Reader r) => B(T.readFrom(r)); | |
// void writeTo(Writer r) => value.writeTo(w); | |
// } | |
// // Provide implementation of Serializable for String | |
// impl Serializable for String { | |
// void writeTo(Writer w) => w.writeString(this); | |
// static String readFrom(Reader r) => r.readString(); | |
// } | |
// // Provide implementation of Serializable for any List<E>, | |
// // such that E has an implementation of Serializable. | |
// impl<E implements Serializable> Serializable for List<E> { | |
// void writeTo(Writer w) => w.writeList(this, (e) => e.writeTo(w)); | |
// } | |
class B<T> implements Serializable /*static implements Deserializable<B<T>>*/ { | |
final T value; | |
B(this.value); | |
factory B.readFrom(Reader r) => B(_getDeserializable<T>().readFrom(r)); | |
@override | |
void writeTo(Writer w) => _doWriteTo(w, value); | |
@override | |
String toString() => 'B<$T>($value)'; | |
} | |
class ReifiedTypeForB<T> implements Type, Deserializable<B<T>> { | |
B<T> readFrom(Reader r) => B.readFrom(r); | |
} | |
// Assume that `List` has `<E static extends CallWithOneTypeArgument<E>>`. | |
abstract class CallWithTypeArguments { | |
int get numberOfTypeArguments; | |
R callWithTypeArgument<R>(int number, R Function<X>() callback); | |
} | |
abstract class CallWithOneTypeArgument<E> implements CallWithTypeArguments { | |
int get numberOfTypeArguments => 1; | |
R callWithTypeArgument<R>(int index, R Function<X>() callback) { | |
if (index != 1) { | |
throw ArgumentError("Index 1 expected, got $index"); | |
} | |
return callback<E>(); | |
} | |
} | |
class ReifiedTypeForList<E> extends CallWithOneTypeArgument<E> | |
implements Type {} | |
// Manually support some well-known types. | |
class DeserializableForString implements Deserializable<String> { | |
String readFrom(Reader r) => r.readString(); | |
} | |
class DeserializableForList<E> implements Deserializable<List<E>> { | |
List<E> readFrom(Reader r) { | |
final result = <E>[]; | |
final deserializable = _getDeserializable<E>(); | |
while (r.hasNext) result.add(deserializable.readFrom(r)); | |
return result; | |
} | |
} | |
typedef ListOfString = List<String>; // Can be used as an expression. | |
Deserializable<X> _getDeserializable<X>() { | |
Never die() => throw "The type $X is not deserializable"; | |
Deserializable<X>? deserializable = emulateFeatureForDeserializable<X>(); | |
if (deserializable != null) return deserializable; | |
if (X == String) { | |
return DeserializableForString() as Deserializable<X>; | |
} if (<X>[] is List<List>) { | |
final CallWithTypeArguments? caller = | |
emulateFeatureForCallWithTypeArguments<X>(); | |
if (caller == null) die(); | |
return caller.callWithTypeArgument( | |
1, | |
<E>() => DeserializableForList<E>() | |
) as Deserializable<X>; | |
} else { | |
die(); | |
} | |
} | |
// Handle `writeTo` even with `String` and `List`. | |
void _doWriteTo(Writer w, Object? o) { | |
if (o is Serializable) { | |
o.writeTo(w); | |
} else if (o is String) { | |
w.writeString(o); | |
} else if (o is List) { | |
for (final element in o) { | |
_doWriteTo(w, element); | |
} | |
} else { | |
throw "The object $o of type ${o.runtimeType} is not Serializable"; | |
} | |
} | |
// Emulate the proposed feature by replacing the given reified type | |
// by an instance that has the members that the feature would add. | |
// We're only covering the cases that actually occur, because the general | |
// case cannot be expressed in Dart. | |
Deserializable<X>? emulateFeatureForDeserializable<X>() => switch (X) { | |
const (B<String>) => ReifiedTypeForB<String>(), | |
const (B<List<String>>) => ReifiedTypeForB<List<String>>(), | |
_ => null, | |
} as Deserializable<X>?; | |
CallWithTypeArguments? emulateFeatureForCallWithTypeArguments<X>() { | |
return switch (X) { | |
const (List<String>) => ReifiedTypeForList<String>(), | |
_ => null, | |
}; | |
} | |
// --- Use it! | |
void main() { | |
final source1 = ['Hello, world!']; | |
final object1 = B<String>.readFrom(Reader(source1)); | |
print('Source: "$source1"'); | |
print('Deserialized: $object1'); | |
print('Reserialized:'); | |
object1.writeTo(Writer()); | |
final source2 = ['one', 'two']; | |
final object2 = B<List<String>>.readFrom(Reader(source2)); | |
print('Source: "$source2"'); | |
print('Deserialized: $object2'); | |
print('Reserialized:'); | |
object2.writeTo(Writer()); | |
} |
Added a version (*_extension*.dart
) which shows how the proposed extension
mechanism (see dart-lang/language#4200, section 'More capable type objects as an extension') can be used to equip String
and List
with the ability to deserialize, even though we don't have edit rights to those classes.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I explored the relationship between the approach taken in the document 'Traits for Dart' and the approach which is proposed in 'More capable type objects', illustrated with code in this gist.
It consists of two separate approaches, each of which has a version that uses the proposed feature plus a version which contains code which expressed using current Dart (emulating the special cases of the feature which are actually used in the example).
In the first approach, all type parameters are explicitly declared to satisfy the constraints needed in order to know statically that the objects support
Serializable
(so they can dovoid writeTo(Writer)
) and the reified types satisfyDeserializable<T>
for someT
(such that they can doT.readFrom(Reader)
). This implies that we can't use system provided types likeString
andList
, because we can't edit those classes such that this is true. So we're using wrapper classesString2
andList2
in order to ensure that the wrapper classes areSerializable
andDeserializable<T>
as needed.This approach is used in
traits_as_capable_type_objects.dart
, andtraits_as_capable_type_objects_desugared.dart
shows how the former will work using an emulation of the feature which is possible in the current version of Dart.In the second approach, type parameters are not constrained (that is, they may or may not have a type argument that
static implements Deserializable<T>
for anyT
, and they may also not be a subtype ofSerializable
). This allows us to include system provided types likeString
andList
in the data structure which is being serialized or the type which is being used to guide a deserialization.Consequently, the second approach relies on run-time type queries (like
value is Serializable
) in order to determine whether or not a given object or type as opted in to the serialization or deserialization protocol. If they have indeed opted in then the code is just as simple as it was in approach 1. Otherwise, it is tested using a run-time type query whether the given object is one of the well-known special cases (here:String
andList
). If this is true then each special class is handled directly. If it is not true then an exception is thrown.The second approach is used in
traits_as_capable_type_objects_pragmatic.dart
, andtraits_as_capable_type_objects_pragmatic_desugared.dart
shows how the former will work using an emulation of the feature which is possible in the current version of Dart.I expect that the second approach is going to be more realistic, because even serialization is probably not going to be important enough to justify that
String
andList
depend on it.