Haskell Tutorial for C Programmers - Section V

Written by Eric Etheridge
version 2.0
last updated May 18, 2009

Page-Wise Table of Contents

  1. Introduction
  2. Section I: What the Heck is Going On?
  3. Section II: Towards Functions
  4. Section III: Now Let's Really Write Functions
  5. Section IV: Haskell and You
  6. Section V: Final Commentary
  7. Section VI: Extended Examples

Back to our main page.

Section V: Final Commentary

    1. Part I: Why is 'Referential Transparency' Worth Anything?
    2. Part II: Feedback and Notes
    3. Part III: Ranting

Part I: Why is 'Referential Transparency' Worth Anything?


Functional languages have often been accused of generating slow code. This is not the case with Haskell, except for the Array type. Switch to IOArrays if you need speed. Anyway.

Referential transparency gives Haskell programs two big boosts: better execution speed and memory use, and shorter, clearer code.

First, here are some reasons why Haskell provides better speed and memory use. No code runs faster than code evaluated during compilation, except code optimized away during compilation. The easiest way to evaluate during compilation is to find where a value is assigned to a variable, find where that variable is next read, and hardcode the value in that place. This avoids a memory write, possibly a read, and also potentially some memory allocation.

In both C and Haskell (for example), functions inherit the namespace of their scope. In C, this means that any variable in scope can be accessed at any time. For programming, this can be a good thing, or at least a time-saving feature. However, for compiling, this is a Bad Thing. Any of those accesses could be a write. Worse, you could have assigned a pointer the value of the memory space of a local variable, or be running a multi-threaded application. Or you could do pointer arithmetic and mess with any memory location at all. The behavior for handling this in C is compiler specific. C programmers are supposed to declare variables as volatile if they have this behavior. To correctly handle those cases, your compiler has to give up most of its hope of knowing how variables will be updated during execution.

Since it is possible to write code that uses those techniques to update a variable when unexpected, the C compiler has to actually read a variable every time it is referenced unless it can prove that no update will touch that variable between two points in a program. Every function call potentially alters every variable in scope, within some limits, such as the assumption that non-volatile, non-global variables are unaffected. All data passed to a function in another module through a pointer not declared const must be re-read after the function call. That happens often in large projects.

What about Haskell? Haskell is 'lazy and purely referentially transparent'. If you pass a value to a function, it will not be updated. That means that GHC can assume that. Specifically, there is never a need to copy a value when it is passed into a function, or ever re-read it. Also, a function which takes a list and returns part of that list hasn't copied anything. Since no update is possible, there is no harm in referencing the original. That's a big savings in memory, which translates into less memory reads and less page faults. Also, since the order of evaluation is unimportant to the programmer, the compiler can determine the fastest order and reshuffle the code and memory usage.

Other things make Haskell code fast. First, Haskell's laziness adds an extra kick to the speed, since some code is never executed. Second, there are only a few pieces to Haskell: functions, if-then-else, polymorphic types, exceptions, the IO code, and a garbage collector. Most library list functions are built on foldr or a variant, if they do any real work at all. This is a very small number of pieces, and that makes Haskell easy to compile and optimize. Finally, GHC does cross-module optimization, even for library functions, something impossible in C.

Contrast that to C/C++, with any number of libraries, most of them pre-compiled, and the only thing fast is memory manipulation. If you need memory manipulation, C is what you use. Python makes code-writing quick. LISP makes logical manipulation easy. Java makes code safely portable. Haskell is great (in my opinion) for the major target of industry: big projects with a lot of parts.

Second, there is the source code itself. If you don't care about the order of execution, you can avoid using syntax to specify it. The best way to discover this is to try it yourself. If you're having trouble breaking your imperative habits, read other Haskell code until you can understand why it is so short. The examples in the next section have been included for that purpose. The source code of the Prelude itself is also a good place to look.

 

Part II: Feedback and Notes


If you liked this tutorial, it came in handy, it helped you understand Haskell, or you think I talk to much, write me at [email protected].

