|
|
|
|
|
by kingdomcome50
1782 days ago
|
|
I understand what the author is doing. I said this earlier but it bears repeating, the author seems to be more concerned with having a concrete type than simpler code. A reference to `Maybe Cache` is good enough (and preferred). The top-level of your program is precisely where you want to have the flexibility to deal with the above. Furthermore, my example is a much better illustration of the axiom ("Parse don't validate") than what the author is doing -- which is more like "Parse and validate". You need to clarify "continuously guard". Sure you have to invoke methods like: maybeCache >> useCache // map
instead of: maybeCache |> useCache // not sure how Haskell pipes
Is that too difficult? The `Maybe` monad is specifically designed so that you don't have to continuously guard against the possibility of the value not existing. That is, you can "map", "bind" and "apply" functions to the value as if it always exists (and it handles the situation when the value doesn't). I also included a `case` block within which you can be statically certain a value of type `Cache` is available if you really need it.The purpose of `Maybe` is to simplify code that needs to deal with a value that might not exist. Attempting to organize your code to avoid using `Maybe` is, by definition, going to be more cumbersome than simply leaning into the construct (that's what it's for!). It also better-illustrates how "parse don't validate" should work. Using an exception to guard against an invariant is... validating not parsing. You don't need to defend the author here. It's just a matter of fact the the code provided could be organized differently according to a more idiomatic usage of `Maybe`, and therefore a more illustrative example of their own point. The choice to exemplify something else is unfortunate and the thrust of this entire comment thread -- I felt like I had to say something now seeing that link a second time. |
|
> Really, a parser is just a function that consumes less-structured input and produces more-structured output. By its very nature, a parser is a partial function—some values in the domain do not correspond to any value in the range—so all parsers must have some notion of failure. Often, the input to a parser is text, but this is by no means a requirement, and parseNonEmpty is a perfectly cromulent parser: it parses lists into non-empty lists, signaling failure by terminating the program with an error message.
So the properties checked by the parser are reflected in the output type. Reifying these properties in the type is what allows the validation to be done once at the top level and avoided throughout the rest of the program. Your complaint about throwing exceptions is focusing on an irrelevant detail in a small example - yes this could have been moved into the main function but doesn't affect the overall behaviour.
However your argument that propagating Maybe values is more idiomatic than parsing into a more precise type is one I - and I assume most - static typing advocates would disagree with. Given the choice you would always prefer an 'a' over a 'Maybe a' since a Maybe represents a point of uncertainty which you would rather not have. As a result, having to chain this imprecision using various combinators is inherently more complex than not having to do so. Yes, using bind etc. is preferable to manually destructing Maybe values but avoiding Maybe is more preferable still.