5 ms·
So what stops anybody from constructing a Segment manually, bypassing mkSegment? Assuming that you can make Segment's constructor private, that's indeed a good
by bad_user 11y ago
So what stops anybody from constructing a Segment manually, bypassing mkSegment?
Assuming that you can make Segment's constructor private, that's indeed a good pattern sometimes, but leaving mkSegment as the only way to build a segment will make working with segments pretty inconvenient. Imagine that what you usually want to do with segments are intersections and unions and if the original segments already have this x < y invariant, then the algorithms, if correct, will also have it. Therefore forcing usage of mkSegment will be a pain in the ass, because then you have to deal with the Maybe monad everywhere.
As @pron is saying, this is still a runtime check. And the problem is, there is no need for it unless you receive outside input that needs to be processed. Otherwise the internal algorithms should always respect the contract anyway. Consider this ...
intersect :: Segment -> Segment -> Maybe Segment
What would a Nothing result mean? Would it mean that the 2 segments do not intersect, or would it mean that one of the segments was invalid? Treating the error in this signature is totally unneeded and forcing usage of mkSegment would do exactly that.
In Scala I sometimes use the mkSegment pattern that you described when receiving data from the outside (like from the database), but in this particular instance I prefer doing the following, because of the above reasoning (i.e. Segments should already be valid, input should be validated elsewhere, if we've got invalid segments then we need to fail as fast as possible):
case class Segment(x: Double, y: Double) {
require(x <= y)
}
And I find this to be totally fine, in addition to whatever the static type-system can pull off.
- the_af 11y agointersect :: Segment -> Segment -> Maybe Segment > What would a Nothing result mean? Would it mean that the 2 segments do not intersect, or would it mean that one of the segments was invalid? The way I see it, it can only mean that they do not intersect, and never that one of the segments was invalid. This is because a Segment is guaranteed to be valid by construction; after all, that's precisely the property you bought by using mkSegment!
- bad_user 11y agoYes but by using mkSegment in the implementation of intersect, you have to treat its Nothing result, even though it can never happen and so you either end up with this reflected in the signature of intersect itself, or you cheat in the implementation of intersect by assuming that Nothing will never happen, which would defeat the purpose of mkSegment.
- the_af 11y agoIt's a bit cumbersome, yes, but I don't think it defeats the purpose of mkSegment. Let's see: because mkSegment is the only way to construct a Segment, you know the arguments to intersect are valid. So there cannot be any doubt about the meaning of Maybe in the result; it simply cannot refer to invalid arguments. There is only one other possible meaning: that the segments don't intersect, which is the idiomatic use of Maybe (another reason why I wouldn't use Maybe in this case to signal an invalid argument: "which argument?"). Yes, this means that even if there is an intersection, mkSegment forces you to handle the None case, even though you know by construction this will never happen. It's annoying but nothing too terrible. I don't see how this is cheating; you are simply using a property that the type system is unable to catch... but this property exists nonetheless.