Important things to know about writing good haskell code

What do you think is important to know in order to write good Haskell code? I am referring to both theoretical topics and good programming practices that you think are important.
What do you wish you had known when you learned Haskell?
All answers are welcome

3 Likes

A list of assorted suggestions:

  1. Use Text (strict) or ByteString (strict) instead of String.
  2. Use foldl' when your producing accumulator function is strict.
  3. Don’t jump to fancy language extensions. Learn how to design programs with simple tools like HOFs, ADTs, Functional Core / Imperative shell.
  4. Use a proper streaming library instead of lists when care about processing data in constant-memory.
  5. Use all the static analysis you can. Including GHC sanity-checks and linters like HLint.
  6. Choose a formatter and use it. fourmolu or ormolu are great choices.
  7. Don’t create too many libraries. Find an app that you want to create in Haskell and work on it. There’s no harm in dedicating a module or two to some code in your app that you think should have a library. Solve real problems first, abstract design patterns second.
19 Likes

I have been only using stylish-haskell. Are they very different?

1 Like

Writing good Haskell code is not very different from writing good code in other languages. It’s always a matter of proper engineering and knowing best practices. Good code is a consequence of a good design and architecture.

  • Simplicity. The code should be as simple as possible, but not simpler. Accidental complexity is the enemy of good design.
  • Separation of concerns. Proper division of responsibilities by means of (functional, object-oriented) interfaces is vital to making the code less coupled and more controllable.
  • Readability. Good code should be written for mere people, not for superman or for aliens. Code should be understandable without much explanation, and, of course, without the need to read a paper.
  • Compiance with important design principles: SOLID, low coupling, high cohesion, KISS, etc.
  • Testability. Good code should be easily testable.
  • Maintainability. This is a derived characteristic, it comes with best practices and the right engineering approach.
  • Pragmatism. Every solution should be honestly justified and should lead to the vocalized final goal. Hidden goals such as playing with a technology/type system/math should not be the driving factor.

Now, there are many techniques, methodologies and mechanisms that allow a developer to design a good code. These techniques are:

  • Interfaces. Haskell has own notion of functional interfaces, even several of them.
  • Proper layering. Pure/impure is a good start, but not the end. A typical application has many various layers that should properly interact with each other. A 3-layered architecture implemented with some architectural design pattern is worth considering.
  • Architectural design patterns. Haskell has many of them: ReaderT, Service Handle, Free monads, Hierarchical Free Monads, Final Tagless/mtl, effect systems. I personally don’t recommend effect systems, but other approaches are good to use. My heart is of course with my Hierarchical Free Monads approach.
  • Application frameworks. Quite often, you don’t need any custom homemade solution. Just using an application framework will be the best idea. There are many of them: IHP, RIO, EulerHS, Hydra etc.
  • Design patterns. In contrast to the spread folklore, various idioms like Foldable, Monoid, Semigroup, Category etc are less like application-level design patterns and more like idioms for data structures. Haskell has more general design patterns for various needs: MVar Request-Response pattern, HKD, Typed-Untyped, Bracket etc. These ones allow to design a system with a proper idea behind each piece. No need to invent wheels when it’s already provided.
  • Proper state management. This is an important design category. In Haskell, it has many dark places. For example, a bare IORef is good if there is no concurrency. For concurrency, it’s better to consider STM or MVars. This topic has a lot of technics how to manage the state properly. Again, it’s a matter of good design.
  • Proper error handling. This is actually a very tricky subject, so it’s better to follow best practices and proven paths.

These questions highly resemble the top-down development process. As you can see, it’s not just code hacking. Naive code hacking could be okay for a single person and for a hobby project, but industry-grade projects should not be developed like that.

Unfortunately, these high-level questions cannot be answered in a detailed way on a forum, so I highly recommend reading my book, “Functional Design and Architecture”, to get those best practices and design approaches for Haskell. This book contains everything you need to know about engineering in Haskell.

What about local code problems, for example, what libraries are better, what language features to use, what specific data structures to use, - this all follows next. Other Haskell books and resources can teach you how to apply the language properly on a lower level than high-level design of the application.

18 Likes

I strongly agree and think this is one of the key messages the Haskell community should be pushing. It would demystify Haskell to outsiders and help improve the quality of software written in Haskell.

