Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: I built a poker site with Haskell (github.com/therewillbecode)
157 points by zzeder on Aug 9, 2019 | hide | past | favorite | 118 comments



So, I know literally nothing about Haskell, and I would rate my knowledge of functional programming theory as 'beginner' at best, but I still get baffled every time when reading Haskell code that implements anything other than something trivial like a fibonacci sequence. The poker server code looks extremely tidy and well engineered, so that can't be the problem, but to me it's utterly incomprehensible. It seems like every expression implements 20 different things at the same time, which makes it really hard to decode what is going on.

Is it just me, or is this typical for all non-trivial Haskell code? I don't have any problems interpreting e.g. Clojure, or Javascript written in a functional style for that matter, but Haskell...


The nice thing about Haskell from a readability standpoint is to unpack what code does at any point, you only have to perform substitution (i.e. beta reduction) over and over. This is a key difference from most other programming languages, which require you to have a little VM in your head. "Localized reasoning" is what Haskellers call this.

To read it quickly, you do have to learn & internalize abstractions. Both a common set of them (the usual type classes) & abstractions custom-built in your project. Abstractions in Haskell tend to be true abstractions & not encapsulations. You don't necessarily need to know the internals to understand the abstraction. I've seen this put off systems programmers before (people used to writing C etc and understanding the assembly).

I wouldn't expect someone with no Haskell knowledge to understand Haskell code. I've seen higher-level people (e.g. VPE-level) get upset by this and knee-jerk decide Haskell is problematic. I'm of the opinion that such knee-jerks aren't worth listening to..I don't care about opinions of people who haven't met (or honestly tried to meet) the prerequisites.

Regardless, I will say as someone who has learned Haskell: Once you learn it, it becomes so stupid easy to do everything. I feel like I can solve more complex problems faster & better in Haskell than other programming languages I have comparable (or more!) experience in.


You mean that decomposing abstractions with substitution works better in Haskell than in a lot of the other languages because programs in Haskell are essentially (pure) functions and abstractions are built by composing functions?


Yeah - referential transparency is what allows it to Just Be Substitution.


Game servers aren’t typical. Texas Hold’Em especially.

Like a chat application is also multi-user real-time. It’s obvious that when a chat participant disconnects you hold onto the messages to deliver to them for later. When you disconnect from a Texas Hold’em online and you were small blind, what should you do? Wait? Shift the blinds over? The next player in line gets big or small? Copy the leading product’s behavior? It’s hard to reproduce all the states in someone else’s live, production game.

It’s not at all obvious and this logic has to live somewhere. It touches a bajillion things, like the raw connection state, timers, transient and long-term persistent state. Your programming language isn’t going to make it simpler for you. It can’t just hide in your database’s conflict resolution or some AWS service.

This is a great Haskell demo because it shows that you can’t hide this code anywhere. It stares back at you with all its ugliness.


> This is a great Haskell demo because it shows that you can’t hide this code anywhere. It stares back at you with all its ugliness.

i enjoyed your comment and these last two lines in particular. different programmers might interpret these lines in completely different ways: one as a critique of haskell for not being able to tidy the details away to make the code appear simpler, another as praise of haskell for making these mechanics explicit.


Our ways to hide that logic has been building libraries that handle nearly all of it.

But since Haskell's ecosystem is small by comparison a lot of that logic leaks to your own application code. Especially when dealing with something like stateful websocket applications.


A general-purpose library able to hide the necessarily specific logic of how network events and a poker game should interact seems... unlikely?


Sure, but the original argument was: > raw connection state, timers, transient and long-term persistent state

Much of this surely can be abstracted away to 3rd party libraries/frameworks. Haskell, even though a higher level language than most out there, lacks the ecosystem support for a lot of things that are handled by some library in lower level languages.


The point was that these things need to be handled unusually in the case of a poker server in particular. If that's true, then a general purpose library that's handling them invisibly is probably handling them wrong for this use case.


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


Ouch. 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.


In your first version, I think I would use =<< to keep "flow" of information right-to-left. I also use parentheses instead of . and $ when there isn't a lot of nesting, I got that habit from this post about writing legible Haskell http://www.haskellforall.com/2015/09/how-to-make-your-haskel...

    getSocketAPIPort :: Int -> IO Int
    getSocketAPIPort defaultPort = do
        maybeEnvPort <- lookupEnv "socketPort"
        return (fromMaybe defaultPort (readMaybe =<< maybeEnvPort))