Video games and other graphical applications written in Haskell can use HOpenGl and HOpenAL. To encourage this, Dave Morra has written a short tutorial on HOpenGL. This tutorial is meant to provide a basic framework for understanding Haskell. Hopefully, the HOpenGL tutorial does not assume much more knowledge of the programmer. If something too great is assumed by the HOpenGL tutorial, please let me know, because these are supposed to go together.

The HOpenGL tutorial:

Dave's HOpenGL tutorial

The Gentle Introduction Haskell, useful as a reference:

http://www.haskell.org/tutorial/

The Tour of the Haskell Syntax, also a good reference:

http://cs.anu.edu.au/Student/comp1100/haskell/tourofsyntax.html

Finally, GHC's Hierarchical libraries, an excellent reference once you know the basics:

http://www.haskell.org/ghc/docs/latest/html/libraries/index.html

You should probably read the Gentle Introduction to Haskell and the Tour of the Haskell Syntax thoroughly. Hopefully a lot more should make sense now. There are a lot of little picky things in every programming language, and Haskell is no exception. Particularly, there are a few cases where strictness is an issue, and it's important to know how Haskell's syntax is interpeted. Good luck. Let me know if there's another tutorial I need to write, or about any bug in the big examples.

 

Part III: Ranting


We've covered a lot of the basics of using Haskell, and in the next section has longer examples of Haskell code that go beyond toy programs. I'd like to take the time to talk about why I think Haskell is important, why I wrote this tutorial, and what learning Haskell was like for me. If you want to get to the examples immediately, skip to the bottom.

During this tutorial, I refer to Haskell's 'power'. I am cribbing from an old equation about programming languages:

flexibility * control of detail = a constant

That statement should really be written as follows:

flexibility * control of detail = power

Haskell has both more flexibility and more control than most languages. Nothing that I know of beats C's control, but Haskell has everything C does unless you need to control specific bytes in memory. So I call Haskell powerful, rather than just 'good'.

I wrote this tutorial because Haskell was very hard for me to learn, but now I love it. I haven't seen tutorials that addressed the difficulty that computer science students usually face with Haskell. I had to take two semesters of college that included Haskell before I really got a grip on it, and I only passed the first because one of my friends knew Haskell and helped me. I kept thinking that other people, maybe the haskell.org people, would write a tutorial aimed at C programmers, but it didn't happen. I understand why, now. As far as I can tell, Haskell is maintained and contributed to mostly by professors, and they have already learned LISP or other functional languages. Also, Haskell is not generally taught to whiny undergrads that throw a fit when faced with something this new and different. UT Austin is somewhat of an exception, and out of my classmates, I was the exception in actually liking it, let alone learning it.

So a relatively small number of people have been in my position, and it seems like none of them have spoken up. Well, here it is. "Haskell is hard!" "You can't write code the way I know how!" "My brain hurts!" "There aren't any good references!" That's what I said when I was in college. There were good references, but they didn't cover the real problem: coders know C. We, as students just getting through our second or third advanced computer science course, expect sequential behavior for code. It isn't good enough for a book to say that Haskell does not execute commands in sequence. We can tell that. What we don't (and what I didn't) understand is why on Earth you would bother coding in a language that wasn't sort of like C, and how you would even think about it. We could read all these tutorials and a few books about the syntax of Haskell and how Haskell is lazy and has pure referential transparency, but none of that helps. We don't know how to put functions together. We don't know what will be evaluated, and we don't know how the pieces of Haskell syntax fit together. Haskell is so different from the industry standard (C/C++) that we can't look at complicated Haskell code and understand it. And if we don't, Haskell will always be an academic language.

Of course, if a small horde of college kids start making little programs in Haskell, the language will survive, and maybe there will be a company that uses Haskell to make big games. I suppose there might be already. With OpenGL and OpenAL bindings, that's a guarantee. But only if students are using it, not just professors. When I started this tutorial, I wondered why the Haskell people didn't see this already. That's okay, they've been busy making a wonderful and extremely powerful language and libraries, and I thank them for it. Hopefully with good examples, applications, and games. Well, I'm done here. Thanks for reading all of this. Good luck with the big examples.

 

Continue to Section VI: Extended Examples