My biggest gripe is record handling. There are a few libraries that help dealing with the pain of records, but if you're using 'vanilla' Haskell, they really can be frustrating.
For instance, any field name in a record pollutes the global namespace for that module.
data MyRecord = MyRecord1 { name :: String } | MyRecord2 { name :: NameType } <-- This is an error because the "name" field can't be both a String and a NameType (a made up type, just for the example). You need to use something like rName or r2Name to differentiate the two constructors...
...Which leads me to the other problem of different record constructors of the same data type having different fields, which can lead to some very ugly runtime exceptions (so much for a strong type system!): => data MyData = MyData1 { name :: String } | MyData2 { value :: Int }
=> let x = MyData1 { name = "hello" }
=> x { value = 0 } -- <-- this causes an exception because 'value' field does not exist for the constructor MyData1, but does for MyData2 so it matches the type system and there is no compile-time warning to catch it.
Another big issue I've recently had with my code was dealing with multiple constructors into pattern matches/pattern guard conditionals. For example, I have a record type like this: data MyData = A { ... } | B { ... } | C { ... }
and I want a case to match either A or B, otherwise do C. Let's say I have a function f1 that I want to run on A and B, and a function f2 that I want to run on C. I'm forced to do something like case myData of
A{} -> f1
B{} -> f1
C{} -> f2
Whereas I'd love to be able to do case myData of
A{} or B{} -> f1
C{} -> f2
This becomes a big problem when I have a lot of cases to take care of, and repetition makes it harder to spot bugs and easier to mess up.