notes-computer-programming-haskell-whyNotHaskell

why not haskell?

Haskell is great and a lot has been said about its advantages. Therefore, here I will here focus on it's flaws. Actually, I think most programmers should learn Haskell.

note: i don't know haskell very well and most of this is probably FLAT OUT WRONG. also, it's years out of date, and much of this may be fixed by now.

It's cumbersome to make everything a typeclass

See | Ralf Lammel, Klaus Ostermann. Software Extension and Integration with Type Classes. Contrast Fig. 6 with Fig. 7.

If you write your code without typeclasses, like in Figure 6, you can't later use "Eval" and "Print" with anything other than the data type Exp that you defined. This is in contrast to dynamic OOP languages like Python, where you can replace a class with another class, and, presuming it provides the same interface that the old one did, it works fine.

If you write your code like in Figure 7, it's hard to read and hard to understand.

There are a variety of add-on frameworks to solve this problem floating around in the Haskell community. Some of them are preprocessors or language extensions. Here's a list: http://www.haskell.org/haskellwiki/Applications_and_libraries/Generic_programming . <sarcasm>If you like trying to decide which Web framework to use in Python, you'll love trying to decide which Haskell generic programming framework to use</sarcasm>. If you want to actually compare them before choosing, you must be able to read programming language research papers. Here is an example of text from the Haskell wiki describing one of these ("Scrap Your Boilerplate 3", not to be confused with "Scrap Your Boilerplate" 1 or 2, or "Scrap Your Boilerplate Reloaded" or "Scrap Your Boilerplate Revolutions" or "Smash Your Boilerplate"): "SyB?3 also requires the restriction on instance declarations to be relaxed in two ways: undecidable instances allow type classes constraints to be satisfied coinductively (the translation generates a recursive dictionary.) Secondly, SyB?3 relies on overlapping instances to override generic definitions of type-indexed functions for specific types. Overlapping instances are not an essential part of SyB?, but they do simplify the use of type-indexed operations.". Of course, the actual research papers are even harder to understand.

Maybe someday Haskell itself will officially settle on one of these solutions.

Lack of typeclass-ness leads to prefixes

Because everything is not a typeclass, you end up using weird prefixes for operators, for example Map.!, so that you can import multiple of these without them conflicting.

The compiler insists that everything should not be a typeclass

If you only mention typeclasses in your Haskell program, you get some sort of an error upon compilation that basically means that the compiler didn't know which actual datatype to instantiate (except that one or two built in typeclasses, I think ones having to do with numbers, have a default type; but I don't think you can define custom ones like that, and I don't think most of the typeclasses in the standard library are like that.

Sorry, I forgot what the error message was. Mb it is this? (todo):

for instance, if i say "array (0,0) [(0,0)]", I get "Ambiguous type variables `t', `a' in the constraint: `IArray a t' arising from a use of `array' at <interactive>:1:0-18 Probable fix: add a type signature that fixes these type variable(s)". Fix (?): default data type instances associated with typeclass?

Typeclass syntax should look like type syntax, and typeclass signatures should look like type signatures

Many things in popular libraries are not typeclasses

This leads to trouble when you want to use those libraries on custom data structures.

For example, Parsec, a popular parser library, used to not be able to parse bytestrings (in Haskell, the normal kind of String is a linked list of chars) (now it can, but you still have to call it a different way: http://stackoverflow.com/questions/2090399/using-haskells-parsec-to-parse-a-bytestring ). This is because the Parsec implementation referred to String, which is defined as a datatype, not a typeclass.

In general, lists in Haskell standard libraries are often defined as datatypes, which forces them to be linked lists. Contrast with Python sequences or Python iterators, which are just defined by "protocols", that is, method interfaces that objects must implement to fill those roles (similar to typeclasses).

Recurring experience: fighting the compiler

"Old programs read like quiet conversations between a well-spoken research worker and a well-studied mechanical colleague, not as a debate with a compiler. Who'd have guessed sophistication bought such noise?" -- Dick Gabriel

"Haskell programmers are in an eternal dialog with the (very intelligent) compiler, but when such intelligent being talks, sometimes the messages are obscure in his metalanguage of meta-types and meta-abstractions." -- Alberto at http://unenterprise.blogspot.com/2008/02/tell-us-why-your-language-sucks.html?showComment=1203431940000#c2736928901933687271

Somehow, every time i use Haskell i find myself spending a long time having to learn about some obscure corner of the type system. Just one person's experience, maybe it won't happen to you.

No keyword arguments

Haskell only supports positional, not keyword, argument passing.

Hard to reason about memory leaks

It's hard to reason about memory leaks (space complexity; "space leaks") in Haskell. There seems to be no way to just turn on "strictness by default" in some large chunk of your program (you can turn on strictness manually by making a small change to the code of each operation, but that's a lot of typing).

