acdg_foundations 0.1.0
acdg_foundations: ^0.1.0 copied to clipboard
Recriação fiel, em Dart puro e sem dependências, dos protocolos e tipos de valor fundamentais da stdlib do Swift: Optional, Equatable, Hashable, Comparable, Identifiable, Copyable e Escapable.
example/acdg_foundations_example.dart
import 'package:acdg_foundations/acdg_foundations.dart';
/// Value object que conforma a `Equatable` + `Hashable` + `Comparable`, como
/// faria um `struct Money: Hashable, Comparable` no Swift.
final class Money with Equatable, Hashable, Comparable<Money> {
const Money(this.cents, this.currency);
final int cents;
final String currency;
@override
bool isEqual(Money other) =>
cents == other.cents && currency == other.currency;
@override
void hash(Hasher hasher) {
hasher.combine(cents);
hasher.combine(currency);
}
@override
bool isLessThan(Money other) {
assert(currency == other.currency, 'moedas diferentes não são comparáveis');
return cents < other.cents;
}
@override
String toString() => '${(cents / 100).toStringAsFixed(2)} $currency';
}
/// Entidade com identidade estável, como `struct Patient: Identifiable`.
final class Patient with Identifiable<String> {
const Patient(this.id, this.name);
@override
final String id;
final String name;
}
/// Recurso de posse única (`~Copyable`).
final class FileHandle extends NonCopyable {
FileHandle(this.path);
final String path;
@override
void onConsume() => print(' fechando $path');
}
/// View não-escapável sobre uma lista (`~Escapable`), à la `Span`.
final class Span extends NonEscapable {
Span(this._storage);
final List<int> _storage;
int sum() {
ensureInScope();
return _storage.fold(0, (acc, v) => acc + v);
}
}
void main() {
print('— Optional —');
const Optional<int> some = Optional.some(21);
const Optional<int> none = Optional.none();
print(some.map((v) => v * 2).or(0)); // 42
print(none.map((v) => v * 2).or(-1)); // -1
print(some.unwrapped); // 21
print('\n— Equatable / Hashable —');
final a = const Money(1990, 'BRL');
final b = const Money(1990, 'BRL');
print(a == b); // true
print(<Money>{a, b}.length); // 1 (dedup via Hashable)
print('\n— Comparable —');
final carrinho = [
const Money(500, 'BRL'),
const Money(100, 'BRL'),
const Money(300, 'BRL'),
]..sort(); // usa compareTo
print(carrinho.map((m) => m.toString()).toList());
print(const Money(100, 'BRL') < const Money(300, 'BRL')); // true
print('\n— Identifiable —');
const p = Patient('pat-001', 'Maria');
print('${p.name} -> ${p.id}'); // Maria -> pat-001
print('\n— ~Copyable (move-only) —');
final handle = FileHandle('/tmp/log.txt');
handle.borrow(() => print(' lendo ${handle.path}'));
handle.consume(); // dispara onConsume (fecha)
try {
handle.borrow(() => 'falha');
} on ConsumedValueError catch (e) {
print(' bloqueado: ${e.message}');
}
print('\n— ~Escapable (lifetime atrelado ao escopo) —');
late Span escaped;
final dentro = borrowing(Span([1, 2, 3]), (span) {
escaped = span; // "escapou" do escopo
return span.sum();
});
print(' dentro do escopo: $dentro'); // 6
try {
escaped.sum(); // uso fora do escopo
} on EscapeError catch (e) {
print(' bloqueado: ${e.message}');
}
print('\n— Optional Binding (if let / guard let / binding / switch) —');
// if let value = opt { ... } else { ... }
const Optional.some(7).ifLet(
(v) => print(' if let → vinculou $v'),
orElse: () => print(' if let → vazio'),
);
// if let value = opt, where(value) { ... }
const Optional.some(3).ifLet(
(v) => print(' if let+where → $v'),
where: (v) => v > 5,
orElse: () => print(' if let+where → 3 não passou no where (> 5)'),
);
// guard let name = opt else { throw } — o else é OBRIGADO a sair do escopo.
print(' guard let → ${saudacao(const Optional.some('Gabriel'))}');
try {
saudacao(const Optional.none());
} on ArgumentError catch (e) {
print(' guard let → bloqueado: ${e.message}');
}
// guard let a = .., let b = .. else { return fallback } (encadeado)
final soma = binding<int>(
(let) => let(const Optional.some(2)) + let(const Optional.some(3)),
orElse: () => -1,
);
final parcial = binding<int>(
(let) => let(const Optional.some(2)) + let(const Optional<int>.none()),
orElse: () => -1,
);
print(' binding → soma=$soma, comNone=$parcial');
// switch opt { case .some(let v): ...; case .none: ... } (nativo)
print(' switch → ${descreve(const Optional.some(9))} / '
'${descreve(const Optional<int>.none())}');
print('\n— String Representation (describing / reflecting / lossless) —');
const ponto = Ponto(3, 4);
print(' toString → $ponto'); // 3,4 (delega description)
print(' describing → ${describing(ponto)}'); // 3,4
print(' reflecting → ${reflecting(ponto)}'); // Ponto(x: 3, y: 4)
final volta = const PontoFactory().call(ponto.description);
print(' lossless → ${volta?.description} (round-trip)');
print('\n— Raw Representation (CaseIterable / RawRepresentable) —');
print(' CaseIterable → ${Direcao.norte.allCases.toList()}');
print(' rawValue → ${Naipe.copas.rawValue}');
print(' init?(raw:) → ${const NaipeFactory().call('♠')} / '
'${const NaipeFactory().call('?')}'); // Naipe.espadas / null
print('\n— Encoding / Decoding (Codable) —');
const pet = Pet(name: 'Rex', age: 3, tags: ['vacinado', 'castrado']);
final json = const JSONEncoder().encodeToString(pet);
print(' encode → $json');
final decoded = const JSONDecoder().decodeFromString(Pet.decoder, json);
print(' decode → ${decoded.name}, ${decoded.age} anos, ${decoded.tags}');
try {
const JSONDecoder().decodeFromString(Pet.decoder, '{"name":"Rex"}');
} on KeyNotFoundError catch (e) {
print(' erro → chave "${e.key.stringValue}" ausente '
'(em ${e.context.pathDescription})');
}
print('\n— Initialization with Literals —');
print(' int literal → ${const CelsiusLiteral().integerLiteral(37).degrees}');
print(' float literal → ${const CelsiusLiteral().floatLiteral(37.5).degrees}');
print(' array literal → '
'${const StackLiteral<int>().arrayLiteral([1, 2, 3]).items}');
// Interpolação dirigida na mão, como o compilador Swift faz com "Olá \(nome)!"
final interpolacao = DefaultStringInterpolation()
..appendLiteral('Olá, ')
..appendInterpolation('Gabriel')
..appendLiteral('!');
print(' interpolation → '
'${const GreetingLiteral().stringInterpolation(interpolacao).text}');
final big = StaticBigInt.parse('340282366920938463463374607431768211456');
print(' StaticBigInt → 2^128: bitWidth=${big.bitWidth}, palavra[2]=${big[2]}');
}
/// `ExpressibleByIntegerLiteral` + `ExpressibleByFloatLiteral`.
final class Celsius {
const Celsius(this.degrees);
final double degrees;
}
final class CelsiusLiteral
implements
ExpressibleByIntegerLiteral<Celsius>,
ExpressibleByFloatLiteral<Celsius> {
const CelsiusLiteral();
@override
Celsius integerLiteral(int value) => Celsius(value.toDouble());
@override
Celsius floatLiteral(double value) => Celsius(value);
}
/// `ExpressibleByArrayLiteral`.
final class Stack<E> {
const Stack(this.items);
final List<E> items;
}
final class StackLiteral<E> implements ExpressibleByArrayLiteral<Stack<E>, E> {
const StackLiteral();
@override
Stack<E> arrayLiteral(List<E> elements) => Stack(elements);
}
/// `ExpressibleByStringInterpolation` (refina `ExpressibleByStringLiteral`).
final class Greeting {
const Greeting(this.text);
final String text;
}
final class GreetingLiteral
with StringLiteralForwarding<Greeting>
implements ExpressibleByStringInterpolation<Greeting> {
const GreetingLiteral();
@override
Greeting stringLiteral(String value) => Greeting(value);
@override
Greeting stringInterpolation(DefaultStringInterpolation interpolation) =>
Greeting(interpolation.description);
}
/// Tipo `Codable`: implementa `Encodable` e expõe a fábrica `Decodable<Pet>`.
enum PetKey implements CodingKey {
name('name'),
age('age'),
tags('tags');
const PetKey(this.stringValue);
@override
final String stringValue;
@override
int? get intValue => null;
}
final class Pet implements Codable {
const Pet({required this.name, required this.age, required this.tags});
final String name;
final int age;
final List<String> tags;
@override
void encode(Encoder encoder) {
final c = encoder.container<PetKey>();
c.encode(name, forKey: PetKey.name);
c.encode(age, forKey: PetKey.age);
final tagsC = c.nestedUnkeyedContainer(forKey: PetKey.tags);
for (final tag in tags) {
tagsC.encode(tag);
}
}
static const Decodable<Pet> decoder = _PetDecoder();
}
final class _PetDecoder implements Decodable<Pet> {
const _PetDecoder();
@override
Pet decode(Decoder decoder) {
final c = decoder.container<PetKey>();
final name = c.decodeString(forKey: PetKey.name);
final age = c.decodeInt(forKey: PetKey.age);
final tagsC = c.nestedUnkeyedContainer(forKey: PetKey.tags);
final tags = <String>[];
while (!tagsC.isAtEnd) {
tags.add(tagsC.decodeString());
}
return Pet(name: name, age: age, tags: tags);
}
}
/// `CustomStringConvertible` + `CustomDebugStringConvertible` +
/// `LosslessStringConvertible`.
final class Ponto
with
CustomStringConvertible,
CustomDebugStringConvertible,
LosslessStringConvertible {
const Ponto(this.x, this.y);
final int x;
final int y;
@override
String get description => '$x,$y';
@override
String get debugDescription => 'Ponto(x: $x, y: $y)';
}
/// O `init?(_:)` de [Ponto], reificado.
final class PontoFactory implements LosslessStringFactory<Ponto> {
const PontoFactory();
@override
Ponto? call(String description) {
final p = description.split(',');
if (p.length != 2) return null;
final x = int.tryParse(p[0]);
final y = int.tryParse(p[1]);
return (x == null || y == null) ? null : Ponto(x, y);
}
}
/// `CaseIterable` — em Dart, `values` já é o `allCases`.
enum Direcao with CaseIterable<Direcao> {
norte,
sul,
leste,
oeste;
@override
Iterable<Direcao> get allCases => values;
}
/// `RawRepresentable` lastreado por `String`.
enum Naipe with RawRepresentable<String> {
copas('♥'),
ouros('♦'),
paus('♣'),
espadas('♠');
const Naipe(this.rawValue);
@override
final String rawValue;
}
/// O `init?(rawValue:)` de [Naipe], reificado.
final class NaipeFactory implements RawRepresentableFactory<Naipe, String> {
const NaipeFactory();
@override
Naipe? call(String rawValue) {
for (final n in Naipe.values) {
if (n.rawValue == rawValue) return n;
}
return null;
}
}
/// `guard let name = nome else { throw ... }`: desempacota e segue, ou sai.
/// Note que `n` é `String` (não-opcional) do `guardLet` pra baixo.
String saudacao(Optional<String> nome) {
final n = nome.guardLet(orElse: () => throw ArgumentError('nome ausente'));
return 'Olá, $n!';
}
/// `switch` sobre a `sealed Optional` — pattern matching nativo do Dart.
String descreve(Optional<int> opt) => switch (opt) {
Some(:final value) => 'some($value)',
None() => 'none',
};