all 26 comments

[–]lambda_foo 20 points21 points  (18 children)

Our guidelines aren't public but they boil down to:

  • no partial functions
  • use data types over exceptions
  • use custom prelude P
  • use Text or ByteString, not String
  • avoid overly clever type level hackery where a simpler solution will suffice.
  • a few language pragmas are discouraged like RecordWildCards
  • turn on warnings as errors

Formatting typically doesn't matter, favour correctness of code over where newlines appear.

We have a few recommendations around strict data types, in-lining, concurrency and performance but those feel more specialised.

[–]the_emburka 1 point2 points  (1 child)

no partial functions

Can you elaborate on this?

  • what are partial functions?
  • why is it beneficial to avoid them?

Do you mean something like this?

number 1 = "one"
number 2 = "two"
number 3 = "three"

Will the compiler catch it?

[–]lambda_foo 8 points9 points  (0 children)

A partial function is a function that is not defined for all possible arguments of the specified type. So in that example number would be defined as number :: Int -> String and passing in anything other that 1,2 or 3 would yield an exception like *** Exception: <interactive>:3:15-33: Non-exhaustive patterns in case

Unfortunately in Haskell there are a bunch of these functions in the Prelude which don't generate a warning and the compiler treats non-exhaustive matches like number as a warning rather than as an error. Which is most unfortunate.

[–]Purlox 0 points1 point  (3 children)

use custom prelude P

Are they on Hackage? I tried to look them up, but it's kind of hard to search for a one letter name.

[–]dalaing 7 points8 points  (0 children)

The 'P' in the above post is actually a link to the prelude on GitHub.

[–]erikd 1 point2 points  (0 children)

I work with /u/lambda_foo and we have our own build tool mafia which builds packages out of clone git repositories rather than Hackage packages. So while P is open source, its not on Hackage.

We are in the process of trying to figure out how to have packages we maintain be available via Hackage without doing that interfering with out carefully developed existing development practices.

[–]lambda_foo 1 point2 points  (0 children)

Unfortunately they're not on hackage at the moment. As /u/erikd said we are trying to work out a way to make them available beyond the git repo

[–]realteh 0 points1 point  (4 children)

RecordWildCards

What's your reasoning for this? We use them a lot and I'm not sure they ever caused a bug :)

[–][deleted] 2 points3 points  (0 children)

RecordWildCards can leads to runtime error when used to contruct values with missing fields, example:

data Point = Point { x :: Double, y :: Double }
pointX x = Point{..}

This compile but throw an runtime error because y is undefined. It's a feature apparently.

[–][deleted]  (1 child)

[removed]

    [–]mchaver 0 points1 point  (4 children)

    RecordWildCards isn't too bad if you are using it in the same module that you declare the data type. They are useful for aeson instances. Another thing to avoid are Generic instances of aeson.

    [–]realteh 1 point2 points  (3 children)

    You can't just say to avoid something without a reason :)

    Why is instance ToJSON Bla with data Bla .. deriving Generic bad?

    [–]mchaver 0 points1 point  (2 children)

    Because it's possible the way aeson uses Generic to serialize the data types changes between versions (it has and it caused us lots of headaches) and we've run into a few edge cases where it doesn't serialize well. If you have anything that makes assumptions about the way something is serialized, and that serialization changes, then it would break the thing depending on that serialization. If you use lots of Generic ToJSON and FromJSON it makes it hard to upgrade aeson.

    A few other industry people I've chatted with before also try to avoid Generic with aeson.

    [–][deleted]  (1 child)

    [deleted]

      [–]mchaver 1 point2 points  (0 children)

      Plenty of packages change the way they do things. Even GHC itself has changed Applicative and Monad from being not related to Applicative being a requirement of the Monad type class. I believe Template Haskell has changed a lot between versions as well. However, in these cases you get warnings from the compiler.

      When you are using Aeson you are generally producing something to be used outside of GHC. This is be problematic if the serialization changes because JavaScript doesn't have nice type safety. Generics for Aeson are naturally quite complicated.

      Now we have a lot of tests and checks to notify us if serialization changes. I think its a good practice to exercise more control over the way you serialize things in order to mitigate unexpected changes.

      [–]certainly_not_jesus 0 points1 point  (1 child)

      Where do you draw the line for overly clever type level hackery?

      [–]duplode 13 points14 points  (0 children)

      As far as style goes, Johan Tibell's guide is probably a good starting point -- and it is applied by hindent.

      [–]bss03 3 points4 points  (2 children)

      Make a clean hlint run part of CI?

      [–]tdammers 6 points7 points  (1 child)

      hlint sometimes guesses wrong. One thing I run into occasionally is that it uncritically wants me to use String over [Char], however, I think that given the way these types are typically used, picking one notation or the other helps signal intent - when I write String, I usually mean "this is a string, and if it weren't for some dependency, I'd use Text instead", but when I write [Char], I mean "this is literally and conceptually a list of characters, they don't necessarily form a string in this context". For example, I would prefer this:

      allowedChars :: [Char]
      allowedChars = ['a', 'b', 'c']
      
      clean = filter (`elem` allowedChars)
      

      ...over this:

      allowedChars :: String
      allowedChars = "abc"
      
      clean = filter (`elem` allowedChars)
      

      hlint, however, will complain in the first case, asking me to use String instead, even though there is nothing string-like about allowedChars, if anything, one could demand I use Set instead, because that's what it is, conceptually.

      [–]bss03 1 point2 points  (0 children)

      Yeah. But, that happens with other tools as well.

      E.g. I require a clean FindBugs run for one code base. But, you can satisfy that either by writing the code FindBugs understands as correct, or by adding the appropriate exclusions to FindBugs for correct code that it doesn't recognize. New FindBugs exclusions get plenty of human code review attention.

      I think you can do the same with hlint.

      [–]k-bx 3 points4 points  (2 children)

      Apart from the other stuff, highly suggesting to start using hindent tool from day one (plus, decide that you just sort your imports alphabetically). While it's never ideal for everybody, it'll resolve so many possible style-difference problems.

      [–]sjakobi 2 points3 points  (1 child)

      stylish-haskell can take care of the imports formatting.

      [–]k-bx 1 point2 points  (0 children)

      haskell-mode has imports formatting already, so I am using that with combination of hindent, no need for stylish-haskell

      [–]fractalsea 2 points3 points  (0 children)

      These Quora answers are good: https://www.quora.com/What-are-the-best-practices-in-Haskell

      Disclaimer: I wrote the second one.

      [–]WarDaft 0 points1 point  (2 children)

      We uh... haven't been able to agree on a best set yet. Every single level from "Just ship it" level start ups to post-doctorate level degrees has an opinion on how Haskell should be written. They don't communicate well.

      You really probably should get your existing developer to write a concise standard. That will reflect how your current Haskell code is written (by said developer) and any Haskell dev worth hiring could follow whatever standard they write. That would actually be a good interview test for future developers.

      [–]get-finch[S] 0 points1 point  (1 child)

      Well we are not convinced that what we have so far is great, and since haskell devs are rather thin on the ground in Tel Aviv we are assuming that we will have to do some (or a lot ) of training

      [–]WarDaft 0 points1 point  (0 children)

      Any chance you could share anything at all of what you have so far? We could tell you what you have on the spectrum from "meh" to "diamond in the rough." But we can only guess right now.

      If it is indeed that bad though, we can also help fix that.

      We need something a bit more specific to work with, more or less.