Specific complaints about profiling in GHC

Note: remember these complaints are a few years out of date, mb they've been fixed by now

time and space profiling in GHC may be better than it was before (before i learned Haskell), but it still sucks. What is needed is for the profiler to quickly tell you:

Proliferation of names caused by referential transparency

In a language with mutable variables, you'd often say:

x = (expression) x = (expression involving x) x = (...)

In Haskell, it has to be:

x = (expression) x' = (expression involving x) x = (...)

that's annoying, especially if you later need to add a step in between x' and x.

Static typing slows down rapid prototyping

When you are rapid prototyping, you constantly want to change the type signatures of your objects and functions. This takes a long time in Haskell (but wait, you say, doesn't Haskell have type inference, meaning you rarely have to write the type signatures explicitly? Maybe in theory, but since i am imperfect, as i code i keep making mistakes which cause compiler errors, and i find the compiler errors impossible to debug without adding a bunch of type signatures to things).

Currying might cause trouble with RMI b/c functions are harder to marshall than objects

"Defining data as dynamic higher-order functions worked badly when most computing was standalone. And it works even worse now that we have these things called networks. The problem is that a dynamic higher-order function is a sort of ethereal construct, a speck of four-dimensional pixie dust, and the idea of saving it to disk or sending it over the network does not make any particular sense." -- http://www.cppblog.com/cfmonkey/archive/2008/07/31/57671.html

This is more of a theoretical problem than one that I have personally faced, and I suspect that people have developed ways around it.

Custom precedence makes code hard to read

Since the syntax allows custom precedence, you can't tell just from looking at an expression how to parse it; you have to know something about (or go look up) the definition of every function in it.

Namespaces are insufficiently modularized

I'm not sure about this part, but, i think that if anyone whom you indirectly import imports a typeclass definition or implementation, you're stuck with it.

That stinks, and also, it makes it hard to find the code that performs a given operation when reading code.

Seemingly recommended behaviors require language extensions

A lot of behaviors which seem like reasonable defaults are only present as language extensions, so as a noob you find yourself trying to understand confusing shortcomings of the compiler that would go away if only you had known to enable a bunch of weird-sounding extensions. When you search for information on these situations, you find old-timers saying something like, "oh, just enable extension X, I always use that one". Well, if everyone's first response to a common problem is to tell you to use extension X, extension X should be part of the core language.

Community: stylistic tendency to use lots of punctuation

The community likes to define functions (or "combinators", as they like to call them here) with names full of punctuations rather than letters. That's valid, but I don't like it, because it seems to subconsciously make everything seem more difficult to understand.

Monad transformers

You have to use monad transformers when you want to combine monads, but they are hard.

A lot of monad stuff to learn even without monad transformers

E.g. at http://kawagner.blogspot.com/2006/12/my-haskell-experience.html , a blogger demonstrates how adding the IO monad to some code made it harder to read. At http://kawagner.blogspot.com/2006/12/my-haskell-experience.html?showComment=1167306780000#c1839087203684138705 , a commenter suggested:

" 1) The idiom do x <- foo return (f x)

is equivalent to fmap f foo

which would make your code a lot more readable.

2) Most probably, your any-inside-of-a-Monad would have been neatly accomplished by liftM any

There are a lot of these tricks and neat little helper function to help you handle the monad enclosures. ... "

So in order to learn to read idiomatic Haskell written by others, in addition to learning how to do all the normal stuff that you'd have to learn for any language, you'll have to learn "a lot of these tricks and neat little helper function to help you handle the monad enclosures".

If you ever do want to make mutable data structures, the syntax is clumsy

I never got far enough to need this, but in a blog post http://kawagner.blogspot.com/2006/12/my-haskell-experience.html (note: from 2006, may be way out of date) the author asserts, "Even Java code is more concise than Haskell code which has to change things." as gives an example:

" For example I have to add elements to a map. In Java I can write

map.add(key, value)

In Haskell the code is something like

modify $ \st -> st { st_map = Map.insert key value (st_map st) }

('modify' if from a state monad).

If I want to access those data:

value = map.get(key)

in Haskell:

st <- get let value = Map.lookup name $ st_map st "

a commentor says,

"

Paul Johnson said...

    The problem with updating state comes from Haskell's clumsy way of modifying a record. Say I define a record containing fields "foo" and "bar". In Haskell foo and bar are automatically defined as getter functions. But there is no equivalent setter function. Instead you have to use a clumsy bit of special case syntax. To increment the foo field in a record "state" you would have to write
    state1 = state { foo = foo state + 1 }
    The workaround is to write your own setter functions using this syntax."

but the original author points out that it's hard to write pretty reusable setter functions too:

"

    Sure, you can write something like
    addValue key value = modify $ \st -> st { st_map = Map.insert key value (st_map st) }
    But chances are that you only need it in once in exactly this form. Problem here is that you can't simply write a generic version of 'addValue' which works not only with 'st_map' but also with some other value in a state, for example 'st_props'. If you only need a getter, it's simple:
    getValue prop key = do
    st <- get
    return $ Map.lookup key $ prop st
    now you can simply write "value <- getValue st_props key" (but you still need the '<-' syntax of course and can't simply insert (getValue ...) into some expression).
    But this solution isn't possible for setters because "st { st_map = ... }" is a special syntax and not a function. So you first have to create a 'setter-function' (like \st v -> st { st_map = v }) from it and then use this in a generic setter function like the one above:
    setValue setter getter key value = do
    modify $ \st -> setter st (Map.insert key value (getter st))
    This can now be used this way:
    setValue (\st v -> st { st_tables = v }) st_tables key value
    But it isn't much shorter and less readable than simply using the full code at the top so I haven't used this."

another commentator suggests that a specific form of helper may help:

" When using Data.Map with MonadState?, I always create some helper functions:

    getMapState k = liftM (! k) get
    setMapState k a = modify (insert k a)
    These two go a long way towards relieving the syntax bloat you were speaking of."

another commentor asserts that Haskell simply can't handle 'imperative style' very well at all:

" I think most of the issue here is that you're trying *so* hard to use Haskell in an imperative style.

    Flipping this the other way, consider the pain of writing C++ in a functional style.
    Sure, you can go use|write all kinds of helpers (boost!) to make it less painful, but when it comes down to it, you've got to program in a style that fits the language."

Complaints about the standard libraries

Remember, these complaints are years out of date, some of these things may have been fixed..

Collections libraries

They are not very concise, orthogonal, and consistent across different types of collections.

See also:

"The containers library is a mess." -- http://osdir.com/ml/haskell-cafe@haskell.org/2011-02/msg00219.html , http://osdir.com/ml/haskell-cafe@haskell.org/2011-02/msg00222.html (agrees), another thread from 2009: http://osdir.com/ml/libraries@haskell.org/2009-08/msg00116.html , another thread from 2008: http://thread.gmane.org/gmane.comp.lang.haskell.cafe/45242 (i feel like i once saw someone saying that Simon Peyton-Jones agreed too, and that he was waiting for a student who wanted to clean it up as a masters thesis, but i can't find it now)

Some examples: there are different syntaxes for different containers;

No standard exception handling idiom

See also http://www.randomhacks.net/articles/2007/03/10/haskell-8-ways-to-report-errors

Other std libs

Tendency for libraries to make things into hermetically sealed boxes

People present neat libraries for special domains, but Haskell seems to tempt people to make such things into hermetically sealed boxes that can't be broken into when you want to access the internals and to something the designer didn't intend.

OK, I don't understand what I'm talking about at all in the following example, so maybe it's all wrong.

I looked at WASH/CGI, and it looked great, but it looked like it wasn't extensible/overridable enough.

For example, it looked like WASH would try to prevent you from modifying the HTML in the forms it creates, or handling an incoming HTTP form submission, or directly interacting with the persistent values stored in user cookies.

I sense that this is a consequence of Haskell's type system; I think Haskell might make it easy for you to write an intelligent, high-level DSL-like library to encapsulate some set of tasks, but maybe Haskell makes it hard for you to do this while at the same time allow the client to override or extend parts of your library in a piecewise or one-off manner.

For example, in an object-oriented language, here is how you could usually tweak generated form HTML provided by a web framework. The form generation code would probably be a method in some object class, and that method would probably return HTML. So the client would subclass that class, override the relevant method, and make a new method which calls the superclass and then makes whatever modifications it wants to the form. In WASH, however, the library API only exposes the WithHtml? type, which is isn't an HTML string, it's opaque. The connection between the WithHTML? type and actual HTTP requests is abstracted away, so you can't interfere with the rendering of a WithHTML? to actual HTML.

Of course other Haskell frameworks exist which don't make HTML rendering opaque, but the charm of WASH is that it does abstract that away -- it seems like it would be hard for Haskell to allow a library to say, "For 99% of your program, pretend that WithHTML? is all there is, but then if you want to override the HTML rendering every now and then, you can do that too". It seems to me that in order to allow the rendering to be overriden sometimes, you'd want to parameterize the WithHTML? typeclass with some parameter that allows you to choose your renderer, or something like that, which would clutter up the code everywhere.

Monadic operations are always ordered

As far as i can tell, there is no way in the core language to say, "here are two monadic operations (i.e. opening and writing to one file, and reading from another), and I DON'T CARE what order they are done in".

A little verbose

Basic syntax and core libraries is a little verbose compared to scripting languages

example: http://changelog.complete.org/archives/492-announcing-hsh-the-haskell-shell

another example: in-place list updates

Syntax for global variables and imperative programming is too heavy

In Haskell, things like global variables and imperative programming are not part of the elegant core of the language, but can be created as constructs; however, because they are constructs, their syntax is not as lightweight as it needs to be. Furthermore, understanding these constructs is difficult (although using them is not as difficult).

Specific complaints about error messages

Code cluttered with fromJust, toEnum, etc

Suggestion: universal coerce operator: full auto-coercion might be undesirable, however, there could be a universal "coerce" operator (a two parameter typeclass?). rather than cluttering code with, e.g. "fromJust", "fromIntegral", "toEnum".

I can't figure out how to make unboxed arrays out of arbitrary types

In C, you can define some structure and then make an array of that structure; you can't make such a hetrogenous UArray in Haskell, at least not with UArray (and i don't know of others that can), not as of a few years ago when i wrote this).

Refactoring a function from referentially transparent to non-referentially transparent is a pain

Have to change a bunch of types

The new impurity of the function will infect others up the call chain, so you'll end up changing a lot of type annotations.

Non-uniform syntax for equivalent monadic and non-monadic code

If you need to convert between monadic and non-monadic forms of things, there are a lot of boilerplate code changes (for instance, if you have a lot of helper functions that deal with IArrays, and you change them to use MArrays).

fix: a way to quickly change a "let..in" or "..where" clause into an UNORDERED "do" syntax. for example, i have a function that taken a 2D immutable array, and returns a list of lists. i want to change this into the equivalent function for mutable arrays. Now, this entire function's execution must be ordered w/r/t changes to the array -- but WITHIN this function, there are only reads, no writes, so the ordering doesn't matter. So, instead of scooping up all of my "where" clauses and putting them inside a "do" block (forcing me to impose an ordering), which has the added "boilerplate" hassle of putting "let " in the front of each line, it would be better if i could just change that "where" to "where-unordered-monad" (or whatever), change some of the "="s to "<-"s, and leave the rest of the function as-is. this means that the compiler is supposed to figure out an ordering for the clauses underneath "where-unordered-monad". (i've heard that arrows generalize monads, but i don't know about arrows. do they already do this?)

Unintuitive names

Example: there is a typeclass whose sole member is a function which is a generalization of "map". There is another one whose sole member is a function that gives you a sort of default value. I would have called these functions "map" and "default", and called the classes "Mappable" and "Defaultable". Haskell calls the functions "fmap" and "pure", and calls the corresponding typeclasses "Functor" and "Pointed".

Unfair complaint: must learn some details

You have to learn about strictness/non-strictness, boxed vs. unboxed datatypes, foldr vs. foldl, fairly early on. Otoh the imperative world has it's share of confusing things too; i think if you started out learning haskell and then learned your first imperative language 10 years later, there would be an equal number of headaches of this sort.

Functional laziness does not (as is sometimes suggested) let the programmer ignore evaluation order

One attraction of functional laziness is the promise of writing code faster because you use higher-level concepts (for instance, supposedly you can ignore evaluation order).

But in reality, ignoring evaluation order can lead to speed and (especially) memory problems. in the end, you need to become MORE aware of how the compiler does garbage collection and the order of evaluation than with imperative languages.

Example: if you don't understand the details of evaluation order w/r/t foldr/foldl/foldl', you can easily get a stack overflow: http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27

Example: my problems with breakUntilRight (todo)

Not really a complaint: community is too smart

You'd think this would be a pro, not a con, and overall it is. But there are some disadvantages to this:

However, in the long term, this makes you smarter, which is more important than getting things done.

Things that make the type system confusing

the Haskell type class defn syntax

"class Eq a where " looks like a is a type parameter of Eq. (btw does haskell allow a destructuring bind on a in that statement?)

example: the way polyvariadic functions work

this is confusing: http://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function

i'm not sure exactly what i want here. i consider this abuse of the type system but by what criteria? see [1].

the monomorphism restriction

http://www.haskell.org/haskellwiki/Monomorphism_restriction


Case study: Haskell vs. Python

Here's a blog post praising Python and giving an example of a program in Python, to which other language enthusiasts responded by posting the same program in their favorite language: http://norvig.com/spell-correct.html

Compare the Haskell version in this thread: http://groups.google.com/group/fa.haskell/browse_thread/thread/0821d82cf27e5a2e/5eb08166a5e1fff6?lnk=raot#5eb08166a5e1fff6 . Note that it is both longer and harder to read than the Python version!

On this page, I dissect the Haskell version line-by-line and compare it to the Python version, explaining where Haskell could be better: http://bayleshanks.com/src/shortspell.hs

Here is what i think it should, more ideally, look like in something like Haskell:

#include myusualimports

tokens = map (map toLower . dropWhile isPunctuation) . words

train filepath = corrector (train' (tokens {{readFile filepath}} )) 
        where train' = foldr (\w \m -> M.insertWith (+) w 1 m) (defaultMap 1) words

corrector fm word = maximumAccordingTo fm  (known [word] || known (edits1 word) || known (edits1 . edits1 word) || [word]) 
    where known            = filter (got fm) 


edits1 [word:words] = concat . map edits1 [word:words]
edits1 word = concat
   [[ word[0:i] | i <- range(n)], 
    [ word[0:i] ++ word[i+1] ++ word[i] ++ word[i+2:] | i <- range(n-1) ],
    [ word[0:i] ++ c ++ word[i+1:] | i <- range(n),     c <- alphabet],
    [ word[0:i] ++ c ++ word[i:]     | i <- range(n+1), c <- alphabet]]
    where n        = length word
          alphabet = ['a'..'z']

Further reading

http://unenterprise.blogspot.com/2008/02/tell-us-why-your-language-sucks.html


Jasper

Someday (when I have time, i.e. never) I plan to write a programming language called Jasper, which will address some of these complaints.

Jasper will be/have:

Addressing the above Haskell complaints, Jasper will have:

It would take me years of study to design a good type system for this language. Unfortunately I'll probably never get around to doing this. Ideally, the type system for Jasper will not be not quite as powerful as Haskell's (I don't mind if type-level programming is impossible), but it would be simpler, while still being "powerful enough". However I don't yet know enough type theory to know if I can deliver that (or even what I want there).

Unfortunately all my notes on Jasper are currently in an inconsistent state of flux, with no plans to correct that anytime soon. See [2] for my (most readable, yet still misformatted set of) notes towards the design of Jasper. As noted, it's not a priority for me in the next few years.

Other Haskell-related pages here

Also, I have some Haskell bookmarks on Delicious.


todo

todo: include complaints from http://unenterprise.blogspot.com/2008/02/tell-us-why-your-language-sucks.html

todo merge with

todo:

things i dont like about haskell

(see that guy's blog post; he had a bunch of good ones)

delete this: todo: merge with lacks a core feature of object-oriented programming languages: extensible operations of data types. Extensions exist to get around this, but they are complex and syntactically heavyweight (one of the simplest ways is to go from Figure 6 of http://homepages.cwi.nl/~ralf/gpce06/paper.pdf to figure 7; but look at all the boilerplate which is added. There are extensions to Haskell which do this for you but that adds complexity (even if the extended system is simple to use, you have to learn enough about these extensions to choose which extension to use) and presumably locks you in to the extension that you choose). * See this paper: Software Extension and Integration with Type Classes

why isn't everything a typeclass? as pointed out in that paper: your datatypes are closed unless everything is a typeclass. but that requires better syntax would be better if typeclass signatures looked the same as type signatures (except for those particular compile errors which reqire a distinction, whatever they are)

    i expect there are disadvantages to this, but i don't understand enough to know what they are yet.