The answer doesn’t necessarily have to be statically known, just ensure that the edge case
is handled. computePlane() can return Maybe, where Nothing is returned in the colinear case.
This seems not that different from throwing an exception, except the caller can’t accidentally forget to deal with the colinear case, there must be code to handle the Nothing case.
But perhaps the question you’re asking is “but how can the writer of toPlane() know THEY did the right thing”. There’s of course no solution to logic errors (function subtract(a,b) { return a + b } will get by most type systems, short of having the type system re-encode the function itself, at which point it’s just correctness through redundancy — unless both the type AND function are wrong!).
However, you COULD protect against future people breaking your implementation by doing something analogous time weak_ptr’s implementation and flipping the Maybe to the input vs the output). So for example, you could make a type NonColinearSet, and have the “constructor” take a PointSet, but return a Maybe, so asNonColinearSet takes a PointSet and returns Nothing if the PointSet is colinear, or NonColinearSet if it isn’t. Now you computePlane() function can take a NonColinearSet and return Plane (not Maybe) since it “knows” the input must be valid, so it can ignore edge cases internally, at the cost of the caller having to deal with toNonColinearSet returning Nothing before calling it, since you won’t be allowed to transparent pass the result of asNonColinearSet(pointSet) to computePlane since the types Wong match (Maybe vs. NonColinearSet).
maybeNonColinear = asNonColinearSet(pointSet);
case Nothing: alertUserBadDataOrWhatever();
case Just: return toPlane(maybeNonColinear.value)
Notice in both cases, ultimately the caller must do something in the edge case, which is what you want.