Live data from Hacker News

Why I never finish my Haskell programs

blog.plover.com

121–130 of 228 posts

Re: Why I never finish my Haskell programs

#121

Earlier quoted context omitted.

I hope you haven't gotten the impression that it's types or nothing when it comes to Haskell (although given the bleeding edge of the community's tendency to asymptotically approximate dependent types with GHC extensions I can see where the sentiment comes from). Haskell is certainly not expressive enough to push all invariants to the type system and sometimes you just record the possibility of an error in the type a…

In the specific case letting it blow up is really the only thing you can do unless the whole architecture is designed around it. However, my point was that there are many ways to handle that type of problem, and I've seen no evidence to suggest that using types is the most effective one in practice. When it comes to error messages, I would argue that Haskell ones are no better than Clojure. If anything they're often…

Oh yeah GHC error messages are bad. Notice how I said Elm instead of Haskell at the end :). One of the things I dislike about GHC is the wasted potential there that's evident in the compilers for newer languages like Rust, Elm, and Purescript when it comes to errors.

I haven't found a good way of switching on ex-info generated exceptions. I usually end up having to do string matching or key searching both of which feel brittle. There's ways of following patterns within the boundaries of my own codebase (e.g. a custom key I know will always be there), but that doesn't play well with the ecosystem at large because there aren't well established conventions around what's what. I don't want to come off as saying there's a fundamental reason why Clojure couldn't have better error handling. It's not a language level thing but rather a community thing. I think Python is a good example of where the community has coalesced around using specific error types even though it's a dynamic language.

I totally agree with doing validation at the edges of the program and try to enforce that in whatever language I'm writing in. I think this is a common misconception about statically typed FP that shows up in e.g. Rich Hickey's talk about types. It's quite rare for things like `Maybe` or `Either` to actually show up in your data structure (e.g. Rich Hickey's SSN example). You usually end up bubbling the error to the top of your module and deal with it at a module level. The type is just there to make sure you don't forget to deal with the error (which is the biggest thing I miss when I'm in languages which emphasize open world assumptions and don't give good tooling to create closed world assumptions; I want to know if I've handled all my errors and all states of my application!).

Yeah the problem is that I've found in the legacy Clojure codebases I've maintained it's rare that nil ever has a unique meaning and often ends up getting reused for a lot of different meanings. For example "A key doesn't exist in this map" and "This data is of a completely different shape than I expected" are different error conditions with different errors that usually both turn nil in the ecosystem.

This lispcast article really hit home for me and sums up some of the pain points I hit using Clojure in production: https://lispcast.com/clojure-error-messages-accidental/. You eventually internalize the compiler errors so they're not a huge deal but the ecosystem at large doesn't have a great story for runtime errors.

Re: Why I never finish my Haskell programs

#122
post #62

I see this a lot with intermediate lisp programmers; they spend so much time building ivory tower abstractions that the original problem is forgotten. I sometimes call this "bottom down" programming. Predicting the future is very hard; remembering the past is much easier. If you find yourself typing the exact same pattern for the Nth time, then it's time to refactor it into a macro or a function as appropriate. Figur…

“Bottom down” really resonated with me in my dalliances with both Common Lisp and Haskell.

I first heard the term "bottom down" from my dad a long time ago, but he used it to mean any sort of programming without a plan. It was many years later that I applied it specifically to people doing "bottom up" but get so obsessed with building the perfect base that they never solve the original problem.

Re: Why I never finish my Haskell programs

#123
As others have said, go ahead and implement it; you'll have a better idea of what you might want to change or generalize after you have that version to experiment with.

