Live data from Hacker News

Becoming Productive in Haskell

mechanical-elephant.com

171–180 of 213 posts

Re: Becoming Productive in Haskell

#171

Scripting languages try to seduce you to just fiddle around until the output looks like something you want. While that quickly gives you some results, I think it's a huge roadblock in the mid- to longterm. Especially when programmers are only familiar with "easy" scripting languages, there are rarely insights about the general approach to the problem until the project already grew to become an abomination. While fidd…

Requirements Uncertainty: Fast iteration has many benefits - especially when you (or your client) don't actually know what's required ("agile"); and when what is required changes quickly, because of changes in competitors, customers, technology, regulation etc. But you're right, as projects get larger, a priori design and static types quickly become essential. And at that point, requirements are usually known and fro…

> ML-family and Haskell are popular where provable correctness is wanted.

Haskell doesn't have a theorem prover, you may want to check out Idris [1]. Haskell gives you more safety that you aren't going to get run time errors than say Java, but not completely. You'll still need automated testing for correctness, that you're not getting garbage in, garbage out.

[1] http://dafoster.net/articles/2015/02/27/proof-terms-in-idris...

Re: Becoming Productive in Haskell

#172
Learning enough Haskell to feel "productive" is an incredibly good way to deepen your understanding of programming, even if you've been programming for years.

Two things, in particular, stand out for me when thinking about Haskell this way (as a "tool for thinking" language).

First, unless you're a mathematician, you probably haven't thought very deeply about algebraic data types, and how useful and expressive it is to build up a program representation from a collection of parameterized types. The article touches on this a little bit in noting that Haskell teaches you to think about data types first.

But it's more than just "data first," for me, at least. Grokking Haskell's type system changed how I think about object-oriented programming. Classes in, say, Java or C++ or Python are a sort of weak-sauce version of parameterized abstract types. It's kind of mind-blowing to make that connection and to see how much more to it there is.

Second, monads are a really, really powerful way of thinking about the general idea of control flow. Again, the most useful analogy might be to object-oriented programming. When you first learn to think with objects, you gain a flexible and useful way of thinking about encapsulation. When you learn to think with monads, you gain a flexible and useful way of thinking about execution sequencing: threads, coroutines, try/catch, generators, continuations -- the whole concurrency bestiary.

I think monads are hard for most of us to wrap our heads around because the languages we are accustomed to are so static in terms of their control flow models, and so similar. We're used to thinking about control flow in a very particular way, so popping up a meta-level feels crazy and confusing. But it's worth it.

For example, if you do much JavaScript programming, and are ever frustrated translating between callbacks and promises, having a little bit of Haskell in your toolkit gives you some mental leverage for thinking about how those two abstractions relate to each other.

Re: Becoming Productive in Haskell

#173

Earlier quoted context omitted.

foldl' is a consistent and meaningful name. fold performs a fold without specifying an order[1], foldl folds from the left, and foldl' is a non-lazy version of of foldl . 1: fold :: (Foldable t,Monoid m) => t m -> m

Is there a spec that leaves the order unspecified? Having it not be a right fold would be quite nutty. It is a right fold in implementation.

Ugg, I had to spend a fair amount of time coming up with a good example. Hope this is helpful!

The Monoid operation mappend is guaranteed to be associative, so the order is irrelevant. Data structures can fold in whatever way is most efficient for their structure.

It's true that lists and arrays are implemented as right folds, however the fold implementation for sets is neither:

From Data.Set:

  fold = go
    where go Tip = mempty
          go (Bin 1 k _ _) = k
          go (Bin _ k l r) = go l `mappend` (k `mappend` go r)

  -- Here, I reorganized the code of `fold` to have the same shape as
  -- `foldl/foldr` so that you can see the difference in structure more
  -- clearly.
  fold2 = fold3 mappend mzero
  fold3 f z = go z
    where
      go z' Tip           = z'
      go z' (Bin _ x l r) = f (go f z' l) (f x (go f z' r))

  foldl f z = go z
    where
      go z' Tip           = z'
      go z' (Bin _ x l r) = go (f (go z' l) x) r

  foldr f z = go z
    where
      go z' Tip           = z'
      go z' (Bin _ x l r) = go (f x (go z' r)) l

Re: Becoming Productive in Haskell

#174
post #118

