5 ms·
This is the second time I've seen the link above. And while I agree with the premise, the author clearly does not understand how to properly use the `Maybe` mon
by kingdomcome50 5y ago
This is the second time I've seen the link above. And while I agree with the premise, the author clearly does not understand how to properly use the `Maybe` monad (a term that does not make an appearance!).
There is little use in wrapping a call in `Maybe` to then immediately unwrap the result on the next line. Doing so isn't really using the construct... One would expect the lines following the creation of `Maybe` to bind calls through the monad.
In the end I see almost no meaningful difference between their "Paying it forward" example and simply utilizing an `if` to check the result and throw. In essence the author is using a parse and validate approach!
- dharmaturtle 5y agoYou might try re-reading it with some charity - the example's purpose isn't to teach the `Maybe` monad, but to remove the redundant check. To go into what `bind` does would be a diversion from the main topic (parsing vs validating). FWIW SPJ has called this blog's author a "genius" so... I think they do know how `Maybe` works. https://gitlab.haskell.org/ghc/ghc/-/issues/18044#note_266170 https://gitlab.haskell.org/ghc/ghc/-/issues/18044#note_26617...
- kingdomcome50 5y agoBut `Maybe` is specifically designed to remove redundant checks for a value that may (or may not) be present! That's the whole point of the monad! It seems rather unfortunate this isn't highlighted (or at least illustrated) doesn't it? I generally agree with the premise of the post.
- travv0 5y agoLexi absolutely understands how to properly use the Maybe monad. What you're saying to do here is the exact opposite of what this post is advocating for. You're talking about pushing the handling of the Maybe till later and the post is all about the advantages of handling it upfront and not having to worry about it anymore. You might want to read it one more time.
- kingdomcome50 5y agoI understand. But what is purpose of `Maybe`? The reason one would reach to the above construct is precisely to offload (pushing to later) the handling of a value that may (or may not) be present at runtime such that a developer can write code assuming the value is always present and ignore the `Nothing` case. Sure you can unwrap it right away, but that isn't necessary because you could also just "bind" the next function call to the monad (which is more idiomatic to the construct). You never have to worry about that value in this case because... well... that's the benefit of using `Maybe`. I'm not super familiar with Haskell, but my sense is that the author is trying more to please the compiler (at a specific point in the program!) than simplify the logic. That is, they want a concrete value (`configDirs`) to exist in the body of `main` more than they want the cleanest representation of the problem in code.
- travv0 5y ago> But what is purpose of `Maybe`? In this case, it's to provide a better error message in case there's an empty list than `fromList` would provide. > You never have to worry about that value in this case because... well... that's the benefit of using `Maybe`. But you do, your entire program doesn't live in `Maybe` so at some point you have to check whether it's `Just a` or `Nothing`. Once again, the whole point of the post is to argue that getting out of the `Maybe` as close to parsing time as possible is preferable so you have a more specific type to work with after that. You also see right away what didn't parse instead of just knowing that something didn't parse, which is what would happen if you stayed in the `Maybe` monad for all your parsing.
- kingdomcome50 5y ago> your entire program doesn't live in `Maybe` Well... if your entire program is dependent on some input that may or may not exist at runtime... then it kind of does live in `Maybe`. I have no issue with unwrapping a `Maybe` to throw an exception. But I do find it a bit ironic that the post is about parsing instead of validating, that the perfect construct is right there to exemplify how it could be done, but the author then chooses to eschew it and instead show examples of how validation could look. The body of `main`, for example, could be refactored to something like: maybeInitialized <- (getConfigurationDirectories >>= head >> initializeCache) Which actually shows how `Maybe` can be used to simplify the system. If you want to unwrap the maybe at this point to throw, go for it! But the above is a much cleaner representation of the program than what author is trying to do (it's crystal clear how the cache might get initialized). I would expect "Parse don't validate" to be about how useful `Maybe` is to combine parsing logic into a functional flow vs. how validation leads to an ugly procedural approach.
- garethrowlands 5y agoI think you're referring to this part of the `getConfigurationDirectories` action, which has type `IO (NonEmpty FilePath)`: case nonEmpty configDirsList of Just nonEmptyConfigDirsList -> pure nonEmptyConfigDirsList Nothing -> throwIO $ userError "CONFIG_DIRS cannot be empty" The "meaningful difference" you're looking for is the type of `getConfigurationDirectories`. The previous version had type `IO [FilePath] `, which _doesn't_ guarantee any configuration directories at all. It did indeed check the results and throw. But it doesn't guarantee that all the `[FilePath]` values in the program have been checked. There are neither tests nor proofs in this code. In contrast, with the revised version, you can be certain anywhere you see a `NonEmpty FilePath` it is indeed non-empty. The code I've quoted that checks which case we have, is the only place that needs to handle that `Maybe`. Or maybe `main`, if we want to be more graceful. The author (I wouldn't say I know her but I know that much) does know how to chain maybes with bind but it's not necessary in this example code.
- kingdomcome50 5y agoMy point is that if you are not chaining `Maybe` then the utility of employing the construct is unobserved. The entire purpose of using `Maybe` is to relieve the client from the need to make checks at every call for a value that may (or may not) exist. If you intend to immediately "break out" of the monad and (even more specifically) throw an error, you might as well just use an `if`. I'm sure `main` could be written to "bind"/"map" `getConfigurationDirectories` with `nonEmpty`, `head`, and `initializeCache` in a way that puts the `throw` at the top-level (of course the above implementations may need to change as well). Unfortunately I'm not familiar enough with Haskell to illustrate it myself.
- lkitching 5y agoThe purpose of Maybe is to explicitly represent the possible non-existence of a value which in Haskell is the only option since there's no null value which inhabits every type. The existence of the monad instance is convenient but it's not fundamental. The type of getConfigurationDirectories could be changed to MaybeT IO (NonEmpty FilePath) to avoid the match but I don't think it would make such a small example clearer.