
Pitfalls in Haskell - 0xmohit
http://users.jyu.fi/~sapekiis/haskell-pitfalls/
======
panic
_The Haskell 98 report mentions a strange type system restriction. It is
called the monomorphism restriction and concerns making decisions about types
during inference. It sounds like abstract nonsense, but makes sense in the
context of category theory, where morphisms are a generalization of functions
and monomorphisms in particular correspond to injective functions. Regardless
of its cryptic name, it solves a practical problem: it prevents ambiguous
types from appearing._

The "monomorphism" in "monomorphism restriction" isn't the term from category
theory: it's just the opposite of "polymorphism". You can read more about the
details of the restriction and why it exists here:
[https://www.haskell.org/onlinereport/haskell2010/haskellch4....](https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-930004.5.5)

~~~
pklausler
I don't really mind the monomorphism restriction for definitions whose types
aren't functions, but it bugs me quite a bit that a function defined in terms
of pattern matching won't trip over the monomorphism restriction but the
otherwise-equivalent definition in terms of lambda can.

------
Morgawr
There are a lot of really annoying quirks about Haskell. It is a beautiful
language but after using it professionally for a while, some things come to
mind that really make me pull my hair out of frustration (although I
understand why they exist).

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.

~~~
dllthomas
Regarding...

    
    
        A{} or B{} -> f1
    

a big part of the problem is that A and B are going to be putting different
things into scope in the context of f1.

If you're not binding anything, you could do it with guards:

    
    
        ...
        | isA myData || isB myData -> f1
        ...

~~~
Morgawr
This assumes that you have an isA and an isB function. This forces me to write
such a function which I might not have and in case of very very very large
data types (like ~20-30, if not more, constructors) it becomes unwieldy and
very messy.

~~~
dllthomas
It's probably less work than duplicating the cases, and ideally you can give a
more meaningful name to the whole "isA or isB" construct - especially when you
have a lot of constructors!

------
nothrabannosir
They saved the best for last:

 _3.5 Naming Conventions_

 _Some names are inconsistent. For example Functor is not called Mappable
while Traversable is not named after abstract nonsense. It is best to focus on
what things are instead of what they are called._

~~~
rspeer
In my only significant Haskell codebase, I end up using the Monoid abstraction
a lot. I don't want people who have to read the code to have to cope with the
word "Monoid". Monoids are simple, but the name makes them sound like they're
"like monads but worse because you haven't heard of them".

I wanted to rename "Monoid" to "Joinable" in my code ("Concatenatable" is too
long). But it turns out you can't alias a type class while maintaining
compatibility with libraries that use it.

~~~
MustardTiger
And now you would helpfully give everyone reading your code either a huge pain
if they know haskell, or a completely wrong impression of what that class
actually is if they don't. Great!

~~~
rspeer
People who know Haskell can cope with much more indirection than a type alias.
Also, I don't know many people who really know Haskell anyway.

Do tell me more about how "concatenatable" is a "completely wrong" impression
of what Monoids are for. I know you can use them to, like, multiply using
abstract nonsense, but that's not _why_ you care what a Monoid is. Monoids let
you generalize `mappend` over many kinds of sequences.

~~~
MustardTiger
>People who know Haskell can cope with much more indirection than a type
alias.

People can cope with all kinds of bad things, regardless of whether or not
they know haskell. That is not an argument in favor of bad things.

>I know you can use them to, like, multiply using abstract nonsense

What? There's nothing "abstract nonsense" about it. Integers are monoids.
Things that can be appended is a subset of monoids, not a description of them.

~~~
dragonwriter
> Integers are monoids.

I think that one bad habit that Haskell promotes is statements of this form,
which are true _in Haskell_ because of the way Haskell forces you to define
typeclasses, but aren't really "true" of the concepts modeled by typeclasses.
(Integer, +, 0) defines a monoid, as does (Integer, ×, 1).

> Things that can be appended is a subset of monoids, not a description of
> them.

The key operation that defines a monoid is the append operation ("mappend" in
the definition of the Haskell typeclass Monoid, to avoid collision with the
list-specific "append" operation), which for the monoid of integers over
addition is (as the name suggests) addition.

~~~
MustardTiger
What? The statement is true in reality, it is not true in haskell. Haskell
does not provide a Monoid instance for Integer, you have to use Sum or
Product.

>The key operation that defines a monoid is the append operation

No, it is the sole monoid operation. It is named mappend in haskell, but that
does not make everything it encompasses into appending. Ask someone what
operations can be done on "appendables" and you will get lots of answers like
looping, sizeof, etc. Append always refers to collections in other languages,
so the argument that it would help people to call something different
"appendable" for those people is silly.

~~~
dragonwriter
> The statement is true in reality, it is not true in haskell.

No, the specific statement is true neither in reality nor in Haskell, though
statements _of that form_ (which is what I said) are often true in Haskell
(e.g., List is a Monad) though not reality.

Integers are not monoids. As I stated in GP, the combination (Integer, +, 0)
_defines_ a monoid -- as does (Integer, ×, 1); there's other monoids one could
define on Integers, though those are the two most obvious and useful ones.
Haskell, because its designed to make statements of the form _type_ is a
_typeclass_ has to do one of two things to support the case where multiple
useful instances of a typeclass can be defined involving a particular type --
one is the newtype wrapper approach used for Sum and Product with numeric
types as monoids, with the base type participating in none of the instances;
the other is to choose a primary instance that the base type participates in,
and relegate the others to the newtype treatment (this is what is done with
lists as monads in Haskell).

Both of these approaches are workarounds for the problem that typeclass
instances aren't naturally defined by a participating type alone, but by the
combination of a participating type, and one or more other values (some of
which are usually functions.)

~~~
MustardTiger
>Integers are not monoids. As I stated in GP, the combination (Integer, +, 0)
defines a monoid

This is simply being pedantic. We're all well aware of the meaning of what I
said, as it is how it is almost always said.

>Both of these approaches are workarounds for the problem that typeclass
instances

That's not a problem, it is the point. That is what typeclasses were intended
to be.

------
seagreen
1.6 "Monad Type Class" was fixed in GHC 7.10!

Some of these complaints are goofy like the one about the naming of Functor.

Others like the one about records being bad are perfectly legitimate. It would
be awesome to see real improvement on this. Unnecessary runtime errors are
evil.