Scripting languages try to seduce you to just fiddle around until the output looks like something you want. While that quickly gives you some results, I think it's a huge roadblock in the mid- to longterm. Especially when programmers are only familiar with "easy" scripting languages, there are rarely insights about the general approach to the problem until the project already grew to become an abomination. While fidd…

Absolutely, Haskell has a richness of concepts to it that's entirely in a class of its own... but that being said, Haskell is to programming what sex is to life (for a woman... because men's natural enjoyment of sex throws off my following analogy). Granted there is richness and joy in finally discovering it, one needs to realize it's important that one isn't forced into it too early. Haskell demands a lot of thinkin…

> And that's where Ruby, Python, Javascript, and to some extent Matlab come in. For whatever else people may say about them later (they don't scale, they're a roadblock, they're a mess, null is not a function, etc.), they were there for you when you were programmatically young and they introduced you gently into a world that's otherwise extremely complex.

Well, no, they literally weren't there for me when I was new to programming. (MATLAB existed then, but I wouldn't actually see it for more than a decade.)

And while I don't think they are bad languages for beginners, I don't see a clear argument presented as to why they are superior for that purpose (just a somewhat vulgar analogy that presumes that people share your subjective opinions about the languages involved.)

Re: Becoming Productive in Haskell

#175
post #162

Earlier quoted context omitted.

Ability to fiddle isn't tied to static or dynamic programming, it's just every static language platform I've seen including The Haskell Platform neglects making it easier to fiddle. When trying to build complex software in Haskell, I find myself spending a lot of time commenting/uncommenting swaths of code, just so I can get part of a algorithm to load in GHCi. It sucks. What I wish would happen is GHCi allowed me to…

> What I wish would happen is GHCi allowed me to load just the things that type check Use `-fdefer-type-errors` (should work with both GHC and GHCi), all errors become warnings, and if you try to use a function which was compiled with an error, you get a runtime error.

Thus perfectly illustrating Cunnigham's law.

Re: Becoming Productive in Haskell

#176
post #87

Earlier quoted context omitted.

I didn't try to say that Haskell is the best language for everything. I also wouldn't want to do everything in Haskell. But as you are pointing out, you learned a lot from Haskell and you are using its lessons in production. The best a language can do is to fill a niche and to be very good at that particular thing. You should always use the language that is most suited for your problem, whatever that is. But there ar…

> I also wouldn't want to do everything in Haskell. Examples, please? And why?

I heard Haskell is a poor choice for real-time systems and numeric computation. It's kinda ironic that a language as "mathy" as Haskell is such a poor choice for doing actual math. Assuming these things are true.

Re: Becoming Productive in Haskell

#177

I really like Haskell, but one of the main problems I've had (that I don't see many people cite) is that the libraries just aren't made for use under serious load/concurrency. Many of the people that have written these libraries, and use them are not using them in high-performance, memory-sensitive areas (production use at companies). There are Haskell libs of course that are used in these environments, and the compa…

Could you name some of these libraries? IME, most of the libraries that are needed for common things are very mature.

I tried to write a Haskell websocket server, the library is quite nice, but it leaked memory (space leak? fragmentation? some of both?): https://github.com/jaspervdj/websockets/issues/72

The author helped me narrow it down to some issues with how ghc by default allocates a stack space that is rarely enough, and once it starts growing the stack space the RAM per connection gets pretty ridiculous. Using higher default stack space helped remedy this some, but the per-connection RAM cost was still way higher than Golang/Python which I was comparing to.

So... separate project, I write a load-tester in haskell for a websocket server. I need to issue some HTTP requests, and I see Brian O'Sullivan made a nice library, wreq. I use it as described and quickly discover it uses ridiculous amounts of memory because it doesn't mention that you should always re-use the Session (the underlying http-client emphasized the importance of re-using the Manager): https://github.com/bos/wreq/issues/17

(I am sorry that this issue prolly came off as a bit whiny there, I was very frustrated that such a gap was omitted from the docs)

So, my program is working pretty nicely, until I discover that its not actually sending multiple HTTP requests at once (even though the underlying http-client lib has a thread-safe TCP connection pool). After browsing some code, I see the problem: https://github.com/bos/wreq/issues/57

The solution that was so far implemented seems equally weird to me.... letting different requests stomp over the Session's cookie jar... I forked it so that I could have multiple wreq Sessions use the same Manager, and now it finally works as it should.

I won't even go into how some of these libs have occasionally wanted conflicting dependencies which leads into its own 'cabal hell' (googling for that is entertaining unless its happening to you).