Keeping the pattern match instead of using "fromMaybe" wouldn't be a bad idea, either:

    getSocketAPIPort :: Int -> IO Int
    getSocketAPIPort defaultPort = do
        maybeEnvPort <- lookupEnv "socketPort"
        return (case readMaybe =<< maybeEnvPort of
            Nothing   -> defaultPort
            Just port -> port)
It makes the default value stand out a bit more.


I've written a fair bit of Haskell, including some production software. Your second version here is my personal favorite of the variants proposed so far. It's the version I can look at and more or less instantly understand. I think sometimes folks go a little too far with using library functions to manipulate Maybe values -- not that `fromMaybe` is particularly onerous, but something about the structure of pattern matching just conveys information to my brain much faster.


For my part, for that logic I'd consider MaybeT. I don't like that a malformed environment variable gets the same treatment as a missing environment variable, though.


I feel like I'm in a unique place of learning Haskell, so I'll try to translate the last line of your first function:

  return . fromMaybe defaultPort $ maybeEnvPort >>= readMaybe
Basically this is composing a function out of "return" and "fromMaybe" (using the composition operator "."), then partially applying defaultPort to that composed function, so you now have a function that takes one argument. The resulting function is then applied (using $) to the result of "maybeEnvPort >>= readMaybe".

In "maybeEnvPort >>= readMaybe", ">>=" is an infix function that takes maybeEnvPort as its first argument (which is a Maybe Monad), "unpacks" it, applies "readMaybe" to the unpacked result. readMaybe returns another Maybe Monad.

The result of everything after the $ is a Maybe Monad that contains the port from the environment, or a failure condition. The result of applying the composed-and-partially-applied function (from before the $) to it is that the port from the environment is chosen if it didn't fail, otherwise the defaultPort is used, and then the whole thing is wrapped in an IO Monad.


The only place I would offer a correction is to say that values aren't monads. "Maybe Monads" and "IO Monads" aren't getting created or passed around because only values exist at runtime.

The only thing that can be a Monad is a type. You could say you have a value of a monadic type, I suppose...

But that gets into something I've learned over time answering beginner questions. Call things types or values. "a Maybe value" (this is a little sloppy, but perfectly fine in conversation) or "the IO type". Don't call types with a Monad instance "Monads" except in the case when you are talking about all of them generically. "The IO Monad" is an incredibly self-limiting and distracting way to think about the IO type. There's nothing inherently interesting about being a Monad. Why not call it "the IO Functor" or "the IO Alternative" or even "the IO MonadRandom"? Those are all instances the type has. None are particularly more important than the rest. Sometimes what you want to do is most easily done via a type class other than Monad. Don't tie yourself so much to a single detail. This is actually really important, because our habits shape our intellectual exploration. When you find a habit that shoehorns you into one direction, it's a good idea to try to weaken it.


i like it, for some reason my brain dislikes switching from left/right a lot, so that's why i often avoid the $ operator, so instead I'd write:

    return . fromMaybe defaultPort (maybeEnvPort >>= readMaybe)


"Have the data flow in one direction" is a good rule of thumb in writing clear Haskell code.

That said, your conversion away from $ changes the meaning here (in a way that doesn't typecheck, I think - remember that regular function application binds tightest whereas dollar binds loosest) and you still don't achieve your goal.

Instead, maybe

   return . fromMaybe defaultPort $ readMaybe =<< maybeEnvPort
or even

   return $ fromMaybe defaultPort $ readMaybe =<< maybeEnvPort
If you still don't like the dollar signs, we can parenthesize instead in two correct ways, although I don't find them more readable:

    (return . fromMaybe defaultPort) (readMaybe =<< maybeEnvPort)

    return (fromMaybe defaultPort (readMaybe =<< maybeEnvPort))


tbf, 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


Is 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.


Yes, Haskell is the one language where you can always invest some more time learning something more advanced that will provide you a large boom in productivity.

That has the downside that Haskell developers speak many different idioms, just like Lisp. I'm prone to claim that the code you posted is basic enough that we can consider that people that don't get it are not proficient on the language yet, but there are way too many things right on the fence for that, and they can't all be required.


> 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.

I definitely didn't argue this point (although I admit to skepticism).

It sounds like you're actually aware that it's easier to read the redundant code when you are not an expert, so we don't have any disagreement there.


> It sounds like you're actually aware that it's easier to read the redundant code when you are not an expert, so we don't have any disagreement there.

Actually, there is minor disagreement. I don't think I used anything requiring expert-level understanding. I would put the tools I used at the level of day-to-day proficiency, not expert level. Roughly, the level it took me 3 months to reach, not the level I'm still working towards after 10 years.


If 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));
          }
        });
    }


