This is called "using a type system" and has nothing to do with parsing or validation. You just don't "get it" I suppose. I feel like we are talking right past each other. You are so hung up on the types that you are just failing to take in the essence of what's going on.
I'll say this one more time: the specific type returned by `getConfDirs` is completely irrelevant -- other than whether it is wrapped in `Maybe` or not (because this best-illustrates parsing vs. validation). The returned type is an implementation detail that is chosen by the author. It is not necessary or "required" that we parse a string into a list (read that again). It's really quite simple:
// this is parsing
getConfDirs :: Maybe Config
// this is validation
getConfDirs :: Config // might throw
How these functions are implemented is not relevant. The irony is that choosing a list representation makes for a good example of parsing precisely because we can't know how many elements are in the list. That is, it gives the author the opportunity to further-illustrate parsing by: // more parsing
maybeCacheDir <- (confDirs >>= first) // or second, third, fourth, etc.
The author's example code is NOT illustrating parsing. Period. They are essentially illustrating a constructor named `getConfigurationDirectories` -- which is the most classic case of validation imaginable (TS): // 1. This is their first example
class ConfigurationDirectories {
dirs: FilePath[];
constructor(env) {
let dirs = env("config").split(",");
if (dirs.length < 1) throw "Error!";
this.dirs = dirs;
}
get cacheDir() {
if (this.dirs.length < 1) throw "Cannot happen!";
return this.dirs[0];
}
}
// 2. This is their "fixed" example with all sorts of unnecessary "NonEmpty" gymnastics because they have chosen the wrong abstraction
type NonEmpty<T> = [T, ...T[]];
const eg: NonEmpty<string> = []; // error
class ConfigurationDirectories {
dirs: NonEmpty<FilePath>;
constructor(env) {
let dirs = env("config").split(",");
if (dirs.length < 1) throw "Error!";
this.dirs = dirs as NonEmpty<FilePath>;
}
get cacheDir() {
// now the compiler also knows dirs is not empty
return this.dirs[0];
}
}
// 3. This is BETTER than their "fixed" example because it is even simpler
class ConfigurationDirectories {
cacheDir: FilePath;
constructor(env) {
let dirs = env("config").split(",");
if (dirs.length < 1) throw "Error!";
this.cacheDir = dirs[0];
}
}
// usage
const config = new ConfigurationDirectories(getEnv());
const cache = initializeCache(config.cacheDir);
You see how the above translates to the author's code? Nothing above qualifies as "parsing". Their examples are defining a function with a guard that validates the input. The author then get confused and decides to go on this side-quest of how to trick the compiler because the first function wasn't actually accomplishing their goal (they have to validate twice!). But you know what would have avoided all of it? Parsing (i.e. actually using the types to simplify their code). Simple. Linear. Fail-safe. Parsing.I'm not really sure why you are dying on this hill. You can't "win" this argument. The best you can accomplish is to learn something yourself through this discussion. It's just a matter of fact that the author is (mis)using `Maybe` to the detriment of their examples. And that isn't an attack on the author! You needn't defend them. We've all written code that wasn't perfect. This is just another instance.
Maybe if the title were something like, "How to validate with static guarantees in Haskell" I wouldn't have said anything...
* I thought you were referring to my TS example (which does statically define the return-type to be non-empty)