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.
acdg_foundations #
Recriação fiel — em Dart puro, sem nenhuma dependência — dos protocolos e tipos de valor fundamentais da stdlib do Swift. A meta é preservar a intenção e a consequência prática de cada construção, não só o nome.
| Swift | Aqui | O que você implementa | O que ganha de graça |
|---|---|---|---|
Optional<Wrapped> |
Optional (Some/None) |
— | map, flatMap, or/orElse (??), unwrapped (! com trap), match, ifSome, toNullable |
Equatable |
mixin Equatable |
isEqual |
operator == (relação de equivalência) |
Hashable: Equatable |
mixin Hashable on Equatable |
hash(Hasher) |
hashValue, hashCode, chave de Set/Map |
Comparable: Equatable |
mixin Comparable<T> on Equatable |
isLessThan |
<, <=, >, >=, compareTo (List.sort) |
Identifiable |
mixin Identifiable<ID> |
id |
identidade estável; + ObjectIdentifier, ReferenceIdentifiable |
Copyable |
interface Copyable<Self> |
copy() |
contrato de cópia independente |
~Copyable (move-only) |
base NonCopyable |
(opcional) onConsume |
borrow/consume/move com trap em use-after-consume |
BitwiseCopyable |
mixin BitwiseCopyable |
— | marcador de tipo trivial (memcpy) |
Escapable |
mixin Escapable |
— | marcador |
~Escapable (lifetime) |
base NonEscapable + borrowing(...) |
guard ensureInScope() |
trap se o valor "escapar" do escopo |
CustomStringConvertible |
mixin CustomStringConvertible |
description |
toString() delega para description |
CustomDebugStringConvertible |
mixin CustomDebugStringConvertible |
debugDescription |
usado por reflecting(...) |
LosslessStringConvertible |
mixin … on CustomStringConvertible + LosslessStringFactory |
description + call(String) |
round-trip T(x.description) == x |
CaseIterable |
mixin CaseIterable<Self> |
allCases |
em enums, equivale ao values nativo |
RawRepresentable |
mixin RawRepresentable<RawValue> + RawRepresentableFactory |
rawValue + call(raw) |
round-trip T(rawValue: x.rawValue) == x |
Os "init falíveis" do Swift (
init?(_:),init?(rawValue:)) viram objetos-fábrica invocáveis (LosslessStringFactory,RawRepresentableFactory), já que o Dart não permite exigir construtores em interfaces.String(describing:)/String(reflecting:)viram as funçõesdescribing(...)/reflecting(...)com a mesma ordem de prioridade.
Decisões de fidelidade #
Equatable≠Hashable: como no Swift,Equatablefornece só==. Quem dá hash (e habilitaSet/Map) éHashable.Comparable: só<é exigido; os demais operadores derivam exatamente como no Swift (a <= b ≡ !(b < a)). O nomeComparablesombreia o dodart:corede propósito — e conformamos a ele, entãoList.sortfunciona.Optional: o force-unwrap (unwrapped) lançaOptionalUnwrapError, espelhando o trap denil!.~Copyable/~Escapable: o Dart não tem move semantics nem lifetimes, então emulamos a consequência em tempo de execução (o compilador Swift faz em tempo de compilação): usar um valor consumido/escapado lança erro.
Initialization with Literals (ExpressibleBy*Literal) #
Os protocolos que, no Swift, deixam o compilador converter um literal diretamente
num tipo custom (let c: Celsius = 37.5). O Dart não tem conversão de literal
definível pelo usuário, então — como nos outros init — eles são reificados
como fábricas, preservando a consequência (construir o tipo a partir do literal):
final class CelsiusLiteral
implements ExpressibleByIntegerLiteral<Celsius>, ExpressibleByFloatLiteral<Celsius> {
const CelsiusLiteral();
@override Celsius integerLiteral(int v) => Celsius(v.toDouble());
@override Celsius floatLiteral(double v) => Celsius(v);
}
const CelsiusLiteral().floatLiteral(37.5); // Celsius(37.5)
Inclui a hierarquia de string (…UnicodeScalarLiteral ← …ExtendedGraphemeClusterLiteral
← ExpressibleByStringLiteral ← ExpressibleByStringInterpolation), o motor de
interpolação (StringInterpolationProtocol / DefaultStringInterpolation) que você
dirige na mão como o compilador faz com "Olá \(nome)!", e StaticBigInt
(literais inteiros de precisão arbitrária).
Encoding / Decoding (Codable) #
A arquitetura inteira do Codable do Swift: Encodable/Decodable/Codable,
Encoder/Decoder, os containers (keyed/unkeyed/single-value, com o split
…Protocol + box), CodingKey & cia., e EncodingError/DecodingError com
codingPath. Vem com coders concretos JSONEncoder/JSONDecoder e um
JSONSerialization (writer + parser) escrito do zero, sem dart:convert.
enum UserKey implements CodingKey {
id('id'), fullName('full_name'); // mapeia para chaves customizadas
const UserKey(this.stringValue);
@override final String stringValue;
@override int? get intValue => null;
}
final class User implements Codable {
const User(this.id, this.fullName);
final int id;
final String fullName;
@override
void encode(Encoder encoder) {
final c = encoder.container<UserKey>();
c.encode(id, forKey: UserKey.id);
c.encode(fullName, forKey: UserKey.fullName);
}
// `init(from:)` reificado como fábrica (Dart não exige construtores em interface):
static const Decodable<User> decoder = _UserDecoder();
}
final json = const JSONEncoder().encodeToString(const User(7, 'Gabriel'));
// {"id":7,"full_name":"Gabriel"}
final user = const JSONDecoder().decodeFromString(User.decoder, json);
Adaptações fiéis às limitações do Dart: init(from:)/init?(...) viram
fábricas reificadas (Decodable<T>); sem overload por tipo, encode recebe
Object? e o decode usa primitivos tipados (decodeInt/decodeString…) + o
genérico decode<T>(Decodable<T>, forKey:) (a fábrica no lugar do T.self).
Optional Binding (if let / guard let / switch) #
As estruturas de controle que vinculam condicionalmente o valor desempacotado de
um Optional:
// if let value = opt { ... } else { ... }
opt.ifLet((v) => use(v), orElse: () => semValor());
// if let value = opt, where(value) { ... }
opt.ifLet((v) => use(v), where: (v) => v > 5);
// guard let value = opt else { <SAI do escopo> }
final v = opt.guardLet(orElse: () => throw ArgumentError('nil'));
// `v` é não-opcional daqui pra baixo.
// guard let a = optA, let b = optB else { return fallback }
final r = binding<int>(
(let) => let(optA) + let(optB), // qualquer None → orElse
orElse: () => 0,
);
// switch opt { case .some(let v): ...; case .none: ... } — nativo do Dart
final texto = switch (opt) {
Some(:final value) => 'some($value)',
None() => 'none',
};
O detalhe mais fiel é o guard let: o orElse tem tipo Never Function(),
então o compilador exige que esse ramo não retorne (precisa throw/sair) —
exatamente como o else de um guard no Swift é obrigado a deixar o escopo.
Tentar orElse: () => 0 é erro de compilação
(return_of_invalid_type_from_closure).
Performance e quando usar (vs Dart canônico) #
Comparação honesta, com benchmarks medidos (Apple M2, AOT). O Dart canônico é mais rápido em quase tudo; a vantagem desta lib é ergonomia/explicitude.
| Cenário | Canônico | acdg_foundations |
Razão |
|---|---|---|---|
Igualdade + Set |
~103 µs | ~130 µs | 1,3× mais lento |
Optional vs nullable |
~0,9 µs | ~37 µs | ~41× mais lento (boxing) |
| JSON encode | ~42 µs | ~81 µs | 1,9× mais lento |
| JSON decode | ~22 µs | ~60 µs | 2,6× mais lento |
Sort Comparable |
~313 µs | ~214 µs | 1,5× mais rápido |
Há ainda benchmarks de alocação/GC, payloads grandes e paralelismo com
isolates (com testes de corretude em test/concurrency_test.dart provando que
os value types são "Sendable" entre isolates). Resumo: o Optional troca memória
- GC por expressividade (evite em coleções grandes/loops quentes); isolates dão speedup real porém sublinear (limitado pela cópia de mensagem).
Regra de ouro: acdg_foundations para expressividade na borda do domínio;
Dart canônico (T?, dart:convert) para performance no núcleo e hot paths.
Detalhes, prós/contras por recurso e o guia completo em
doc/comparativo-dart-canonico.md. Rode os
benchmarks:
dart compile exe benchmark/comparison_benchmark.dart -o /tmp/bench && /tmp/bench
dart compile exe benchmark/allocation_benchmark.dart -o /tmp/alloc && /tmp/alloc
dart compile exe benchmark/concurrency_benchmark.dart -o /tmp/conc && /tmp/conc
🧭 Isolates valem a pena num app comum? Quase nunca. Payloads pequenos decodificam em <2 ms e o spawn + cópia de mensagem custa mais que o trabalho. Vá para isolate (via
compute()no Flutter) só quando: (1) Flutter com payload grande travando a UI — o ganho aqui é responsividade, não throughput (atéK=1ajuda) — ou (2) worker/CLI de lote (import/export/ ETL) e compute pesado (imagem, cripto). E nesse hot path usedart:convert, não esta lib. Detalhes na nota completa do doc.
Como usar em outro projeto #
Este package não está publicado no pub.dev. Importe por caminho ou git no
pubspec.yaml do projeto consumidor:
dependencies:
acdg_foundations:
path: ../acdg-foundations # caminho local
# ou:
# git:
# url: https://github.com/acdgbrasil/acdg-foundations.git
import 'package:acdg_foundations/acdg_foundations.dart';
final class Money with Equatable, Hashable, Comparable<Money> {
const Money(this.cents);
final int cents;
@override
bool isEqual(Money other) => cents == other.cents;
@override
void hash(Hasher hasher) => hasher.combine(cents);
@override
bool isLessThan(Money other) => cents < other.cents;
}
void main() {
final precos = <Money>{const Money(990), const Money(990)}; // Hashable → dedup
print(precos.length); // 1
final ordenado = [const Money(3), const Money(1), const Money(2)]..sort();
print(ordenado.map((m) => m.cents)); // (1, 2, 3)
print(const Optional.some(21).map((v) => v * 2).or(0)); // 42
}
Veja example/acdg_foundations_example.dart para os sete em ação.
Desenvolvimento #
dart pub get
dart analyze
dart test
dart run example/acdg_foundations_example.dart
Licença #
BSD-3-Clause © 2026 ACDG Technology.