It'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)


100% 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.


Though 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.


> 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.


Well, 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.


> 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.


Weird, I thought the point of this thread was how to bikeshed looking up environment variables while folks look at the proverbial camera and appeal to the hypothetical audience that `if x == null: ` would surely be morally superior.


> 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.


>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.


On 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.


Do 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.


Better examples of how Haskell can make the OP's eyes glaze over? Basically the rest of it.


monad transformers can help alot here:

  getSocketAPIPort :: Int -> IO Int
  getSocketAPIPort defaultPort =
    fromMaybe defaultPort <$> runMaybeT do
      envPort <- MaybeT $ lookupEnv "socketPort"
      MaybeT $ return $ readMaybe envPort


in python:

  def getSocketAPIPort(defaultport):
       try:
           return os.environ["socketPort"]
       except KeyError:
           return defaultport


Close, but values from os.environ are always string, and not guaranteed to be parsable numbers.


This is a bit clearer:

    def getSocketAPIPort(defaultport): 
      return int(os.getenv('socketPort', defaultport))


Closer, 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.


I 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.


It 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?


I’d say the original code was wrong. anyway catching an exception isn’t tricky.


If it's easy then why did you fail twice at even making a valid python expression?

When we talk about the oppressive nature of Python culture—how the "there is only one way to do it" culture leaks out of the place where it's applicable and starts to be focused on other communities that have different requirements—we think of examples like this. "Your code in a language I don't now or use is wrong."

Over focusing on how the example code polls the environment is the least interesting thing you could hyperfocus on. What's more, I think it's a bit more normal to use something like optparse-applicative, which leads to inevitable complaints about using infix operators that every Haskell novice knows but that armchair Haskell programmers hem and haw about asking, "is this too many operators?"


I only answered once. The other code was from another guy.


Okay, my apologies. I accept the correction.

Do you want to respond to the rest or just let it stand?


I'm going to take another look at Haskell first.


here's ho I'd write it:

    getSocketAPIPort :: Int -> IO Int
    getSocketAPIPort defaultPort = readWithDefault <$> lookupEnv "socketPort"
      where 
        readWithDefault mbPort = fromMaybe defaultPort $ readMaybe =<< mbPort


Though 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"


Haskell is not a difficult language to learn, but unlike imperative languages, you can't just start reading the code if you "know literally nothing about Haskell". One thing to keep in mind is that every other line in Haskell is basically just composing functions together, but you need to know how all those weird >>= and <*> and <$> effect code/composition flow


> is not a difficult language to learn

People say this about every single programming language.


dunno. to me PHP and CPP are the hardest, because I cant figure out the logic behind their design.


Some have genuinely uniquely confusing bits. Like `this` in javascript. Or `Hold` and `Evaluate` in mathematica.


Probably because they're all right.


> So, I know literally nothing about Haskell, and I would rate my knowledge of functional programming theory as 'beginner' at best, but I still get baffled every time when reading Haskell code that implements anything other than something trivial like a fibonacci sequence.

Then perhaps you should fix these before indicting the code?

Haskell isn't just a different language to learn because it's different, it's also a different language because it has a community that values math-driven models of things. As such, you're going to end up at a disadvantage trying to understand every aspect of it without any prior consideration.

Sorta like how templates often baffle new programmers but are considered absolutely essential by folks who get a year or three of C++ experience.


I think part of the problem is that Haskell code can be very terse. When reading Python I expect to be able to easily understand what's going on in a 5 line function because the pythonic style limits what you can do in 5 lines. But in haskell a 5 line function can be pretty sophisticated.


Careful about sweeping generalizations... The internet has shown what is possible in "one line" of python, and I've seen this in the real world scarily enough!


What you can accomplish in 5 lines of “ready for production, maintainable, easily understandable by coworkers” python is vastly less than what you can accomplish in 5 lines of similarly constrained Haskell.


Unfortunately, a lot of work there is done by the Haskell community having utterly absurd standards for those things. I work in a different functional programming language, and you would not believe the amount of time I spend convincing new hires with Haskell backgrounds to spend the extra few characters to expand out the unreadable point-free/single-character-names style that Haskell folks prefer.