I've only been writing Haskell for a bit over a year now, but everytime I write code with it, despite my love of the language, the libraries and run-time end up frustrating me.

Re: Becoming Productive in Haskell

#178

Earlier quoted context omitted.

Is there a spec that leaves the order unspecified? Having it not be a right fold would be quite nutty. It is a right fold in implementation.

Ugg, I had to spend a fair amount of time coming up with a good example. Hope this is helpful! The Monoid operation mappend is guaranteed to be associative, so the order is irrelevant. Data structures can fold in whatever way is most efficient for their structure. It's true that lists and arrays are implemented as right folds, however the fold implementation for sets is neither: From Data.Set : fold = go where go Tip…

I went through some examples to make 100% sure that fold has different behavior than foldl/foldr:

  fold      [[_ 4 _] 3 _] → f 4 (f 3 #)
  fold2 f # [[_ 4 _] 3 _] → f (f # (f 4 #)) (f 3 #)
  foldl f # [[_ 4 _] 3 _] → f (f # 4) 3
  foldr f # [[_ 4 _] 3 _] → f 3 (f 4 #)
Reductions:

  fold [[_ 4 _] 3 _]
  f    (go [_ 4 _])  (f 3 (go []))
  f    4             (f 3 (go []))
  f    4             (f 3 #)

  fold2 [[_ 4 _] 3 _]
  fold3 f                       #             [[_ 4 _] 3 _]
  f     (go [_ 4 _])            (f 3 (go _))
  f     (f (go _) (f 4 (go _))) (f 3 (go _))
  f     (f #      (f 4 #     )) (f 3 #     )
  f     (f #      (f 4 #     )) (f 3 #     )
  f (f # (f 4 #)) (f 3 #)

  foldl f                     #             [[_ 4 _] 3 _]
  go    #                     [[_ 4 _] 3 _]
  go    (f (go # [_ 4 _]) 3)  _
  f     (go # [_ 4 _])        3
  f     (go (f (go # _) 4) _) 3
  f     (go (f # 4) _)        3
  f     (f # 4)               3

  foldr f                    #                      [[_ 4 _] 3 _]
  go    #                    [[_ 4 _] 3 _]
  go    (f 3 (go # [_ 4 _])) _
  f     3                    (go # [_ 4 _])
  f     3                    (go (f 4 (go # _)) _)
  f     3                    (f 4 (go # _))
  f     3                    (f 4 #)

Re: Becoming Productive in Haskell

#179

Earlier quoted context omitted.

foldl' is a consistent and meaningful name. fold performs a fold without specifying an order[1], foldl folds from the left, and foldl' is a non-lazy version of of foldl . 1: fold :: (Foldable t,Monoid m) => t m -> m

Is there a spec that leaves the order unspecified? Having it not be a right fold would be quite nutty. It is a right fold in implementation.

It's useful for more general folds over more general types. If you have a tree structure then you might not want to fold from the left or the right but instead in multiple places in parallel and then combine them at the end

    * + (* + (* + (* + (* + (* + *)))))   versus
    (((((* + *) + *) + *) + *) + *) + *   versus
    ((* + *) + *) + (* + (* + *))

Re: Becoming Productive in Haskell

#180

I really like Haskell, but one of the main problems I've had (that I don't see many people cite) is that the libraries just aren't made for use under serious load/concurrency. Many of the people that have written these libraries, and use them are not using them in high-performance, memory-sensitive areas (production use at companies). There are Haskell libs of course that are used in these environments, and the compa…

I've actually found the exact opposite. The library ecosystem is rich and mature. Haskell is, by default, "concurrency safe" because of referential transparency. You can safely "async" and compose almost any library in the Haskell ecosystem without worrying about shared memory etc underneath. Also, a lot of the really common libraries like text, attoparsec (parsers), aeson, networking, etc are highly tuned for low la…

I actually thought that too, but I guess that's not the case. I helped write some HTTP2 frame-parsers for Haskell using attoparsec, but apparently it wasn't fast enough as the lib author later rewrote all the attoparsec code to use pointers to the underlying byte buffers.

https://github.com/kazu-yamamoto/http2/commit/0a3b03a22df1ca...

The stream fusion stuff is sweet, but not exactly unique to Haskell since any language with good iterator/generator abstractions have similar constant-time memory characteristics.

Post reply on HN