ianmackenzie / elm-geometry

2D/3D geometry package for Elm
Mozilla Public License 2.0
183 stars 26 forks source link

Rethink 'with' naming pattern for constructors? #140

Open ianmackenzie opened 3 years ago

ianmackenzie commented 3 years ago

Several elm-geometry constructors use a similar naming pattern using with as part of the name, e.g.:

Circle2d.withRadius : Quantity Float units -> Point2d units coordinates -> Circle2d units coordinates
Axis3d.withDirection : Direction3d coordinates -> Point3d units coordinates -> Axis3d units coordinates
Vector2d.withLength : Quantity Float units -> Direction2d coordinates -> Vector2d units coordinates

These are reasonably concise (e.g. compare Vector2d.withLength to Vector2d.fromLengthAndDirection) and work well with partial application; for example, you can create a bunch of circles with the same radius at different points using

List.map (Circle2d.withRadius (Length.centimeters 5)) listOfPoints

However, there are some downsides:

For the next major release of elm-geometry, it may be worth reconsidering this pattern and seeing if there's an alternative that is more clear.

MartinSStewart commented 3 years ago

I think your suggestion in Slack to use Vector2d.fromLengthAndDirection seems like a good approach. You mentioned drawbacks being it's verbose and doesn't work well with piping but I think the verbosity is made up for due to it being more discoverable and the piping issue doesn't seem like an issue to me since creating a vector is not as often used in the middle of a pipe.

I think it would be ideal if I could write "ModuleName.from" and have my editor be able to list all the constructors for that type. Right now, sometimes it's from*, or with*, or something else and I need to browse all the functions in a module to make sure I haven't missed something.

ianmackenzie commented 3 years ago

Never mentioned piping, I mentioned partial application 🙂 Piping is one use of partial application, but I think it can also be useful when doing things like mapping over Lists (as in the example), Maybes, JSON Decoders etc.

Your point is well taken, though - looking through my code, I think most uses of Vector3d.withLength, Circle2d.withRadius etc. would arguably be clearer as Vector3d.fromLengthAndDirection, Circle2d.fromCenterPointAndRadius etc. I guess I could add the new names and see how they feel, then deprecate the old ones and eventually remove them in the next major release.

Ooh, and I do really like these somewhat-weird constructors, I use them quite a bit:

Vector2d.from : Point2d units coordinates -> Point2d units coordinates -> Vector2d units coordinates
Vector3d.from : Point3d units coordinates -> Point3d units coordinates -> Vector3d units coordinates
LineSegment2d.from : Point2d units coordinates -> Point2d units coordinates -> LineSegment2d units coordinates
LineSegment3d.from : Point3d units coordinates -> Point3d units coordinates -> LineSegment3d units coordinates
Arc2d.from : Point2d units coordinates -> Point2d units coordinates -> Angle -> Arc2d units coordinates

I guess those still count as "starting with from", though 😛

ianmackenzie commented 3 years ago

A couple questions/thoughts (partially just thinking to myself here, but happy to take suggestions):

MartinSStewart commented 3 years ago

To me Point.fromXY is worth it for consistency (even if Point2d.xy is otherwise a good name).

Not sure what I think about the rest. Lots of tricky decisions!

I think the from constructors are fine. Most of them create types in a "canonical" way so it makes sense that they are just "from".

ianmackenzie commented 3 years ago

Could also just rename xy to fromCoordinates - even longer (I think I remember you saying you were happy to move away from that name a while ago!) but pretty consistent with xCoordinate, yCoordinate and coordinates.

MartinSStewart commented 3 years ago

Yeah, for me xy is a function I use often enough that I find fromCoordinates to be excessively long. fromXY isn't too bad though.