Has it ever occurred to you that Haskell programmers prefer single-letter variable names in some contexts because it makes the code better? Perhaps it is not the children who are wrong.


Right, I find pointfree style to be much more readable than pointful, I expect the GP would too if they became used to it


The problem is you can’t bring that change about in a corporate setting by just doing things your own way and hoping your coworkers catch up. I think that’s more what GGP was having issue with.


IME, this isn't consistent. If you can write it as `f . g . h` then point-free will probably be clearest. If you need to use each argument in a few places, I find going all the way to point-free is usually much less clear than some intermediate points or even the fully pointful version.


I have spent quite a bit of time in both styles, and there's a pretty clear winner in my experience. I don't work in Haskell, but in another statically-typed FP language - maybe type classes are the feature that flip everything we know about readability from other languages?


No, the problem is giving names that are too specific to things. Like if you're implementing a merge sort for some reason, and go to write merge:

    merge :: Ord a => [a] -> [a] -> [a]
    merge (x:xs) (y:ys) | x <= y = x : merge xs (y:ys)
                        | otherwise = y : merge (x:xs) ys
    merge [] xs = xs
    merge xs [] = xs
Those are the ideal variable names. Clarity is only hurt by making the names "descriptive" by naming the type variable elementOfListsToMerge or the parameters things like theHeadOfTheFirstList and so on. It's useless precision, like using 100 digits of pi to calculate volume of a... pie. You pay the cost every time you use it, while it adds no additional knowledge.

This is what I mean - there are a lot of cases when writing Haskell code that you write generic polymorphic combinators. Using long variable names is negative utility. Haskell programmers know this, and so they don't use long names when short ones convey identical information in a more readable manner.

This is where most other languages fall down, and why most programmers don't understand the concept. It's common in Haskell to write code that applies to so many different situations that any "descriptive" name you give to a local variable is just plain incorrect in many use cases.

In the other direction though, Haskell programmers also appreciate the value of a well-chosen name when one applies. Note that the function being discussed elsewhere in this thread isn't polymorphic. It has descriptive local variable names. No one suggested renaming them x or a.

In other words, choose names well, not according to dogma that says short is bad.


I think this is kinda my point. Haskell folks like to show unrealistically small code samples that don't even do the small thing they claim, where one-letter names are kinda defensible, and then you realize that that's how they name everything.

Since you like sorts, have you seen a Haskell implementation of quicksort that spells out the word "pivot"? This seems like a small ask. Surely someone has done it. Before typing this, I assumed it couldn't possibly be as bad as I remembered and that I was falling prey to confirmation bias, but in fact 9 out of the top 9 results (one is a dead link, which is why there aren't 10) for "haskell quicksort" on Google use a single-character variable name for it (n, p, or x). As a side note, 7 out of 9 of them exhibit neither the time nor space complexity promised by quicksort. (I admit that two of them appeared to be the same code - call it 8/8 and 6/8, respectively, if that seems unfair.)

Finally, one of the two that does attempt to perform it in place (which is far more complicated) contains variables named lb, ub, mub, ma, and, my favorite, iLTj. Good luck figuring out how that code works. This is the thing I'm talking about, and it is not an isolated example.

I like Haskell as a language! It has a lot of cool ideas. But people who learn it are often made into much worse programmers for having been exposed to it, and it is because of stuff like this. It's some weird community standards problem.


> and, my favorite, iLTj

"i less than j". Not hard to guess if you know about "LT" in the standard library.


By the comment, the GP is not a Haskell developer and those people are not programming in Haskell.

So, even though it is well known that short named variables and point free syntax often improve the readability of Haskell code, it probably does not improve the readability of his code.


I work in a statically-typed FP language. Unless type classes are the magic feature that make point-free viable, it's hard to see how that could be the difference.


Oh believe me, I’d believe.

My Haskell background comes from building upon academic proofs of concept during a stint at CSAIL. Never again.


That world has very little to do with commerical Haskell though, now does it? Why, we even discourage the use of Singletons and if you import a unification library everyone will immediately demand to know why.


> Is it just me[?]

It's not just you, I came here to say the same thing. I just feel dumb when I try to read Haskell, though I've been using Clojure in production for over 5 years.


While I feel like writing Clojure for 5 years would improve one's ability to /write/ Haskell, I feel it'd have almost no impact on one's ability to read Haskell.

With all pros (e.g. local reasoning, referential transparency, no destructive updates) and all cons (e.g. tons of marshalling/converting, piles of imports, shitty records) aside for a moment, I think you learn to read Haskell like you learn to read any other programming language: by writing a lot of it.


