7 ms·
Can't comment on F#, but in Haskell you'd create a special datatype and use smart constructor which only allows to create a value which conforms the requirement
by wereHamster 11y ago
Can't comment on F#, but in Haskell you'd create a special datatype and use smart constructor which only allows to create a value which conforms the requirements:
data Segment = Segment Double Double
mkSegment :: Double -> Double -> Maybe Segment
mkSegment x y
| x < y = Just (Segment x y)
| otherwise = Nothing
There are things you can check at compile time, and for that F# and other strongly typed languages are great. And if that's impossible they can still force you to do these checks at runtime.
And with such great support for custom types, you can rely on complex invariants in large parts of your application. In the example above, if you have a `Segment`, you know that x < y, even if the compiler can't prove it.
- pron 11y agoThat's still a runtime check rather than a compile-time guarantee, and whatever the type does guarantee (i.e. constructed instances are OK) is identical to throwing an exception (or a checked exception if you want to guarantee handling) in an OO constructor.
- the_af 11y agoBut it's not identical to throwing an exception, that's the thing. The signature immediately makes it explicit you must deal with the possibility of failure; it's encoded in the type. You cannot "forget" to handle it the way you can forget to catch an exception. The closest to this are checked exceptions, which turned out to be a bad idea and most languages I'm aware of don't have them (and the consensus in the case of Java is that they were a mistake).
- pron 11y ago> The closest to this are checked exceptions, which turned out to be a bad idea But that's because lots of people actually used them and came to the conclusion that they didn't want to manually handle every exceptional case at the call-site. People realized that if an operation has two outcomes, but one of them would cause the entire computation to fail and that outcome happened only 1/1000 of cases, they'd rather just ignore it, and catch any exception up the stack. If many people used Either/Maybe they might come to the same conclusion. So we don't know today if Either/Maybe work well just as we didn't know in 1995 if checked exceptions work well -- simply because the pattern hasn't seen enough use yet (and I have no reason to believe that the conclusion would be any different, namely that a possible though unlikely outcome might want to be handled collectively not near the call-site).
- the_af 11y ago1- You seem to believe Either/Maybe are new ideas. They are not. It's an old pattern which has been used plenty and we have a pretty good idea of how they work. 2- Either/Maybe are not for when an outcome "will fail in 1/1000 of cases", so this is an example of how they are unlike exceptions. If what you said was true, it wouldn't make sense for a hybrid language like Scala to have both. Obviously Scala's designers don't consider exceptions and Either/Maybe "identical", do they? (And no, exceptions aren't only used for Java compatibility, though their rampant use is discouraged). 3- You can silence checked exceptions with reckless abandon, but you cannot silence a Maybe just as easily, without resorting to obviously frowned-upon practices; you either handle Maybes by using library functions that already know how to handle them properly, or you handle them manually and you are forced to think about the outcome for each case. You cannot simply say "I don't know how to handle the None case, so I'll leave it blank" (because it won't compile) or "I'll throw an exception" (because that's not how you use Maybe, to the point absolutely no-one uses them like that, unlike the case in Java). 4- I'm sure you know there is plenty of bad stuff about checked exceptions beyond the obvious "lazy/rookie Java programmers swept them under the rug and forgot about them". For example, checked exceptions are terrible for generic code, composable code. And there are more problems with them which don't happen with Either/Maybe. I truly don't see how you can seriously consider them "identical".
- pron 11y ago> You seem to believe Either/Maybe are new ideas. They are not. It's an old pattern which has been used plenty and we have a pretty good idea of how they work. I don't. I used them first over 15 years ago when Haskell was "the next big thing" and I learned it at university. Fact of the matter is that not enough code has been written with them to say that we know how effective they are. > If what you said was true, it wouldn't make sense for a hybrid language like Scala to have both. Obviously Scala's designers don't consider exceptions and Either/Maybe "identical", do they? I wouldn't take lessons from Scala. Scala has inheritance types, sum types, structural types and dynamic types, each can be (and is) used in isolation to solve almost all problems. If there's any language with lots of overlapping features, that would be Scala. After all, it's a language designed to study various approaches, not to recommend the "best" ones. > You can silence checked exceptions with reckless abandon, but you cannot silence a Maybe just as easily, without resorting to obviously frowned-upon practices You can silence checked exceptions by doing exactly what you'd be doing to handle Maybe: You need to explicitly handle it. The difference is that you can catch multiple exception types in one block. > For example, checked exceptions are terrible for generic code, composable code. They're not. They just don't work too well with Java's limited effect system of checked exceptions. As a matter of fact, "checked exceptions" are the basis for effect systems in languages like Koka, and are also the basis of transformations of Haskell-ish monads to type-safe nested continuations, which are more appropriate than monads (yet equally expressive and powerful) for imperative languages. Moreover, a system of checked exceptions composes much better than monads do. And you don't have to believe me. Phil Wadler says it[1] as does Oleg Kiselyov (who's trying to port the same idea back to monadic notation with free monads). This is my own treatment of the subject (which shows how continuations, of which exceptions are a special case, can do monadic composition without monad transformers, and provide type safety through checked exceptions): http://blog.paralleluniverse.co/2015/08/07/scoped-continuations/ http://blog.paralleluniverse.co/2015/08/07/scoped-continuati... > I truly don't see how you can seriously consider them "identical". It's not a question of consideration. Wadler and Filinsky proved them to be the same (each from a different direction). See the link to my post above for the exact references. Still, that's not to say that checked exceptions or Maybe are good approaches. We really don't have data on the subject. It basically comes down to personal preference. [1]: https://cs.au.dk/~hosc/local/LaSC-7-1-pp39-56.pdf https://cs.au.dk/~hosc/local/LaSC-7-1-pp39-56.pdf
- bad_user 11y agoSo 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.
- bad_user 11y agoOne other thing, try this in Haskel (GHC): round (1/0) I would have preferred if it failed with a runtime exception, instead of giving the result that it does.