I've started reading Real World Haskell and it is a great way to learn functional programming. Coming from XSLT (a rather functional language) I had a feeling of preparedness. Fortunately, Real World Haskell has revealed a huge gap in my experience with functional languages as well as presented me with an opportunity to make major strides towards growing as a programmer.
Before going on though, I'd like to point out that Real World Haskell (RWH) is not a book for beginning programmers. There is a constant comparison made to traditional languages such as Java or Python that almost require the reader to have programming experience. In addition to the programming experience, functional programming has a tendency to come across rather "mathy" compared to other languages. This most likely says more about yours truly than RWH, but for those programmers who manage to scrape by (like myself) without a strong background in math, it might take a little extra effort.
First off, Haskell as a language becomes very concise. This is especially true compared to XSLT where the XML can get in the way of seeing an algorithm's implementation. RWH does a very good job revealing immediately how functional patterns help to keep code concise and almost force elegance. Function composition, for example, provides a clear way to manage complexity. For example, in Python, you might see something like this:
from someapp import somequeue
def __init__(self, some_data_dict):
self.data = some_data_dict
for k, v in self.data.items():
def save(self, key, value):
f = open(key, 'w+')
def send(self, key, value):
This is a pretty basic way to build and manage functionality. Haskell would be no different:
-- removing actual definitions for brevity
saveAndSend = send . save . clean
Assuming I've got the code close to correct, you read the collection of functions from right to left. The
clean function is simply a placeholder to do any data mangling similar that you would do in a constructor. The
send methods then do similar actions as defined in the Python example. The function signatures would then accept list of "Foo" data types.
-- the function signature
clean :: [Foo] -> [Foo]
While it may not be entirely clear, there is an elegance to this kind of pattern. The elegance partly is through the use of functions. The
saveAndSend function is a new function created by combining other functions. In Python, you might see things like:
x = a(b(c(some_dict)))
The difference between the Python and Haskell composition is that Haskell acts lazily. Each function in Haskell only takes one argument, which allows the function composition to effectively pass the reference of each function as an argument. Python could do a similar operation by creating a combination function.
def combo(funcs, arg):
f = funcs.pop()
if not funcs:
return f(combo(funcs, arg))
funcs = [
lambda x: x + 2,
lambda x: x * 2,
lambda x: x - 1
print combo(funcs, 1) # returns 5 == (1 + 2) * 2 - 1
The issue here is that while you can combine these function, the result must be defined in terms of the argument. You could not simply save a declaration such as
new_func = combo([a, b, c]) where
[a, b, c] is a list of functions and expect to call
new_func later. In Haskell, because the functions are evaluated lazily, you can easily combine almost any function with another and things should work assuming the signatures are compatible. The elegance of function composition becomes syntactically concise in Haskell with the use of the combination function, denoted by a period (.).
There is a separation between functions and data that may feel wrong, but in practice it is actually very natural. While Haskell doesn't have traditional objects, it effectively has structs and helpers to make common idioms, such as getters, effortless. Also, Haskell's type system becomes a valuable constraint in managing complexity in that it forces you to consider types immediately.
This idea of constraints is a powerful concept that is pervasive. These constraints are what helps to realize the power of functional programming as well as expand your horizons as a developer. The difference between the constraints found in functional programming and requirements is that the functional constraints help improve the software design. Haskell's type system, for example, clearly helps to make code safer and more maintainable. In functional languages, variables traditionally can never be changed once declared. On the other hand, the type system of Java or C# forces you to declare types in order to appease the compiler, yet the mutable nature of the vast majority of the types prevents seeing a clear progression of data. RWH makes a very convincing argument regarding the positive and negative trade-offs within the type system. Both the explanations and balance in the argument is refreshing and factual.
Other reviews of RWH have made the comparison of learning Haskell to learning Lisp. For many people, Lisp has historically been the language to help people expand beyond the traditional programmer thought process. RWH is able to provide a very similar experience along side a more accessible environment and active community. In addition to expanding your programming perspective, RWH gives you a definite direction towards writing real applications with Haskell. RWH provides obvious instructions on how to get started with Haskell, organize code and constantly provides direction on how to write good Haskell code.
Again, RWH is not for the faint of heart. It is a pretty serious programming book that delves into rather deep territory very quickly. As you can see from my lack of in depth Haskell examples, I'm far from having a complete grasp on the language. Fortunately, RWH's exercises have been very helpful getting my hands dirty. So far, the journey I've taken through the book has resulted in understanding a great language as well as gaining insight found through functional programming. If you are serious about programming, this is a great book to read.