It's not a good idea to try reading Haskell by "brute force", trying to figure it out as you go.

I suggest you first read through something that teaches you the basics like "Learn You a Haskell" or similar.

In a way, the experience is more like Clojure than Javascript. If you've read a Java-like language before, figuring another one tends to be easy. But if you've never read a Lisp-like, all the Java-like experience in the world won't help you to read a non-trivial Lisp program. You will just see some weird parentheses and won't be able to make head or tails of it.

Haskell is similar. Some upfront learning is needed before reading non-trivial programs.


As someone who learned Haskell in their spare time: Yes you are correct sort of.

I'd say 50% of the reason it's hard to read is you are not familiar and 50 hours of learning Haskell would sort that out. Training your visual memory to get used to (f a b) rather than f(a,b) etc. I liked to add redundant parens in my play code just to help me with this.

The other 50% is those damn library authors and their love of funny operators and advanced GHC extensions. And also some people like to play code golf with "point-free" style where instead of the x -> f x you'd just use f.

Which if taken to the extreme produces hard to read code that is lovingly called "pointless".

Code golf in Haskell is rife. I really prefer longAndMeaningfulVariableNamesThatErrOnTheSideOfBeingTooLong, but the Haskell culture isn't that way, and they prefer names like: s'.


One problem is very generic tooling in Haskell. In many languages, you'll have a library that might provide a function like "iteratePokerGameStep." In the haskell community, the author is more likely to realize that this could just be implemented via "BiApplicativeProfuctorCategory.map." So instead of writing a 6 line "iteratePokerGameStep" which gets its own name, you're more likely to see someone just call "map." Much simpler code in some ways, much less simple in others.


If it's not obvious why someSuperGenericThing is thisSpecificThingINeed in this case, I'll often give it an appropriate name and more specific type locally. It helps the next reader (which, of course, might be me) and can also help keep type errors better localized. It takes noticing, though, to be sure.


This is very good style.


"BiApplicativeProfuctorCategory".

Please don't just make things up.


Trying to read Haskell code without any knowledge of it is interesting to say the least. I like how compact it looks, but I'm guessing a lot of the simple looking statements (at least at first glance) do a lot under the hood. Is that a characteristic of Haskell itself or just the developer has a good style?


In Haskell vs other languages, a large amount of your program is written using various forms of composition (function composition, applicative/monadic composition [sequencing of side-effects], monoidal composition [combining values], library-specific composition [e.g. Conduit =$=] etc).

The stuff you compose tend to be small functions that are easy to test & reason about.

The nice thing about composition in Haskell is that if you have correct program A & B, then the composition of A & B is also correct & easy to reason about.


Haskell tends to be terse because a number of features and very general built in functions. The author is also good at Haskell and lacks a lot of ugly optimizations making it look even cleaner.


Dumb question. Is Haskell ever the best tool for the job? I feel that every language has a sweet spot, but Haskell seems to be a language that people use just to show that they can. I've always wanted to learn, but I don't want to learn if there isn't a reason to.


It’s arguably the best pure functional language implementation. So if you think functional programming is a good thing, then yes it’s worth learning. The worst part about Haskell is you realise how lacklustre functional programming is in every other hybrid language. The lisps probably come closest in capturing a similar feel.


Here's a popular "State of the Haskell ecosystem" page:

https://github.com/Gabriel439/post-rfc/blob/master/sotu.md

There are a number of things where haskell is best in class and a larger number of things where it's immature. The language itself could be excellent at far far more than it is today, but libraries are limited for many tasks.


Once you get over the learning curve, Haskell becomes the best tool for most jobs. The main reason I've _not_ used it is ecosystem (e.g. if Go or something has a library for some API that Haskell doesn't)

For instance, you could make a video game in Haskell. It has SDL2 bindings, plenty of networking libraries, best-in-class support for DSLs (user scripting), and you can even write the music for your game itself in Haskell!


Very awesome-looking project! I think this is the kind of thing that Haskell needs more of: networked application code that is tangible and can give more curious folks (like me) an idea of how this language might be useful for our own projects.

Edit: only thing missing is PureScript on the frontend ;)


Go one further and implement Mental Poker [0] so the server doesn't have to be trusted. I wrote an implementation in Go [1] when I was toying w/ p2p card games.

0 - https://en.wikipedia.org/wiki/Mental_poker 1 - https://github.com/cretz/go-mental-poker


