Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Bob Hayes and Michael Hayes and Chris Rodden and Eric Stoeckel Jr
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Bob Hayes Michael Hayes Chris Rodden Eric Stoeckel Jr

Seven Languages In Seven Weeks: Haskell - Day 1

By
Published in Comments (12)

I can't believe it! I am finally on Haskell - the last language from my Seven Langauges in Seven Weeks book by Bruce Tate. It's been quite a journey. Haskell is a pure functional language. And, as with the other functional languages in this book, I have found the syntax to be on of the most challenging aspects.

I love intermediary variables and values!

There, I said it. I love to take a problem and break it up into steps that are bite-sized and can be profusely commented. As such, I have a hard time falling in love with any language that doesn't have a natural top-down affordance for local variables and intermediary values. What I'm finding, and I'm sure that much of this is just syntactic ignorance, is that functional languages are not all that variable-friendly. Even when a language like Clojure or Haskell allows for intermediary variables, they seem to be extremely ceremonious about it.

As an extremely trite example, image that I wanted to build a multi-step "doubleIt()" function in Javascript. I might do something like this:

function doubleIt( n ){
	// Double the value and store locally.
	var result = (n * 2);

	// Return local result.
	return( result );
}

I know this is a silly example; but, as you can see, it's extremely easy to read and understand. You can read it like a book - from the top, down.

Now, let's do the same thing in Haskell. And, of course, I have to prefix this once again by saying that this is the first time that I have ever used Haskell; so, I am sure there is a good deal that I simply don't know. But, from what I have been Googling, it looks like this multi-step approach could be accomplished as such:

doubleIt n =
	-- Return result.
	result

	-- Calculate result as the input multiplied by 2.
	where
		result = n * 2

In this approach, the algorithm is almost backwards. You're defining the function as returning the result before the result is even calculated.

The Let clause, on the other hand, allows the algorithm to be put in a more top-down manner:

doubleIt n =
	-- Calculate result as the input multiplied by 2.
	let
		result = n * 2

	-- ... and use that result in the following expression.
	in
		result

As you can see, this is getting closer to our Javascript version; but, it is still very ceremonious. You still have the seemingly superfluous formality of explicitly scoping the let-binding to the code following the "in" clause.

Now, don't get me wrong - I know that the language must have been constructed a certain way for a reason. I understand that the Where clause works across multiple guard statements and that the Let clause works only within the confines of a single guard statement; and, I'm sure that this approach has some very powerful consequences.

All, I'm saying is that by making intermediary variables so ceremonious, it makes it much harder for someone like me to wrap my head around the language.

Ok, enough pointless ranting, let's get to the homework.

HW1: How many different ways can you find to write allEven.

-- How many differnt ways can you find to write allEven?


-- Define our module.
module Main where

	-- The one in the book.
	allEven :: [Integer] -> [Integer]

	-- Define the base case.
	allEven [] = []

	-- With a populated list, deconstruct the list into its head
	-- and tail portions.
	allEven (head:tail) =

		-- Check to see if the first value is even.
		if (even head) then

			-- The first value is even, so add it to the allEven()
			-- result of the rest of the list.
			head:allEven( tail )

		else

			-- The first value is NOT even, so return the result
			-- of the tail only.
			allEven( tail )


	-- ------------------------------------------------------ --
	-- ------------------------------------------------------ --


	-- Using a list comprehension with a guard statement.
	allEven2 :: [Integer] -> [Integer]

	-- Map the given list onto the result list with a guard statement
	-- that requires the list item to be even.
	allEven2 numbers = [n | n <- numbers, (even n)]


	-- ------------------------------------------------------ --
	-- ------------------------------------------------------ --


	-- Using a folding-right approach.
	allEven3 :: [Integer] -> [Integer]

	-- Use the foldr. Since we want to return evens in the same order
	-- in which they are listed, we actually want to collect them in
	-- reverse order so we can build the list using the current index
	-- value as the new "head".
	allEven3 numbers =
		foldr
			(\n evens -> if (even n) then (n:evens) else evens)
			[]
			numbers


	-- ------------------------------------------------------ --
	-- ------------------------------------------------------ --


	-- Using a filter approach.
	allEven4 :: [Integer] -> [Integer]

	-- Our filter function will be the native "even" method.
	allEven4 numbers = filter even numbers

I'm kind of excited that it's my first day of Haskell and I was able to find a few different approaches to this problem. I have to say, it's kind of cool how many ways there are to work with lists. This is actually true of many of the languages that we have seen in this book. Between mapping, filtering, and folding, it really shows you how awesome lists can be.

Anyway, when I load in the above module and use it, I get the following console output:

*Main> :load hw1.hs
[1 of 1] Compiling Main ( hw1.hs, interpreted )
Ok, modules loaded: Main.

*Main> allEven( [ 1, 2, 3, 4, 5 ] )
[2,4]
*Main> allEven2( [ 1, 2, 3, 4, 5 ] )
[2,4]
*Main> allEven3( [ 1, 2, 3, 4, 5 ] )
[2,4]
*Main> allEven4( [ 1, 2, 3, 4, 5 ] )
[2,4]

As you can see, each version of the allEven() function returns a list containing 2 and 4.

HW2: Write a function that takes a list and returns the same list in reverse.

Like the first problem, I wanted to see if I could come up with a few different ways to approach this problem, the first of which simply leveraged the native reverse() function:

-- Write a function that takes a list and returns the same list
-- in reverse.


-- Define our module.
module Main where


	-- Use the native reverse function.
	reverseList :: [a] -> [a]

	-- Simply pass this off to the native reverse function.
	reverseList values = reverse values


	-- ------------------------------------------------------ --
	-- ------------------------------------------------------ --


	-- Use a folding approach.
	reverseList2 :: [a] -> [a]

	-- We are going to use foldl to add the current value to the
	-- head of the resultant list. This will move the head to the
	-- body of the resultant list.
	reverseList2 values =
		foldl
			(\newList value -> value:newList)
			[]
			values


	-- ------------------------------------------------------ --
	-- ------------------------------------------------------ --


	-- Use a more recursive approach
	reverseList3 :: [a] -> [a]

	-- Define the base case for empty lists.
	reverseList3 [] = []

	-- Break the list apart and append the head to the reversed
	-- tail portion.
	reverseList3 (head:tail) = reverseList3( tail ) ++ [head]

When I load in the above module and use it, I get the following console output:

*Main> :load hw2.hs
[1 of 1] Compiling Main ( hw2.hs, interpreted )
Ok, modules loaded: Main.

*Main> reverseList( [ "a", "b", "c", "d" ] )
["d","c","b","a"]
*Main> reverseList2( [ "a", "b", "c", "d" ] )
["d","c","b","a"]
*Main> reverseList3( [ "a", "b", "c", "d" ] )
["d","c","b","a"]

In the first homework problem, as well as this one, you might notice a weird looking syntax:

(\newList value -> value:newList)

This is actually a lambda function (a.k.a. anonymous function, a.k.a. headless function). It's basically a way to define inline functions that get passed as arguments to other, higher-order functions, like filter, map, and foldr.

As confusing as this might look, I found defining method signatures in Haskell to be quite complicated in general! From what I read, Haskell seems to make extensive use of the concept known as "currying". When you curry a function, you build another function in which some of the original parameters are encapsulated within the scope-binding of the new function. This allows you to invoke the subsequent function with fewer parameters.

From what I read, which I hardly understood, it looks like Haskell implicitly curries a lot of the functions. So, when you invoke a function with 2 arguments, you aren't actually passing in two arguments; rather, Haskell is currying the function with the first argument and then passing the second argument to the resultant function.

I know this sounds crazy, so actually, just ignore it. When I read about it, I didn't much understand what they were saying. And, more likely that not, I am totally misrepresenting the language.

HW3: Write a function that builds two-tuples with all possible combinations of two of the colors black, white, blue, yellow, and red. Note that you should include only one of (black, blue) and (blue, black).

-- Write a function that builds two-tuples with all possible
-- combinations of two of the colors black, white, blue, yellow,
-- and red. Note that you should include only one of (black, blue)
-- and (blue, black).


-- Define our module.
module Main where


	-- Define our function.
	getColors :: [String] -> [(String, String)]

	-- Use a list comprehension to return the combinations.
	-- The guard statement (a < b) will prevent reverse sets
	-- of the same colors.
	getColors colors = [(a, b) | a <- colors, b <- colors, (a < b)]

When I load in the above module and use it, I get the following console output:

[1 of 1] Compiling Main ( hw3.hs, interpreted )
Ok, modules loaded: Main.

*Main> getColors( [ "black", "white", "blue", "yellow", "red" ] )
[("black","white"), ("black","blue"), ("black","yellow"), ("black","red"), ("white","yellow"), ("blue","white"), ("blue","yellow"), ("blue","red"), ("red","white"), ("red","yellow")]

This approach uses a list comprehension that builds and then filters the product of two lists. List comprehensions have been available in a few of the languages in this book and they are simply awesome! Although the syntax is rather terse, the power trade-off feels totally worth it. In fact, after this problem last night, I was inspired to express my enjoyment:

Oh Katie! You are adorable.

HW4: Write a list comprehension to build a childhood multiplication table. The table would be a list of three-tuples where the first two are integers from 1-12 and the third is the product of the first two.

-- Write a list comprehension to build a childhood multiplication
-- table. The table would be a list of three-tubles where the first
-- two integers are from 1-12 and the third is the product of the
-- first two.


-- Define our mdule.
module Main where


	-- I return the multiplication table going up to the given value.
	getTable :: Integer -> [(Integer, Integer, Integer)]

	getTable maxValue =
		[(x, y, x * y) | x <- [1..maxValue], y <- [1..maxValue]]

I built the function getTable() to take one argument. To be completely honest, I did this simply because I couldn't figure out how to define a function with zero arguments. One problem that I kept running into last night was the outlining of method signatures. I found it incredibly hard to find a good online tutorial on how to define Haskell functions. There seems to be a bit of an art form to it (harkening back to my note on signatures above).

That said, by defining the max-value as a function parameter, it allowed me to explore the use of Ranges - another badass data type that many of the languages in this book have had. A range, using the ".." notation, is a way to build up a sequence by defining its end points.

When I load in the above module and use it, I get the following console output:

[1 of 1] Compiling Main ( hw4.hs, interpreted )
Ok, modules loaded: Main.

*Main> getTable( 12 )
[(1,1,1), (1,2,2), (1,3,3), (1,4,4), (1,5,5), (1,6,6), (1,7,7), (1,8,8), (1,9,9), (1,10,10), (1,11,11), (1,12,12), (2,1,2), (2,2,4), (2,3,6), (2,4,8), (2,5,10), (2,6,12), (2,7,14), (2,8,16), (2,9,18), (2,10,20), (2,11,22), (2,12,24), (3,1,3), (3,2,6), (3,3,9), (3,4,12), (3,5,15), (3,6,18), (3,7,21), (3,8,24), (3,9,27), (3,10,30), (3,11,33), (3,12,36), (4,1,4), (4,2,8), (4,3,12), (4,4,16), (4,5,20), (4,6,24), (4,7,28), (4,8,32), (4,9,36), (4,10,40), (4,11,44), (4,12,48), (5,1,5), (5,2,10), (5,3,15), (5,4,20), (5,5,25), (5,6,30), (5,7,35), (5,8,40), (5,9,45), (5,10,50), (5,11,55), (5,12,60), (6,1,6), (6,2,12), (6,3,18), (6,4,24), (6,5,30), (6,6,36), (6,7,42), (6,8,48), (6,9,54), (6,10,60), (6,11,66), (6,12,72), (7,1,7), (7,2,14), (7,3,21), (7,4,28), (7,5,35), (7,6,42), (7,7,49), (7,8,56), (7,9,63), (7,10,70), (7,11,77), (7,12,84), (8,1,8), (8,2,16), (8,3,24), (8,4,32), (8,5,40), (8,6,48), (8,7,56), (8,8,64), (8,9,72), (8,10,80), (8,11,88), (8,12,96), (9,1,9), (9,2,18), (9,3,27), (9,4,36), (9,5,45), (9,6,54), (9,7,63), (9,8,72), (9,9,81), (9,10,90), (9,11,99), (9,12,108), (10,1,10), (10,2,20), (10,3,30), (10,4,40), (10,5,50), (10,6,60), (10,7,70), (10,8,80), (10,9,90), (10,10,100), (10,11,110), (10,12,120), (11,1,11), (11,2,22), (11,3,33), (11,4,44), (11,5,55), (11,6,66), (11,7,77), (11,8,88), (11,9,99), (11,10,110), (11,11,121), (11,12,132), (12,1,12), (12,2,24), (12,3,36), (12,4,48), (12,5,60), (12,6,72), (12,7,84), (12,8,96), (12,9,108), (12,10,120), (12,11,132), (12,12,144)]

HW5: Solve the map-coloring problem (on page 87) in Haskell.

I couldn't solve this one. I put about an hour of time into it; but, nothing came of it. I thought I was on the right track, but didn't have enough foundational knowledge of the language to get through it.

There's definitely some cool stuff in Haskell, but I am finding the syntax to be very trying. This holds especially true for the function signatures. I am sure that this becomes second nature; but, for this homework, I definitely spent way too much time trying to figure out why some value wouldn't map properly to some signature.

Want to use code from this post? Check out the license.

Reader Comments

19 Comments

So how would you build a data driven cross browser dynamic grid with inline editing using only a few lines of code?
I bet if a Haskell boffin spent as much time and effort with ColdFusion it would blow their minds how much they could actually accomplish ;)

59 Comments

Darren,

I love ColdFusion as much as the next guy. I think it is *the* best language for web development, but I bet for certain kinds of problems Haskell would be much easier and more concise.

In fact, ColdFusion could really benefit from a few of the fundamental functions popular in Functional programming. A friend of mine took me through fold (foldl and foldr) once and it really blew my mind.

I actually wrote a fold in ColdFusion once (probably not the best implementation, of course). It is really nice.

I bet Sean Corfield will cover that stuff nicely in his presentation on Functional Programming at cf.Objective(). I want to see that presentation so badly I *may* violate my standing rule of attending at least one Charlie Arehart presentation per conference in order to see it.

15,848 Comments

@Steve, @Darren,

You guys raise a great point. When I started looking into other programming languages, I always found myself asking the question:

How can this language do everything that ColdFusion does?

While each language seemed to have fascinating features, I always came back to the awesome robustness of the ColdFusion language.

Then, it hit me - these languages aren't hot-swappable in that way. They are not meant to be complete substitutions for each other. Each language has strengths that suit it for different purposes. Concurrency, distribution, speed, reliability, meta-programming, etc..

While I understand that at the philosophical level, it still feels odd at the practical level; but, it shouldn't. Think about building a web site - we use ColdFusion, Java, Javascript, HTML, CSS. These are all languages specialized for certain purposes that all work together.

Even many of the languages in this book (Seven Languages) exist on top of the JVM (Java Virtual Machine) and can inter-operate with Java and all of the core Java classes.

Understanding this, though, and getting to a point where you can architect a solution that actually uses the best tool for each job... yikes, I have no idea how that even happens :)

2 Comments

If you don't want any parameters, you can write your

getTable

as

getTable :: [(Integer, Integer, Integer)]
getTable = [(x, y, x * y) | x <- [1..maxValue], y <- [1..maxValue]]
			where maxValue = 12

Also, you can skip the type signature, load your file in ghci, and then do

:type yourFunction

and it will show you the inferred type.
I found that useful when starting out.

I also noticed you put parentheses around function parameters, these are not needed in any of your examples,

foo 1

calls

foo

with the parameter

1
19 Comments

@Steve, @Ben

I agree, horses for courses and all that. And also I appreciate the esoteric forms of different languages, their expressive power. Like listening to some forms of jazz, when you're outside the comfortable norms of what popular music has trained your ears to accept, it can be fascinating, exciting and aggravating at the same time.

But I also appreciate the simple beauty of just getting things done, it might not be pretty or clever but if your code or website can make someone happy or just help them then it's arguably of greater value to the world...

And surely if these languages all run on the JVM and can interface with Java then they can interface with ColdFusion, no? Yay for ColdFusion again! There's some homework for you Ben.

59 Comments

Darren,

Absolutely! ColdFusion is great for general-purpose web development.

As Ben alluded, the hard part is figuring out when and how to take advantage of other languages (as well as what we can learn from them and apply to ColdFusion or other languages).

I certainly haven't solved the when, but for the how I suspect Barney Boisvert's CFGroovy 2 may hold the answer in many cases.

http://www.barneyb.com/barneyblog/projects/cfgroovy2/

This should allow you to use languages included in the JVM (rather than ones, like ColdFusion, that run on the JVM but are not included within it).

If you haven't already, I would recommend checking it out. He actually presented on it to our local CFUG a while back - really impressive stuff!

http://adobechats.adobe.acrobat.com/p20888993/

15,848 Comments

@Nlogax,

Ahh, thanks! I see that you simply provide the resultant type, but no argument types. Good tip. I also have been trying to not use as many parenthesis, but old habits die hard. In today's homework (day 2), I actually found that using the parenthesis in some cases was more confusing. I guess I am just getting used to the concept.

@Darren, @Steve,

Yo, the CFGroovy project is just badass! I think it has to do with scripts that implement a certain interface. Not sure how compatible it makes CF with arbitrary JVM-based languages. But, I am outside my understanding at that point :)

59 Comments

Ben,

I think Barney said you could use it to implement any language that is included in the JVM. I haven't tried it yet (really should make it a point to do that), though, so I am not sure I understood everything correctly.

1 Comments

Have you had any luck with the state coloring problem? I'm working through that now and haven't gotten very far.

1 Comments

State coloring can be done in quite similar way as it is done with Prolog - just by describing conditions, in this case as conditions of list comprehension. Like this:
states colors = [(tennessee, mississippi, alabama, georgia, florida) |
tennessee <- colors,
mississippi <- colors,
alabama <- colors,
georgia <- colors,
florida <- colors,
tennessee /= mississippi,
tennessee /= alabama,
tennessee /= georgia,
mississippi /= alabama,
alabama /= georgia,
florida /= alabama,
florida /= georgia]

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel