Hacker News new | past | comments | ask | show | jobs | submit login

In addition to the other TypeScript examples, here's one that I would actually use. Now, it's not encoding the types not as positional, but named arguments via objects. Allows me to destructure them, for added clarity.

  type Circle = {x: number; y: number; r: number};
  type Rectangle = {x: number; y: number; w: number; h: number};
  
  type Shape = <T>(xs: {
      circle: (args: Circle) => T;
      rectangle: (args: Rectangle) => T;
  }) => T;
  
  function Circle(x: Circle): Shape {
      return ({circle}) => circle(x);
  }
  
  function Rectangle(x: Rectangle): Shape {
      return ({rectangle}) => rectangle(x);
  }
  
  const exampleCircle = Circle({x: 2, y: 1.4, r: 4.5});
  const exampleRectangle = Rectangle({x: 1.3, y: 3.1, w: 10.3, h: 7.7});
  
  const area = (shape: Shape): number =>
      shape({
          circle: ({r}: Circle) => Math.PI * r * r,
          rectangle: ({w, h}: Rectangle) => w * h,
      });
  
  console.log(area(exampleCircle));
  console.log(area(exampleRectangle));
/edit: Fixed formatting. /edit: Clarify something.



This feels like a pattern where the naming choices obscure the intent. The ‘Shape’ type doesn’t capture the essence of a ‘shape’ - it captures the essence of being ‘visitable by a visitor that knows about shapes’ or ‘able to handle shapes’. The things which are instances of Shape are functions that accept circles or rectangles, not actual circles or rectangles. So maybe call it ‘VisitableShape’, or ‘ShapeHandler’; and instead of calling its functions ‘circle’ and ‘rectangle’, call them ‘visitCircle’ or ‘handleCircle’...

Also, you seem to have created functions and types with the same names (Circle and Rectangle) which seems dangerous.


> The things which are instances of Shape are functions that accept circles or rectangles, not actual circles or rectangles.

The naming is confusing, but it's misled you in the opposite direction. Things that are instances of Shape are functions that accept handlers of circles or rectangles. The handlers, unfortunately, are named `circle` and `rectangle`. I would prefer `onCircle` and `onRectangle` in this context, because we're lacking the context of a true `match` expression to disambiguate the naming.

    type Shape = <T>(handlers: {
      onCircle: (args: Circle) => T;
      onRectangle: (args: Rectangle) => T;
    }) => T;
> Also, you seem to have created functions and types with the same names (Circle and Rectangle) which seems dangerous.

I think this is an idiom for defining a canonical constructor for a type. It's a little funky here because they're returning Shape, not Circle or Rectangle, and the latter are not subtypes of Shape. But it mostly tracks alongside the rest of the encoding.


Nah, the Circle function takes an instance of Circle and returns a Shape. It’s not a constructor of circles.

The ‘exampleCircle’ is not an instance of Circle, it’s an instance of Shape.

I like the ‘onCircle’ naming though.


You're speaking at the level of the code, while I'm speaking at the level of the domain. Neither of us are wrong, but we're talking past each other.

The Shape class itself is formally the sum of Circle and Rectangle -- you can think of this as a "prefer composition over inheritance" principle, but it's still a way of relating Circle to Shape. In particular, the Circle and Rectangle factories are canonical injections of these two classes into the Shape class. The naming of the factories is a little suspect, but if the only use of Circle and Rectangle is meant to be in the context of Shape, it mostly flies.

> Nah

This came across as excessively dismissive.


Agree, I intentionally didn't change the names to mirror the other examples in sibling comments as I meant to focus on the "named parameters" bit.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: