Skip to main content

acdg_foundations 0.1.0 copy "acdg_foundations: ^0.1.0" to clipboard
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',
    };
0
likes
150
points
75
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

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.

Repository (GitHub)
View/report issues

License

BSD-3-Clause (license)

More

Packages that depend on acdg_foundations