What would be the best IDE/editor for Haskell?


We're gonna have AGI before we have a good IDE for Haskell. Spacemacs seems to be the best option at the moment with https://github.com/haskell/haskell-ide-engine.


I'm curious about this too.

Every once in a while I try going through the editor setups in this chart - https://github.com/rainbyte/haskell-ide-chart. But I run into lots of friction in any one I try. Between using the REPL, getting harmony in the project libraries and the IDE engine libraries, and learning a new editor, I run out of energy to also learn the language ecosystem (libraries, concepts, idioms, package managers).

An IDE that that surfaced everything the language encodes seems like it would lower the learning curve of Haskell. And every year it seems a little closer. Is there anything close to a jetbrains/visual studio/xcode yet?

It seems like all the type safety would be valuable enough that companies would pay for/invest in the ecosystem. Learning the ecosystem has been more challenging than learning the language at this point for me.


Emacs works very well. Haskell is the second best emacs experience I've ever had, second only to Lisp.

Things were not always this way. Just a few years ago, it was a bad experience, so if you have bad memories, it may be time to try again.


I used Haskell on Emacs a few weeks ago and there still seems to be some issues with regards indentation.


Ouch, it has been so long that I have forgot about this. Add this to your emacs init script somewhere:

    (add-hook 'haskell-mode-hook 'turn-on-haskell-indentation)
It is also not compatible with older versions of intero and haskell-mode, so if you have old configuration there, you may want to remove it.

Honestly, it's even weird this isn't on by default.


That's strange. I don't have anything like that in my .emacs but I do get indentation automatically for Haskell files. I do have a recent dante-mode though. Perhaps dante-mode turns it on automatically?


Thanks!


Editor of your choice in one window and ghcid, which shows compiler errors and warnings on change of file, in another window.

From time to time a web browser to search a function in hoogle [1] and ghci (the interpreter console) to interactively try something out.

Honestly, I come to the conclusion, that at least for Haskell I do NOT need an IDE. I would need it for Java and other imperative languages that have huge libraries.

Haskell is one of the languages where it is a better investment of learning the language that spending time searching a good IDE.


You can use a language server with Atom. Beware of stuff breaking (barely any ABI compatibility), and it taking like 12G ram, however.

Otherwise you can also integrate a language server with Vim or Emacs.


Anything that highlights other instances of what you highlight yourself.

My pick is Kate on KDE. But I've been using vscode as well so I'm not tied to OSs that can run Kate.


I used to love Emacs + Intero but now I use VSCode with the Haskell plugins for syntax hilighting, code completion, and syntax error flagging.


Vim.


Yup, that's what I use. Just like for any other language, it works great.


I tried spacemacs with intero for a while, but I'm now using https://github.com/haskell/haskell-ide-engine + vscode + vimmode. Mostly because I'm using vscode in my work and I don't want to invest more into emacs.


I use the exact same setup and like it a lot.


I would contest the other commenters and say that vscode + haskero (intero integration) is the simplest and most robust method of Haskell programming at the moment.

Add in ghcid for continuous builds and it's almost ideal.


I used Sublime and it was just fine, I didn't feel the need for anything else.


I see you are using ekg. I've written quite a lot of haskell, but haven't ever used that, but have been curious - how much does it help you, or is it just for fun/test?


Very neat! I've been working on a network application in Haskell too; this is a great reference.

Is this project still active? Are there some features you'd like help developing?


Yes this project is active. That would be awesome if you contributed. There are a lot of features to add. Would you like me to add some of these to the issues on Github? Feel free to add your own suggestions to the issues as well.


That'd be great! I saw some todos, but you'll have the best perspective on what's important. I'm https://github.com/Gandalf-


Ok I have added some things to work on to the issues. Feel free to pick one up or add your own. :)


zzeder, I would like to thank you for posting this, because after browsing your code, I finally found the excuse I was looking for to teach myself Haskell; so far I'm not only enjoying it, but I sincerely loving it and have finally realized I should have learned FP in the first place!

For once again, thank you mate! +1


piling on... I build a bookmarking site (self-hosted) with Haskell https://github.com/jonschoning/espial/


What are good Haskell codebases (or videos) to read through if you have a passing knowledge and want to improve?


Check out this list of applications written in Haskell:

https://github.com/soupi/haskell-study-plan#a-few-cool-open-...


If you haven't seen https://haskell-at-work.com/episodes.html yet, I can recommend it




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: