freezed icon indicating copy to clipboard operation
freezed copied to clipboard

feat: generate proxies for shared methods implementation

Open mrverdant13 opened this issue 2 years ago • 2 comments

Currently, this implementation is valid.

@freezed
class Shape with _$Shape {
  const Shape._();

  const factory Shape.square({
    required double side,
  }) = Square;

  const factory Shape.circle({
    required double radius,
  }) = Circle;

  double get area => when(
        square: (side) => pow(side, 2).toDouble(),
        circle: (radius) => pi * pow(radius, 2),
      );
}

extension ExtendedCircle on Circle {
  double get diameter => 2 * radius;
}

void main() {
  const square = Shape.square(side: 2);
  final squareArea = square.area;

  const circle = Shape.circle(radius: 5);
  final circleArea = circle.area;

  const shape = Shape._();
  final area = shape.area;
  final maybeDiameter = shape.whenOrNull(circle: (radius) => 2 * radius);
  if (circle is Circle) {
    final diameter = circle.diameter;
  }
}

However, if the implementation of the area getter were too complex or long, it would be handy to isolate it.

One alternative to accomplish this is by using top-level functions. However, a more straightforward option would be the use of mixins that implement this shared/common behaviour, that might be identified by freezed and exposed by the base class.

@freezed
class Shape with _$Shape {
  const Shape._();

  @With<SquareMixin>()
  const factory Shape.square({
    required double side,
  }) = Square;

  @With<CircleMixin>()
  const factory Shape.circle({
    required double radius,
  }) = Circle;

  double get area => $area;
  Shape scale(double scale) => $scale(scale);
}

mixin SquareMixin {
  double get side;

  double get area => pow(side, 2).toDouble();
  Square scale(double scale) => Square(side: scale * side);
}

mixin CircleMixin {
  double get radius;

  double get area => pi * pow(radius, 2);
  Circle scale(double scale) => Circle(radius: scale * radius);

  double get diameter => 2 * radius;
}

// GENERATED BY FREEZED vvvvvvvvvvvvvvvvvvvv

extension __$Shape on _$Shape {
  double get $area => map(
        square: (value) => value.area,
        circle: (value) => value.area,
      );

  Shape $scale(double scale) => map(
        square: (value) => value.scale,
        circle: (value) => value.scale,
      ).call(scale);
}

// GENERATED BY FREEZED ^^^^^^^^^^^^^^^^^^^^

void main() {
  const square = Shape.square(side: 2);
  final squareArea = square.area;

  const circle = Shape.circle(radius: 5);
  final circleArea = circle.area;

  const shape = Shape._();
  final area = shape.area;
  final maybeDiameter = shape.whenOrNull(circle: (radius) => 2 * radius);
  if (circle is Circle) {
    final diameter = circle.diameter;
  }
}

mrverdant13 avatar Jul 26 '22 03:07 mrverdant13

Hello!

I don't quite understand what you're trying to solve. Could you explain more in depth the problem?

rrousselGit avatar Jul 26 '22 04:07 rrousselGit

The mixins are already providing implementations for area and scale, so there's no need to create an extension. Your code will work as intended just by changing the Shape area/scale implementations to throw UnimplementedError() or similar.

(You can enforce that all shapes must provide implementations of area/scale by making Shape abstract, but then Freezed will output a warning informing you that declaring Freezed classes abstract is not needed anymore.)

jason-sch avatar Jul 28 '22 16:07 jason-sch

I have no plan to implement this. Closing :)

rrousselGit avatar May 24 '23 14:05 rrousselGit