Last active
January 4, 2024 14:59
-
-
Save eernstg/90377a2d4a923b665b1b7435e70bdf50 to your computer and use it in GitHub Desktop.
Example showing selective access to a private interface using extension types
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
// Main idea is "same object, different treatment". We create one | |
// `_Buffer` object and use it as a `WriteBuffer` or as a `ReadBuffer`. | |
// The two perspectives on the `_Buffer` yield unrelated types, | |
// different members, even different variance of the type parameter. | |
// A client _must_ use one or the other extension type, there is no | |
// way a caller outside this library can directly call any instance | |
// member of `_Buffer`. | |
class _Buffer<X> { | |
X? _value; | |
bool _occupied = false; | |
void _put(X newValue) { | |
if (_occupied) throw "Error: Cannot put!"; | |
_value = newValue; | |
_occupied = true; | |
} | |
X _get() { | |
if (!_occupied) throw "Error: Cannot get!"; | |
_occupied = false; | |
return _value!; | |
} | |
} | |
typedef _Inv<X> = X Function(X); | |
typedef WriteBuffer<X> = _WriteBuffer<X, _Inv<X>>; | |
extension type _WriteBuffer<X, Invariance extends _Inv<X>>._(_Buffer<X> it) { | |
_WriteBuffer(): this._(_Buffer<X>()); | |
ReadBuffer<X> get readBuffer => ReadBuffer._(it); | |
bool get isNotFull => !it._occupied; | |
void put(X newValue) => it._put(newValue); | |
} | |
extension type ReadBuffer<X>._(_Buffer<X> it) { | |
bool get isNotEmpty => it._occupied; | |
X get() => it._get(); | |
} |
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
import 'dart:math'; | |
import 'selective_access.dart'; | |
final random = Random(); | |
bool get perhaps => random.nextBool(); | |
void main() { | |
var writer = WriteBuffer<int>(); | |
var reader = writer.readBuffer; | |
for (int i = 0; i < 10; ++i) { | |
if (perhaps && writer.isNotFull) writer.put(i); | |
if (perhaps && reader.isNotEmpty) print(reader.get()); | |
} | |
if (reader.isNotEmpty) print(reader.get()); | |
// The WriteBuffer is invariant, the ReadBuffer is covariant. | |
// WriteBuffer<num> numWriter = writer; // Compile-time error. | |
ReadBuffer<num> numReader = reader; // OK. | |
writer.put(42); | |
print(numReader.get()); | |
// We can perform dynamic invocations, but only on `Object` members. | |
// In other words, the buffer interfaces can _only_ be used via the | |
// extension types. | |
dynamic d = writer; | |
d.toString(); | |
d._put(1); // Throws, no such member. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment