
Confession of a Haskell Hacker - malloc47
http://r6.ca/blog/20120708T122219Z.html
======
crntaylor
This post is a representative of everything that I both love and hate about
Haskell:

1) It is certainly true, and I've had the experience many times, that "if it
compiles, it's correct" applies frequently to Haskell. Even better, I've
frequently written code for the first time, hit the compile button, and had it
work first time - something that almost never happens to me with Java or C. 2)
The huge, HUGE caveat to that is that it's only true for pure and sufficiently
polymorphic code. If you're writing anything in the IO monad, or if you're
using specific data types (e.g. Int, []) rather than writing polymorphic code,
then all bets are off.

There's only one way (ignoring ⊥) to write the forward pipe operator (|>) :: a
-> (a -> b) -> b in Haskell. If your code compiles, it's guaranteed to be
correct.

There are many, many ways to write the isPrime :: Integer -> Bool function,
which differ widely in correctness, understandability and efficiency. Once you
move from type variables to concrete types, you open yourself up to many more
potential errors.

There are even more ways to write the function deleteFile :: FilePath -> IO
(), including but not limited to (i) deleting the file and doing nothing else,
(ii) deleting the file and its containing folder, and (iii) wiping your entire
filesystem. All of these would type check. Sure, you probably wouldn't make
these mistakes, but the point is that _the type system won't help you here any
more than it would in <insert untyped language here>_.

I think Haskell is an incredible language. It's certainly the language I have
the most fun with, and I find its approach to parallelism, concurrency, I/O
and state to be natural and appealing. But there's a real danger of
overstating what Haskell is capable of, and turning off newcomers to the
language in the process.

~~~
djhworld
Your description on deleting files not being protected by the type system is
the result of side effects at runtime.

The point of the OP was discussing the safety that can be achieved from pure
code.

~~~
crntaylor
I feel like I addressed this fully in my comment. I agree with the OP - you
can do great things with the Haskell type system, _if_ you're writing pure
code and _if_ you're writing sufficiently polymorphic code. I think this is
awesome!

 _However_ , aside from a few libraries and a smattering of very heavily
theoretical work, no code satisfies both of those predicates. If you want to
write a 'real world' application in Haskell, you're going to have to get your
hands dirty with IO and with concrete types, which means that the ability of
the type system to prevent you from shooting yourself in the foot is severely
curtailed.

------
batterseapower
I find this happens a lot with Haskell. I can write hundreds of lines of code
and have them work perfectly as soon as I get a error-free and warning-free
compilation.

The type system is definitely a big part of this, but almost as important are
algebraic data types and compiler checks for exhaustivity when scrutinising
values of such types.

~~~
jlarocco
This just screams "bad development practice", IMO.

Maybe I've only worked with amazing super hero developers (not likely), but
the type of bugs avoided by Haskell's type system just aren't a big problem in
any of the projects I've ever worked on.

The difficult bugs are almost always mistakes in the requirements and design
errors, which become even more difficult to fix when you have a bunch of code.

On the other hand, I guess I could see type errors being a problem if you
often write hundreds of lines of code before trying to compile and run it...

~~~
statictype
Bad requirements and design errors can't be fixed by a perfect programming
language.

Those are certainly the biggest problems in building software, but not the
types of problems _this particular article_ is talking about overcoming.

(I think we're both in agreement on this)

------
sanxiyn
This is supported by the parametricity theorem. That is a big word, but it
boils down to this: let's say you wrote an identity function of type "a -> a",
and it passed the type checker. Then it is correct: you simply can't do much
with a value of type "a", because you don't know anything about it.

If an identity function is too simple, consider "compose :: (a -> b) -> (c ->
a) -> (c -> b)". I think it can be proven that if you write an implementation
that passes the type checker for this signature, the implementation is
necessarily correct.

~~~
DanWaterworth
That's not quite true, it must either be correct or bottom.

I can write:

    
    
      id :: a -> a
      id = id

~~~
sanxiyn
This is why Haskell is evil and you should use ML.

~~~
tikhonj
This is why Turing-complete languages are evil and you should use Agda.

------
sanxiyn
From the article: "The types are so polymorphic that I conjecture that there
is only one way to write functions matching the required types such that all
parameters are used non-trivially and recursion is not used."

He is not arguing "it works if it compiles" in general. He is arguing "it
works if it compiles", because types are so polymorphic. Polymorphic part is
actually important. If you don't get that part, you are missing the point.

------
weeksie
Ah, a Haskell circle-jerk waiting to happen and a confession that's really a
way to act superior about Haskell's type system. Listen, it's a great language
and I've spent a few years deep in it, but I find this post arrogant and not
even accurate.

The libraries he is talking about are for data types, something that the type
system (almost by definition) is perfectly adequate for testing. So yes, he's
tested it by compiling it. But for any real world Haskell program the type
system alone is not enough.

~~~
flatline3
I think your reply is inappropriately negative ("circle-jerk", "real world"),
and that the negatively of your post detracts from what value might be derived
from the article.

The author of the post provided an amusing example of a case where he posits
the type system is sufficient and valuable. He didn't claim that the type
system would be sufficient for a complete real-world application.

~~~
weeksie
Well, I guess I'm just old enough and ugly enough to call things like I see
them. Haskell is a neat language, but it is frustrating to listen to these
sorts of brag-posts about the language because they're filled with dangerous
hubris. The type system is amazing for certain kinds of problems. Hey, if
you're writing a compiler then Haskell is probably the best language out
there. Or in memory data structures. Hm. Or parsers. . . . It's a great
language for CS research.

The term "real world" is not meant to slur, it's meant to be accurate. I've
shipped code written in Haskell and I deeply understand the huge pain in the
arse it is to use in production for applications that are not toys.

~~~
ozataman
Sorry to burst your bubble, but I wonder if your experience is clouded by the
(past) inadequacy of your skill set and learning level. We use Haskell in
production across a number of domains and it has produced solid results. So do
a number of other very serious companies out there (as an example, most banks
have varying degrees of Haskell in production for critical systems).

Also, how are parsers or in-memory data structures NOT real-world tasks? You
never had to parse some bit-stream or stage a sophisticated-enough computation
pipeline that required lots of different, intermediate data types?

I'm not sure why you're seemingly dying to make negative blanket statements
based on how "it was a pain in the arse" for YOU.

------
Cieplak
What are functional lenses?

[http://www.haskellforall.com/2012/01/haskell-for-
mainstream-...](http://www.haskellforall.com/2012/01/haskell-for-mainstream-
programmers_28.html) <http://stackoverflow.com/questions/8307370/functional-
lenses>

------
DanWaterworth
I think the interesting point here is that Haskell provides such a high static
assurance out of the box that what you write is correct that this can happen.
You'd never hear of a ruby programmer releasing a gem without trying it.

~~~
calpaterson
With the serious caveat that the program you are writing must only be
vulnerable to the kind of errors which Hindley-Milner can show aren't present.
ie: no IO, no non-deterministic concurrency, no non-total functions, etc, etc.

~~~
dwc
I wish this caveat were attached every time someone mentioned the "once it
compiled it worked first time" bit. Not because there's no truth in the works-
if-compiles idea, but because getting smug about it is sure to bite you in the
ass sooner rather than later.

Now that I've said that, I'll state what should be obvious (and probably is to
many/most): failure to compile due to type errors is a big clue I am thinking
about something incorrectly or incompletely. It's not like I just twiddle
stuff until it compiles. I look at it and say, "Duh! That was dumb of me!"
It's like using Unit Analysis working out a long physics problem... errors in
your thinking about it are very likely to show up in unit analysis. The chance
of you coming up with a bogus equation that passes unit analysis is pretty
small. When it doesn't pass, you have a good look and find where you went
wrong rather than merely tweak.

~~~
scarmig
That little connection sent me on a Google quest.

Interesting paper along those lines:

"Types for Units of Measure: Theory and Practice":
[http://research.microsoft.com/en-
us/um/people/akenn/units/CE...](http://research.microsoft.com/en-
us/um/people/akenn/units/CEFP09TypesForUnitsOfMeasure.pdf)

~~~
dwc
Oh! I am so glad you looked that up. Thank you!

------
nothacker
I have some open source projects that I've released on multiple occasions
without having tested them. I think people make the assumption that things
that have adequate documentation and comments/support tickets, etc. that
indicate use, that they are tested before release. That is simply untrue. You
get what you get. That is true in the world of both paid and unpaid open-
source and closed-source software and well as life in-general.

Something that helps in this regard is travis-ci. It's a free CI server and if
you have at least some level of testing, you have some level of confidence.

------
spookylukey
Why did you write it if you have never run it? Was it just for people who read
your article and might want to run the code?

~~~
VMG
Probably as an intellectual exercise.

~~~
hobin
But what point is there to an intellectual exercise if you don't know whether
you've done it right in the end?

~~~
srparish
"Beware of bugs in the above code; I have only proved it correct, not tried
it." -- Knuth

~~~
hobin
Touché. I see your point.

------
Xion
That's not very surprising; Haskell's type system is really that ridiculously
powerful. But I suspect many hackers working with other languages would be
able to state the equivalent: "I released a library which I only unit-tested,
without writing any helper project that actually uses it".

~~~
ludflu
except that they had to write unit tests - whereas the compiler/type system
automatically checked the type safety of the code this guy wrote.

(Not that writing unit tests for haskell code is a bad idea - QuickCheck is
WAY more powerful than junits, for example)

~~~
Confusion
QuickCheck and JUnit have different purposes. QuickCheck is not a unit testing
framework.

~~~
Xion
> QuickCheck and JUnit have different purposes. QuickCheck is not a unit
> testing framework.

Indeed. What I meant was that basically:

    
    
        P(HaskellCodeCorrect|Compile+QC+UnitTest) =~= P(OtherCodeCorrect|Compile+UnitTest)
    

while the distribution of effort between compilation (if any) and unit tests
in other languages are quite different than in Haskell, i.e. skewed towards
tests in the former and getting code to compile in the latter case.

------
jlarocco
And that's something he's proud of?

If I never ran my code, it wouldn't have any bugs either, no matter what
language I was using.

I guess it does help explain all the half-assed packages on Hackage.

------
gosu
Hindley-Milner is powerful, but there are usually multiple ways to construct a
value of the correct type. Only one of those ways is the correct way, and I'd
want to test my functional code to make sure that this was the way that I
chose.

~~~
JadeNB
As sanxiyn points out in <http://news.ycombinator.com/item?id=4215055>,
although there are certainly many ways to produce a value of any _concrete_
type, sufficient polymorphism will often guarantee ( _via_ the parametricity
theorem) that there is only one total function of a specified type.

------
aw3c2
nothacker, you are "dead"/"ghosted". I am reposting his comment here:

nothacker 42 minutes ago | link [dead]

I have some open source projects that I've released on multiple occasions
without having tested them. I think people make the assumption that things
that have adequate documentation and comments/support tickets, etc. that
indicate use, that they are tested before release. That is simply untrue. You
get what you get. That is true in the world of both paid and unpaid open-
source and closed-source software and well as life in-general.

Something that helps in this regard is travis-ci. It's a free CI server and if
you have at least some level of testing, you have some level of confidence.

------
smcl
Minor aside, is "I conjecture" valid English?

I'm not criticising, just curious and in the pub.

~~~
lubutu
'Conjecture' is a verb as well as a noun. But I'll admit it sounds weird — by
French inflection one would expect "I conject", which seems to be obsolete...

~~~
sigkill
Yep. Dictionary.com calls it obsolete. Webster wants me to pay, and OED can't
find it.

conject late 14c., obs. verb replaced by conjecture (v.). Also in form
congette.

~~~
andrewcooke
my _concise_ oxford dictionary (dead tree, c 1984) lists conjecture as a verb
(not marked as obsolete).

~~~
tikhonj
It's the word "conject" that's obsolete, not "conjecture".

~~~
andrewcooke
oh, sorry, mis-followed the conversation.

------
dkubb
As someone who is interesting in writing more Haskell, I've heard all the "if
I get it to compile it works perfectly the first time", and I wonder if people
are missing out on what proper TDD can bring them.

At first TDD becomes a way to assert your code does what you expect. After a
few years you begin to use it as a design technique, and the correctness
argument becomes less and less of a reason for using it. It's more of an
(albeit nice) side effect at that point. I can write code that I test after
the fact and still get the same correctness benefits, but the feedback I get
from testing my design is gone.

Maybe I'm missing something, but I don't know if just having an excellent type
checker is enough to provide the same quality feedback loop as well executed
TDD does.

~~~
tikhonj
You can use the types the same way--define your types and write type
signatures for your functions before implementing them.

I've found this particularly useful when writing really confusing code (like a
Prolog interpreter, for example). Coming up with the types on a function
clarifies what it has to do and shrinks the space of possible solutions
significantly.

Types are a symbolic way to reason about code. Using types, you can get
additional insight into how your function can be written just by following
some simple rules. A good example of this is realizing that a function you're
working on is actually a specific version of a more general function--you can
see this if the type signatures look similar. This lets you reuse very high-
level, generic code easily.

Going back to the Prolog example, I was having some trouble figuring out how
the resolution algorithm should work. Then I realized that the sub-part I was
having problems with was just special type of fold. This helped me get a very
concise version of the function by reusing an existing library function.

So types can provide exactly the same sort of benefits as TDD.

------
anothermachine
This is why Hackage is full of junk and Haskell is unapproachable for real-
world projects. Everyone's throwaway toy file is published as a package, and
the useful well-documented packages get lost in the mix.

~~~
tikhonj
Ignoring the baseless FUD in the first part of your post, your last point is
actually valid: it _is_ hard to find good, maintained packages on Hackage.
Right now the standard solution seems to be just asking somebody with more
experience, but this is clearly not scalable.

Happily, a new version of Hackage creatively called Hackage2 is being worked
on which should ameliorate this problem (along with some other improvements).
Improvement is just around the corner.

~~~
anothermachine
Hackage2 has been around the corner for years. The "public testing instance"
doesn't exist. <http://hackage.haskell.org/trac/hackage/wiki/HackageDB/2.0>

If someone's actually _working_ on it again, that would be delightful.

------
stewbrew
As somebody who knows Haskell only superficially, this line of code and the
author's statement makes me wonder if people can read their own or somebody
else's code after some time.

~~~
dwc
Yes, you can. I'm not a Haskell guru, and I haven't played with it in some
time. I can read that line of code just fine. I don't actually know what it's
doing, because I don't know the functions involved, etc., but the form of the
line is easy enough to grok. Like any other language, it looks mysterious if
you haven't read and written enough code in it, or in a language with a
similar style.

~~~
dscrd
"I don't actually know what it's doing"

Sorry, but that means you couldn't read it.

~~~
papsosouid
No it doesn't:

    
    
        foo(bar(x))
    

Can you read that code? Yes? But you don't know what it is doing, because you
don't know what the functions foo and bar do. Same deal with the haskell code
in question. Just because you don't know what Compose and getCompose do,
doesn't mean you can't read the code.

~~~
dscrd
I don't feel it's so clear cut. Haskell fuzzies the difference between
syntactic and semantic elements. What in your piece of code is the parentheses
might very well be defined as functions in Haskell. For instance, the ($) and
(.) functions, but also I would call StateT and perhaps Compose and WrapMonad
(while not knowing what they do) such things too.

So, I still feel that understanding a piece of Haskell means that you will
need to understand the individual functions at least superficially.

~~~
papsosouid
You feel wrong. There is no such fuzzy. StateT, Compose and WrapMonad are
functions. They are just like foo and bar in my example. Just as in a C-style
language you need to know the parens mean function application, in haskell you
need to know spaces and ($) are function application, and (.) is function
composition. The code is perfectly readable even if you don't know what the
functions being used do.