(One case you might want to consider: with that way to create polynomials, won't x^1000000 take a lot of typing?)

Re: Why I never finish my Haskell programs

#124

I've always felt of Haskell that it's a way for very smart people to never get anything done. I want to like Haskell, I really do, but it's like learning Latin - you'll feel very smart except no one can talk with you except other people that correct your grammar. Bleh.

Part of the reason I stopped using haskell is because of how generalized everything is. I want to get something basic done and you are expected to read a whole book some maths topic to understand how do something basic. Another language would have an example on how to do that thing 99% of users are trying to do but in haskell land they want you to understand the 1000 different ways the library can be used and piece that together to work out what you want.

Re: Why I never finish my Haskell programs

#125
post #106

I am not a Haskell programmer, but is this correct? (Poly a) + (Poly b) = Poly $ addup a b where addup [] b = b addup a [] = a addup (a:as) (b:bs) = (a+b):(addup as bs) Imagine a simple example, adding `x+2` and `10`. In OP's representation, these would be represented as the lists [1, 2] and [10]. That is, the first element is the coefficient of the term of highest degree. But doesn't this implementation add list ele…

  module Main where

  newtype Poly a = Poly [a] deriving (Eq, Show)

  instance Num a => Num (Poly a) where
    Poly a + Poly b = Poly $ addup a b
      where
        addup [] b  = b
        addup a  [] = a
        addup (a:as) (b:bs) = (a+b):(addup as bs)

  main :: IO ()
  main = print (Poly [1, 2] + Poly [10])


  *Main> main
  Poly [11,2]
Yep, you are correct.

Re: Why I never finish my Haskell programs

#126

Earlier quoted context omitted.

I know Elm isn't the same as Haskell, but these points are also used as a selling point for Elm. And more often than not, as with Haskell, the type system makes it possible to refactor a large program with the confidence that all the parts you replace will slot perfectly back into the original structure OR the compiler will yell at you until it does :).

The drawback with Elm is that it has a trigger-happy BDFL who doesn't care about breaking everything all the time. Haskell has a long history and has broken very few things over the years (n+k patterns come to mind).

Started looking at Elm this weekend as a possibility for it to compile into other languages (javascript/elixir). Its too bad every release requires most packages to be rewritten. The good news is that releases are getting slower and slower, so you might go a year without any releases (even minor version patches). I guess that counts as LTS.

Elm would be so awesome if they just forked it at 0.19 and said "no more breaking releases". But I doubt that will ever happen.

Re: Why I never finish my Haskell programs

#127
post #35

Earlier quoted context omitted.

I don't disagree in general but is Haskell a big language?

Haskell has a ridiculous number of obscure operators. Here's a list of "common surprising" operators in Haskell: https://haskell-lang.org/tutorial/operators

There are just functions.

Re: Why I never finish my Haskell programs

#128

Earlier quoted context omitted.

In the specific case letting it blow up is really the only thing you can do unless the whole architecture is designed around it. However, my point was that there are many ways to handle that type of problem, and I've seen no evidence to suggest that using types is the most effective one in practice. When it comes to error messages, I would argue that Haskell ones are no better than Clojure. If anything they're often…

Oh yeah GHC error messages are bad. Notice how I said Elm instead of Haskell at the end :). One of the things I dislike about GHC is the wasted potential there that's evident in the compilers for newer languages like Rust, Elm, and Purescript when it comes to errors. I haven't found a good way of switching on ex-info generated exceptions. I usually end up having to do string matching or key searching both of which fe…

Yeah that's a fair point regarding lack of standard error handling with ex-info. It does feel like one of the less thought out areas of the language to me. I definitely agree with the lispcast article in calling the errors accidental.

I'd really like to see something along the lines of dialyzer for Clojure This talk proposes a good approach for that I think https://www.youtube.com/watch?v=RvHYr79RxrQ

It would be great to have a linting tool that finds obvious errors, and informs you about them at compile time. For me that would be an acceptable compromise.

Overall, I would say that it does take more discipline to write clean code in a dynamic language. The problems you describe with legacy codebases are quite familiar. I've made my share of messes in the past, but I also find that was a useful learning experience for me. I'm now much better at recognizing patterns that will get me into trouble and avoiding them.

Re: Why I never finish my Haskell programs

#129
I heard this gem a couple of weeks ago:

"Don't engineer things to failure."

When you find yourself saying "I ought to be able to generalize this", that's when you need to stop and just write the code you need to write NOW. Just because you can, doesn't mean you should.

Re: Why I never finish my Haskell programs

#130
post #68
post #66

Earlier quoted context omitted.

Well yeah, because a logging statement in production can fail (i.e. network connection drops). The type system forces you to deal with that fact instead of letting you write code that e.g. brings down your server unexpectedly because of some random log call. Many would consider that a feature, but if you want to #yolo it anyway, like in most other languages, just use trace in prod and call it a day.

The type system forces you to solve this problem in a very specific way by structuring your entire app around pushing IO to the edges. There are plenty of other ways to address the problem that work perfectly fine in practice. For example, you can specify what should happen is IO fails in your logging configuration. This handles the exceptional case consistently and in a single place without forcing you to structure…

you don't need to do either. just run performUnsafeIO if you really want to spit out log statements randomly.

I like having to do logging the Haskell way, actually. it allows for more coherent and easier reuse. What if want to reuse some code, but do logging in a specific way? By allowing the caller to decide where logging goes you can readily accomplish this.

Post reply on HN