?

Log in

No account? Create an account

Fri, Oct. 16th, 2009, 10:26 am

This post unintentionally demonstrates that functional-style Python is ugly and bad.

Let us start at the top. At the beginning, it has some sample code which defines a multiple() function, which could be trivially inlined, resulting in code which looks like this (I'm doing all examples in Python 3):
print(sum(x for x in range(1, 1000+1) if x % 3 == 0 or x % 5 == 0))

There's no reason whatsoever to expand that out. This should be an early indication that maybe the code samples here aren't the greatest.

Moving on, there's getting the sum of all fibonacci numbers less than 4 million. This is done in the example using itertools and yield, resulting in a fair amount of ugly code. Here's how a sane person does it:
def fibsum():
	a, b, c = 0, 1, 1
	total = 0
	while c < 4000000:
		total += c
		a, b, c = b, c, b + c
	return total

Now that's much more readable, flexible, and maintainable.

Finally, there's the problem of finding the largest palindrome which is the product of two three digit numbers. Here's my solution, which contains less code, is much more readable, and oh yeah, I threw in an optimization to make it return almost instantly instead of having to crunch for a second:
def bigpalindrome():
	best = 0
	for i in range(999, 0, -1):
		if i * 999 < best:
			return best
		for j in range(999, i-1, -1):
			x = list(str(i*j))
			x.reverse()
			if int(''.join(x)) == i*j:
				best = i*j

Much better. I think these examples do a good job of exploding the idea that the functional style of programming is clearly better and the appropriate first thing to teach people. Obviously some people are being driven to write horribly contorted and ugly code because they started in a functional language when they switch a more, ahem, mainstream one.

Sat, Oct. 17th, 2009 08:10 pm (UTC)
stigant

It's also significantly more modular and hence flexible and maintainable as well. I might note, for example, that your code has an error in it - it adds up all the fib numbers instead of the even ones. This is simply fixed in the Clojure code by adding a (filter even ... bit. In your code, it will be harder to notice and fix because while I'm looking at the low level details of your programming-language-dependent implementation rather than parsing natural English sounding instructions, I'm likely to miss the fact that you didn't include anything to filter out odd numbers.

Your code is less extensible. Consider the following variations on the problem that are suggested by the problem description:
- Add the first 100 fib numbers
- Multiply the first 100 fib numbers
- Add the first 100 prime fib numbers
- Add the second 1000 prime fib numbers
All are easily, and naturally, implemented leveraging and recombining the code for the fib sequence and the routines that filter, add, multiply etc. Since your interleaves the generation of the fib sequence with the code that uses it (ie the adding part), all of the code will need to be duplicated for the new programs.

Had you taken a more functional approach to generating the fib sequence (for example by defining a fib function which maps integers to integers, or by implementing a list/stream), your code would be much better. But these techniques are inherently more difficult in imperative languages. For example, lazy evaluation (which you get for free in Haskell, and with a keyword in Clojure) would require several more lines of code which are difficult to understand to the beginning programmer. A general purpose lazy implementation would be impossible in any imperative language since evaluation order becomes important when you allow state to mutate. Furthermore, I expect you will have a hard time making everything thread safe in such an implementation.

Sat, Oct. 17th, 2009 08:25 pm (UTC)
bramcohen

The lack of filtering for even number is due to me having missed that in the problem statement. Adding an if for it is completely trivial.

I don't know what you're babbling about thread safety. None of these examples have anything to do with thread safety, and trying to make thread safety be embedded into code itself rather than using semaphores is just stupid.

Sat, Oct. 17th, 2009 08:41 pm (UTC)
stigant

>> Adding an if for it is completely trivial.
Sure, but missing the point. ie it makes your code less readable, maintainable, and flexible.

>>None of these examples have anything to do with thread safety,
I was providing that as an example of a way to extend the program's usefulness (ie flexibility). You will have a hard time implementing the fib seq as a list in a way that is thread safe because it will require state changes which will have to be surrounded by semaphores. With the functional/lazy approach you get thread safety for free, no extra effort - no need to embed it in the code. The alternative is to embed the fib seq generation code in your processing code (something I consider to be just as stupid as embedding thread safety) as you have done. Unfortunately, that requires that part of the program to be duplicated for every fib-seq consuming application.