Real World Haskell: Chapters 1-2

Chapter 1 of Real World Haskell covers the most basic aspects of the language, such as common operators and operator precedence and gives some overviews of aspects of the language.

Chapter 2 discusses the type system. What stands out to me the most is that many of the claims that are made about the advantages of statically and strongly typed languages also applies to C++. The following excerpt illustrates my point:

Haskell's combination of strong and static typing makes it impossible for type errors to occur at runtime. While this means that we need to do a little more thinking “up front”, it also eliminates many simple errors that can otherwise be devilishly hard to find. It's a truism within the Haskell community that once code compiles, it's more likely to work correctly than in other languages. (Perhaps a more realistic way of putting this is that Haskell code often has fewer trivial bugs.)
...
In Haskell, the compiler proves the absence of type errors for us: a Haskell program that compiles will not suffer from type errors when it runs. Refactoring is usually a matter of moving code around, then recompiling and tidying up a few times until the compiler gives us the “all clear”.

Type Inference
However, Haskell says that it can infer types more often than not, making type declarations unnecessary. This reads as if every function call is equivalent to a C++ template.

Tuples
The Haskell notion of tuples seems to be very similar to that of boost::tuple, but cleaner as it is built into the language.

C++ example:

//Without the new "auto" type, coming in C++0x
boost::tuple<int, std::string, float> t = boost::make_tuple(5, "Hello", 2.3);
 
//The above is a bit cumbersome, and will be cleaned up with the auto tupe
auto t = boost::make_tuple(5, "Hello", 2.3);

Haskell, which is clearly cleaner:

t = (5, "Hello", 2.3);

Parametric Polymorphism
Haskell's parametric polymorphism seems to be strongly related to C++ function templates, but we'll hopefully get into that more later.

Comments

Those of us playing along at home

Some of the things I picked up reading Chapter 2:

Introduction of recursion

Since Haskell uses tail recursion, it's basically like writing a while loop performance-wise, which is a bit of a stretch for those of us in in the C++/C# camp. But instead of getting into the nuts and bolts of tail calls, the authors just get you used to thinking in recursive terms. If they keep up this style - ever erring on the pragmatic side rather than the theoretical - I think I'm going to enjoy reading this.

Laziness and Purity

Haskell's laziness and purity let it "cheat" in some places where C++ would have to tread more lightly. This example jumped out at me:

if (fun_1() || fun_2()) {
//do something
}

I'm not sure how good modern compilers are at hunting down side effects, but in a strict sense both functions would have to be run at the chance fun_2() causes some side effect to happen. Jason, do you know if this is the case in C++, or does C++ ignore the second call if the first one is true?

Haskell, by comparison, can rely on the pure aspect to know if fun_2() even needs to be run. It can also go another step further. If, for instance, the value returned by the "if" was never really used, the Haskell app could catch that at runtime as it lazily evaluated the value for later use. This type of optimization would likely be impractical (if not bordering on impossible) for languages perform immediate evaluation.

Another point for purity is that it makes functions easier to test (no doubt something they'll get into more later in the book). Since the function has no side effects, you can just pull it out and test it separately without needing a harness. Something that could definitely carry over into working with other languages like C++ would be to denote when things are pure and which aren't specifically for that purpose.

C++ operator overloading in the context of Haskell

Jon says:

if (fun_1() || fun_2()) {
//do something
}

I'm not sure how good modern compilers are at hunting down side effects, but in a strict sense both functions would have to be run at the chance fun_2() causes some side effect to happen. Jason, do you know if this is the case in C++, or does C++ ignore the second call if the first one is true?

Modern C++ or not, short circuiting is in affect. If the first one is true, the second one is never called. This behavior is often used to test pointers:

if (ptr && ptr->doSomething())
{
}

There is no reason why the above should execute any differently than:

if (getPtr() && getPtr()->doSomething())
{
}

The interesting point to note is that short circuiting does not work with a user defined && or || operator, which is a point that the Haskell book makes. C compilers must treat && and || differently than normal functions for the built in versions, otherwise short circuiting would not work. However, the lazy evaluation of Haskell allows && and || to always do what you would expect, built in or not.

Here's some example code that illustrates the point. The built in (bool) versions of && and || short circuit while the user defined ones do not, regardless of the source of the data.

#include <iostream>
 
bool getbool(bool b)
{
  std::cout << " getbool called " << std::endl;
  return b;
}
 
struct Boolean
{
  private:
    bool value;
 
  public:
    Boolean(bool t_value)
      : value(t_value)
    {
    }
 
    bool operator&&(const Boolean &right) const
    {
      return this->value && right.value;
    }
 
    bool operator||(const Boolean &right) const
    {
      return this->value || right.value;
    }
};
 
Boolean getBoolean(bool b)
{
  std::cout << " getBoolean called " << std::endl;
  return Boolean(b);
}
 
int main()
{
  std::cout << " Testing getbool || expecting 1 output: " << std::endl;
  if (getbool(true) || getbool(false))
  {
  }
 
  std::cout << " Testing getbool || expecting 2 outputs: " << std::endl;
  if (getbool(false) || getbool(true))
  {
  }
 
  std::cout << " Testing getbool && expecting 1 outputs: " << std::endl;
  if (getbool(false) && getbool(true))
  {
  }
 
  std::cout << " Testing getBoolean || expecting 2 outputs: (no shortcircuit) " << std::endl;
  if (getBoolean(true) || getBoolean(false))
  {
  }
 
  std::cout << " Testing getBoolean && expecting 2 outputs: (no shortcircuit)" << std::endl;
  if (getBoolean(false) && getBoolean(true))
  {
  }
}

Addendum to C++ and Operator Overloads

The above example illustrates that && and || basically should never be overloaded. It is impossible for the author of the overload to preserve the semantics of the operator. A better choice is to allow an implicit conversion to bool, if that is what you need.

Other syntax things

Something else I noticed but didn't think to mention, not only are tuples more terse in Haskell (and I'd say most "modern" languages), but so are arrays:

x = [1, 2, 3]

It's got everything it needs to figure out what the variable type is and to initialize the array. Definitely handy. C/C++ have their own too:

int x[] = {1, 2, 3};

...and I'm sure that the automatic type inference with C++0x will improve that further.

I guess this is on my mind, since I just added the Haskell syntax to Minnow as well, so you can now do something like this as of the latest SVN:

x : var = [1, 2, 3]

Now umpteen-1 more functions/operators to go :)