9 Likes

Types are cheap (unlike classes in most languages). Don’t hesitate to create even throw-away oneline type if needed.
But avoid type synonym. They add a layer of indirection and make error message more confusing.

6 Likes

Might you or others be interested in posting a framework guide? Playing around with Monomer and Servant, it’s obvious why you might want a framework; it gets that pesky IO code out of the way (and also allows you to dodge program-structuring wars) and lets you play more with pure functions.

I’m actually surprised that EulerHS exists: a complaint I’ve heard around FP is that FP doesn’t make CRUD apps simpler to develop, and thus rarely gets used. A framework that simplifies writing CRUD apps seems to be quite valuable.

1 Like

I know people will disagree but writting a CRUD app with Yesod is actually pretty straight forward.

I find them useful for documentation purposes, Title -> Category -> Author -> Post is better than String -> String -> String -> Post.

1 Like

Of course but it gives you a false security and every time you need to “convert” a Title to a String you need to check the doc to find out that there is nothing to do.
Above all is not much better than

... :: String -- ^ Title
    -> String -- ^ Category
    -> String -- ^ Author
    -> Post

Or just look at the argument names. In all cases you need to go to the “doc”.

Another alternative using cheap types is to do

newtype Title = Title String
newtype Category = Categyro String
newtype Author = Author String

But only don’t use those types except when calling a function. In fact we probably make them unique to the function with

newtype MkPostTitle = Title String
newtype MkPostCategory = Categyro String
newtype MkPostAuthor = Author String

then declare

mkPost :: MkPostTitle -> MkPostCategory -> MkPostAuthor -> Post
mkPost (Title title) (Category category) (Author author) = ...

and when you are calling it

   mkPost (Title title) (Category category) (Author author)

That is just forcing the caller to check what is doing.
That way, you don’t need to look at any doc, everything is in the error message.

Ideally, I would like to be able to export only the constructor without exporting the types (maybe it can be done somehow).

Alternatively there is the record way (another throw-away type)

data MkPostParam = MkPostParam {title, category, author :: String}

mkPost :: MkPostParam -> Post

calling it

 mkPost MkPostParam{title=title, category=category, author=author}
3 Likes

In my book, there is an introduction to Servant. However, having a separate tutorial would be nice.

I and my team have designed EulerHS for Juspay in order to simplify the backend development, including SQL, KV DB, logging etc. (SQL is not extremely simple though because it uses beam). In essence, EulerHS is the sibling to my personal framework, Hydra. The latter is a showcase project for my book, but not only. It’s functioning. You’ll find demo apps shipped with it. This might be a useful content.

Writing a tutorial doesn’t fit into my current activity unfortunately. But again, my book is a good replacement for it

2 Likes
  • Build types such that wrong states do not have a representation
  • Don’t try to find the most elegant/abstract solution right away, but go for simplicity at first
  • Once stuff kind of works, never hesitate to do a big refactoring

Those hints take advantage of the fact that the compiler can be your friend. Max out this advantage at any time. If you plan changes to your code, you can intentionally break your code at strategic places and then just fix the compilation errors one by one.

7 Likes

what are Haskell’s notions of functional interfaces?

I like stylish haskell a lot. Fourmolu and I had to split up, it didn’t work out between us.

2 Likes

Browse Hackage and read Haddocks. Poke into the hyperlinked source. Write little sketches you run in ghci to try out extensions and libraries and really get a feel for what Haskell programming can feel like. I have two guiding lights: “What problem am I trying to solve here?” and Curry-Howard (“What am I trying to prove here?”) Your gut will take you the rest of the way. This was my main method of learning post-LYAH.

1 Like

Free monads, but not only. Depends on how close these interfaces should be to the essence of this notion. Free monads are the best. Also, usual interpretable languages may be a good competitor. Bare functions and Service Handle are a bit dumber. FT/mtl do not posses the needed properties.

1 Like

20 posts were split to a new topic: Type classes vs interfaces

If you have an account on X (or any other means to access the content there), you can read my longer thread on the subject.

TL;DR stylish-haskell doesn’t format much, and in my OSS projects, I want to say to external contributors “Just apply this formatter” instead of arguing over stylistic choices. Besides, since fourmolu is configurable, I can have a config that is close to my personal preferences.

1 Like