6 ms·
Haskell is dense. I'm picking it up now[0], so this project was very useful for me to see how some "real world" Haskell is written. But yes, take for example
by daenz 7y ago
Haskell is dense. I'm picking it up now[0], so this project was very useful for me to see how some "real world" Haskell is written. But yes, take for example this function
getSocketAPIPort :: Int -> IO Int
getSocketAPIPort defaultPort = do
maybeEnvPort <- lookupEnv "socketPort"
case maybeEnvPort of
Nothing -> return defaultPort
Just port -> maybe (return defaultPort) return (readMaybe port)
It gets a port from an environment variable, if it can, otherwise a default port. Conceptually, this is easy to understand, but translating your understanding of that process to Haskell is not 1 to 1. For example, the last line alone:
Just port -> maybe (return defaultPort) return (readMaybe port)
You have to understand Maybe (and failure contexts), you have to understand that "return" does not return from a function like typical imperative languages, instead it wraps a type in a Monad (in this case, the IO Monad), and you also have to understand that most of those things on that line, including the "return" function, are parameters to the "maybe" function.
There's a lot to understand in terms of the underlying machinery of Haskell to be able to read its cryptic flow and syntax. But it is worth it imo.
0. http://learnyouahaskell.com http://learnyouahaskell.com
- hombre_fatal 7y agoThough notice that you picked literally the most trivial example in the entire codebase that anyone can understand without explanation. Elm is onto something with its obsession with simplicity and lack of features. I go back to old Haskell code and have to completely recredentialize in Haskell before I remember what's going on. I return to old Elm code and need very little ramp up. I'm not making an "Elm > Haskell" argument, I just think experimentation with simplicity does this family of languages a favor.
- jecxjo 7y agoDo you have a better example from this code base? I'm looking but unfortunately I'm not seeing anything that isn't either trivial or just a lot of monad stack handling.
- hombre_fatal 7y agoBetter examples of how Haskell can make the OP's eyes glaze over? Basically the rest of it.
- jscholes 7y ago> Though notice that you picked literally the most trivial example in the entire codebase that anyone can understand without explanation. I needed the explanation to understand what the code snippet was doing. Careful with those generalisations.
- hombre_fatal 7y agoWell, non-programmers are even more helpless in understanding the code, but that's just not the point I'm making. Let's not require everyone to couch every statement in disclaimers, especially such an ancillary one. And you surely can understand that code is trying to read a port or use a default one.
- jscholes 7y ago> And you surely can understand that code is trying to read a port or use a default one. In programming languages which use paradigms I'm more familiar with, yes. In Haskell, not so much, which is the entire point of this thread.
- hombre_fatal 7y ago> In Haskell, not so much, which is the entire point of this thread. Yes, that's my point, too. I'm not sure what you're arguing with. That you don't understand even the most trivial snippet in the code base is only a point in my favor. You're picking beef with an irrelevant detail and confusing it for disagreement. Remember, I was replying to someone who is trying to show that Haskell isn't so hard once you break down a snippet. I pointed out that the snippet was the most trivial selection they could have picked, that the rest of the code is even harder so it's not a very big consolation. You chimed in that even the simplest snippet was still alien to you.
- daenz 7y ago>Remember, I was replying to someone who is trying to show that Haskell isn't so hard once you break down a snippet. That's not what I was trying to show, and I'm not sure how you got that sense. I was trying to show that even the most simple snippet requires a lot of background knowledge to understand.
- verttii 7y agoOn the other hand, Haskell's strength is its abstraction power. Elm intentionally lacks (some of) that punch, as do a lot of other languages. But since Elm is a DSL it can afford to cap the abstraction somewhere whereas Haskell is perhaps built for more complex problems than just a client facing web UI. Having learned both, I feel the abstraction level is capped too low on Elm, sadly.
- chowells 7y agoOuch. That function is actually written in a pretty convoluted and redundant way. getSocketAPIPort :: Int -> IO Int getSocketAPIPort defaultPort = do maybeEnvPort <- lookupEnv "socketPort" return . fromMaybe defaultPort $ maybeEnvPort >>= readMaybe That's starting to border on over-terse, so you could expand the bind operator into do notation if you wanted to spread it out a bit further. On the other hand, it's also starting to feel over-verbose, using do notation for only a single IO action. Maybe... getSocketAPIPort :: Int -> IO Int getSocketAPIPort defaultPort = fromMaybe defaultPort . (readMaybe =<<) <$> lookupEnv "socketPort" That might be going too far. But maybe it's what I'd write. Just depends on how much I expect to make this more complicated in the future. This form has the simplest flow to read. I mean... it's dense. Really dense. But it has the fewest total things going on, and it neatly divides into three interesting parts, easily understood in isolation, plumbed together with two common combinators. But it's also pretty rigid in structure. If you ever want to add other sources for finding the port or change the priorities of them, that form would need to be totally rewritten, and probably would end up back in do notation. But in every case, all the various return calls should be combined into one (or none, if you use fmap or <$>), and the fallback to the default should only be written once.
- llimllib 7y agotbf, I (as a person with some very basic haskell understanding) can more or less read the parent's code, but can't make heads or tails out of yours. "Convoluted and redundant" is in the eye of the beholder I guess
- chowells 7y agoIs it? There's no part of you that looks and that and wonders why it's stuffing return into every leaf of a branching structure instead of just leaving it at the root? That's just objectively redundant. And there's no part of you that's wondering why it's using nested branches to implement the railway oriented programming pattern? That's just objectively more convoluted than using the combinators that abstract that out and coalesce all the failure branches into one spot. My second version has an extra really nice property. It consists of three subexpressions that can be understood in totality in isolation from the rest of the code. It is compositional code of the sort we all claim we want to work with. What my code does have as a real downside is a much higher burden of knowledge to understand. You have to know much more of the contents of the base library. You have to be familiar with how idioms like the aforementioned railway oriented programming work. But that knowledge has its rewards. You get to reduce manual plumbing in your own code, replacing it with standard library plumbing. When you know Haskell, the standard plumbing fades into the background. I guess it's like what lispers talk about with their parenthesis. So yes, there is an additional burden in understanding my versions of the code. But that burden amortizes very nicely over a lifetime of getting the advantages of having all that plumbing just there when you need it.
- mrkeen 7y agoIf you've wandered in from another language and are wondering what it might look like in a less terse language, here's an attempt: static IO<Integer> getSocketApiPort(@NotNull final Integer defaultPort) { return lookupEnv("socketPort") .flatMap((Optional<String> maybeEnvPort) -> { if(!maybeEnvPort.isPresent()) { return IO.of(defaultPort); } else { String strEnvPort = maybeEnvPort.get(); Optional<Integer> envPort = readMaybe(strEnvPort); return IO.of(envPort.orElse(defaultPort)); } }); }
- deleted 7y ago[deleted]
- nemetroid 7y agoIt's some time since I last worked in Haskell (and I never worked on anything useful), but I would write the function this way: getSocketAPIPort :: Int -> IO Int getSocketAPIPort defaultPort = do maybeEnvPort <- lookupEnv "socketPort" return $ case maybeEnvPort of Nothing -> defaultPort Just port -> fromMaybe defaultPort (readMaybe port)
- zeendo 7y ago100% this is how I'd write it (except probably with pure instead of return). I don't get the desire to sprinkle bind throughout the other replies in this thread. I feel like it makes things less clear.
- empath75 7y agoin python: def getSocketAPIPort(defaultport): try: return os.environ["socketPort"] except KeyError: return defaultport
- daenz 7y agoClose, but values from os.environ are always string, and not guaranteed to be parsable numbers.
- icedchai 7y agoThis is a bit clearer: def getSocketAPIPort(defaultport): return int(os.getenv('socketPort', defaultport))
- daenz 7y agoCloser, but int can throw a ValueError. The original Haskell code defaulted to the defaultPort if the specified port could not be cast to an int.
- icedchai 7y agoI see. Though I think it's better to throw an error. I wouldn't want my app just coming up on default port if I had configured it incorrectly.
- KirinDave 7y agoIt might be in Python, but it's sorta oranges to apples for you to just not do the same thing your Rosetta code is supposed to do. What's the value of your exercise if you just redefine the problem to get rid of the tricky part?
- icedchai 7y agoI’d say the original code was wrong. anyway catching an exception isn’t tricky.
- ArminCherusker 7y agomonad transformers can help alot here: getSocketAPIPort :: Int -> IO Int getSocketAPIPort defaultPort = fromMaybe defaultPort <$> runMaybeT do envPort <- MaybeT $ lookupEnv "socketPort" MaybeT $ return $ readMaybe envPort
- enobayram 7y agohere's ho I'd write it: getSocketAPIPort :: Int -> IO Int getSocketAPIPort defaultPort = readWithDefault <$> lookupEnv "socketPort" where readWithDefault mbPort = fromMaybe defaultPort $ readMaybe =<< mbPort
- enobayram 7y agoThough I should add that it's in general against the Haskell spirit to write code against needlessly specific types and to combine unrelated functionality. So, I'd first look for a function or write one myself in some utils module that looks up and parses a value from an environment variable: readEnv :: Read a => String -> IO (Maybe a) readEnv var = (readMaybe =<<) <$> lookupEnv var Then the function in question becomes much easier (and probably unnnecessary too): getSocketAPIPort defaultPort = fromMaybe defaultPort <$> readEnv "socketPort"