The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Functional Programming - Where's The Beef?

Hey, all -

I'm working my way through a Ruby guide I just bought last week in hopes of keeping my skills up to date. The language seems fine so far, although you never REALLY know until you work your way through a serious bit of code, but I'm not really seeing the benefit to something my book claims will revolutionize the way I code.

Ruby's functional additions really don't seem, in my mind, to buy me a whole lot. Being able to pass code to a function as an argument seems neat, in a limited context, but hardly revolutionary. The standard example I've heard - passing code to a function that maps over an array and performs a computation on each element - doesn't seem like it buys me anything over an Iterator with some function calls. As for returning functions from functions, that sounds like a dangerous practice - run-time code generation seems to be something that shouldn't be done lightly at all.

I guess my question is, then - what does functional programming allow me to do that I couldn't already do as a competent OO programmer?

Thanks!
BrotherBeal Send private email
Wednesday, October 31, 2007
 
 
First of all, Ruby is not a good representative of Functional Programming as seen in other languages that really target it all the way. Just so this doesn't derail into a thread of war on the misconceptions.

Ruby's features have come from several languages including Perl (Regular Expressions literals and shortcuts like $1), LISP (I've seen some deference to Ruby from that camp), Smalltalk...

Just to put things in context, I don't have first hand experience with either Smalltalk or LISP so can't speak from experience, but one of the Ruby's main features, Closures, and its close/quite similar one Blocks, seems to have come from those languages. Of importance is that Closures can be found in JavaScript as well, so it's a pretty mainstream concept which is still being assimilated by users and other languages. But JavaScript doesn't seem to support Blocks as effectively as Ruby, so there are differences. When one becomes an API author, such differences become rather annoying because one could wish JavaScript had full support to it just like Ruby does. :-)

Alright, that's just for setting the context. I have currently about 52 thousand lines of Ruby code that I care about personally, but the it's all distributed in hundreds of files, and tens of modules. I've been lucky to be able to improve the layout of some of this code and it's all pretty straightforward to add more code, but just Joel Spolsky says, it's important to first fix the bugs, before adding new features. I get more freedom once I fix things now and I have been maintaining all of this with mostly joy, because the development cycle is fast, as there isn't much between editing, compiling and deploying, aside from having to support Windows. :-)

Where was I?
Joao Pedrosa Send private email
Wednesday, October 31, 2007
 
 
To get a senseof what Functional programming is all about, Do watch the MIT SICP video lecture series.

It may seem back to basics for the first couple of lectures, but you will be blown away by the time you see them all.

Vivek
Vivek Send private email
Wednesday, October 31, 2007
 
 
Alright, here's a sample of GUI programs I can create with Ruby. The context is set by default so this code is fully runnable for me, no need to require anything anymore, just have to name the file with the ".gui" extension:

w.title = 'Hello World Window'
b_close = w.button('Say bye bye and close'){
  w.msg 'See you soon!' # dialog msg
  if w.confirm('Are you sure you want to close this window?')
    w.close
  end
}
w.pack b_close


The way I reached this point has been by refinement and by using the Ruby features as I've learned about them. At first I coded in Ruby like it was Java or Delphi, but it was much more painful then and that old code just plain sucks now. Some folks like to say that Ruby allows for creation of Domain Specific Languages as I've shown in this example. Rails is supposedly (I don't use it and have my own alternative to it) to be a DSL for Web programming and it does have a lot of Web mojo, including supporting the Prototype JavaScript library out of the box.

That is, it's probably a mistake to consider Ruby or Rails alone, as they are part of bigger context. My GUI program uses GTK+ for example. Rails uses Apache, MySQL, Mongrel, ImageMagick, Prototype, Memcached (easy caching of Web data)... Just like they say, a bigger program is created from a smaller program. It all has to start somewhere, and by incremental improvements, by reusing what's available, and more important, by being able to redefine the problems using the Ruby's dynamic features in DSLs, it all comes to a happy ending.

When Ruby started, over 10 years ago, it was much slower at the time, not only because it hadn't been as optimized as it has been (in December we should get an even speedier Ruby in a new release (1.9.1) with a Virtual Machine), but because the computers at the time were much slower and had much less memory. Nowadays Ruby thrives in what is current hardware, making the good aspects of Ruby come forward, like reliability, stability (this new version 1.9.1 is going to change things a little, but it's all for the better with a great deal of backwards-compatibility).

In the micro level, in the way that the features of the Ruby language work, it's all important, but they are too many to say something like "Ruby rocks because of this one feature only". The focus of Ruby has been on developers without much of corporation goals of easy replacement of developers or empowering of weak developers. While Ruby can be used by weak developers as we all have to start somewhere, there's much under the hood to keep anyone busy for a long time building stuff.

There are hundreds of hidden gems in Ruby itself that make up for its shortcomings. I like clean and concise code. I like using it to redefine the problems in DSLs. I like combining Ruby with JavaScript Object Notation (JSON) or just JavaScript on the Web. I like that the standard Ruby is rock solid so it's hard to blame it for bugs.

Sorry for not delving into more of its micro features. Do you have any in particular that you would like to discuss?
Joao Pedrosa Send private email
Wednesday, October 31, 2007
 
 
Vivek - Thanks for the tip on SICP - I'll check them out when I can find twenty hours or so (tried once at work, but they're a bit denser than Java Posse).

Joao - I'll buy your point that Ruby is not a good representation of functional programming, as it does strike me as a jack-of-all-trades language rather than something with a strong central design (the principle of least surprise doesn't count - English doesn't surprise me, for example, but that can hardly be considered to be a product of design).

All -

I think I may have confounded the issue by mentioning Ruby. The crux of my question is - what does functional programming allow me to do that I could not do in a non-functional context? If it just boils down to syntactic sugar, then that's fine and I can approach it that way. However, I'd like to think that it is in fact revolutionary and not simply evolutionary, if only for the fact that so many obviously smart people (Steve Yegge, Paul Graham, etc) talk so highly about it. So I guess I'd like to hijack my own thread and bring it away from Ruby and into a more general context, if that's allowed :)
BrotherBeal Send private email
Wednesday, October 31, 2007
 
 
"The crux of my question is - what does functional programming allow me to do that I could not do in a non-functional context?"

Have you read the Wikipedia entry on the subject?
John Topley Send private email
Wednesday, October 31, 2007
 
 
My "cs 101" course was taught in Haskell.  Boring text, half-dead instructor, kinda bleh overall experience.  However, I'm extremely grateful for having been exposed to some of those concepts (like list comprehensions), because they provide a useful set of tools for thinking about problems now.  FP isn't the alpha nor omega of computational patterns, just like OO isn't, but it's certainly handy (even if you don't use it regularly, it helps keep your mind working).

JS himself wrote an article (in JS, how recursive...) about why functional programming language features can be fun/useful: http://www.joelonsoftware.com/items/2006/08/01.html
son of anon
Wednesday, October 31, 2007
 
 
Look, Turing-equivalence means that clearly no programming paradigm can allow you to do anything that you can't do in another.  What does OOP allow you to do that you can't do in plain assembler?  Nothing; fundamentally it's all just a heck of a lot of syntactic sugar.

So sometimes FP is a more convenient sugar than OOP.  Other times it's not.  It's worth learning because it's always worth having more tools in your toolkit.  OOP might solve all your problems today, but tomorrow you might run into something that would be simpler and more maintainable if you approached it from an FP angle.

Oh, and returning functions from functions isn't runtime code generation, and isn't dangerous in the slightest.  :)
Iago
Wednesday, October 31, 2007
 
 
Functional programming is hardly revolutionary, since it has been with us since 1962... or 1958.
For me, the main interest is that it is easier to avoid some cases of code duplication. It also helps writing code in a more robust style (using fewer side effects).
Nothing terrific, but useful tools.
Pakter
Wednesday, October 31, 2007
 
 
I think part of the benefit comes from the fact that passing a function to another function allows you to easily abstract over behavior rather than just the data.

In other words, compare the following (pseudocode):

isAdditionTrue(1,2,3)
isMultiplyTrue(1,2,2)
isSubTrue(1,2,-1)

versus

isTrue(+,1,2,3)
isTrue(*,1,2,2)
isTrue(-,1,2,-1)

Perhaps a more relevant example:

multiplyBy(list,x){
newlist = null;
for(item in list) newlist.append(item*x);
return newlist;
}

versus

map(list, *2)

You already know how iterator and map work, but notice that since map is usually predefined in some languages, and you can pass functions as parameters, the map method is much shorter and error prone (I had to go back and fix the iterator example because i was returning 'x' rather than 'newlist' :) ).

If "map" is given, "filter" and "reduce" (or "fold") are also very likely given.  You are able to save quite a bit of space and time using the functions which have already been given.

If you see how map and reduce are actually implemented, you might notice that they are themselves just one line or two line functions that make use of fold/reduce (not always done in practical libraries though).  I won't mention 'catamorphisms' :).

Iterators are very useful and easy to do, and you can type them out very quickly. When programmers think "double this list of integers," their iterator code says "create a temporary list, go through the old list and for each item, multiply blah blah blah"

Map/fold/filter programmers think "double this list of integers" and their code says "do 'double' to every item in this list."

This is subtle, notice that "do X to every item" is done by the predefined function, whereas the iterator makes you do it manually (even if it is trivial to do).

Finally, at least one benefit: what if you wanted to spread your computation across 100 machines.  According to the iterator method, you, the programmer, told the machine to do something to the first item and wait for it to finish, THEN do something to the second item and wait for it to finish, THEN do ....

Using the map method, you didn't define the solution in such detail.  In other words, someone can change the map method to do the computation in parallel and you won't notice.
falcon Send private email
Wednesday, October 31, 2007
 
 
>Do watch the MIT SICP video lecture series.

Can someone please provide a URL for these. Google isn't helping me.
Neville Franks Send private email
Thursday, November 01, 2007
 
 
Peter Send private email
Thursday, November 01, 2007
 
 
falcon Send private email
Thursday, November 01, 2007
 
 
I'm also trying to understand this, so I appreciate the thread. 

falcon, from what you're saying it sounds like there are just better libraries involved which allow for the easier syntax/method calling. 

Maybe I'm so trapped in C++, function pointers and STL that I just don't see what is different, but it seems like if I had a large enough template library I could do the same thing in C++.  And of course someone could change the template library out from under me and I wouldn't know or care. 

I DO get the idea of not storing/affecting state (from reading the Wikipedia article), although that seems terribly inefficient, although possibly less bug prone too though?
Doug
Thursday, November 01, 2007
 
 
In Java or a similar language if you want to call a method like this:

function1( a(), b() );

This will send the result of a() and b() a parameters to function1.  In Lisp this looks like:

(function1 (a) (b))

Now, in lisp, you could make a macro and instead pass the blocks of code a and b with something that looked like:

(macro1 (a) (b))

which could be defined somewhere to anything you wanted with those blocks of code, as they haven't been interpreted yet.

The simplest example of passing a function to something that I can think of is:

(apply '+ '(1 2 3))

which is equal to (+ 1 2 3), which is the sum of those numbers.  Basically it apply passes a function and a list of parameters to be used with that function.  It's simply syntactic sugar at that simple level, I mean that more for illustrative purposes.

In Java, there is the try/catch/finally monstrosity.  I've always wanted a simple construct to to the same thing for databases that would resemble try/catch but be transaction/rollback/commit.  My Lisp macro lets me write code like this:

(transaction (sql-code)
            (rollback-code)
            (commit-code))

That sort of simplicity really isn't possible without Lisp macros.  Even this is on the simple end of what can be done but it's really hard to explain why you want code writing code.

There is a quote out there somewhere that goes along the lines, "You don't solve problems with Lisp, you use Lisp to write a domain specific language to solve your problem."

The functional style of programming alone (even without passing code itself) yielded immediate benefits for me in terms of code reuse.

It took me a while before I ever wrote a macro that couldn't have been a function but eventually I wrote one that made my project easier and it just clicked.
Lance Send private email
Thursday, November 01, 2007
 
 
Doug,
Before I continue, I am not an expert in this stuff.  I am merely interested in different paradigms of programming.

Functional programming is not necessarily distinct from what most mainstream languages offer us.  For example, I believe C's ability to pass around function pointers was inspired by a predecessor which took its ideas from LISP (frankly I never seriously programmed in C/C++).  Other features attributed to functional languages are also either becoming available or have been available  in 'popular' langauges for a while.  Garbage collection, 'generic types', ruby's blocks or python's lambda, etc., etc.

I think it is better to talk about specific features of what is usually referred to as functional languages.

Higher order functions, closures, lambda expressions, etc. mean that it is easier to pass around behavior.  In Java, you can't pass the '+' function to the isTrue() function above.  Which means there can be no library which lets you pass in the behavior along with data.  You can pass in an object that just contains one function or uses anonymous classes to to simulate lambdas.  It is not very clean, but certainly possible and is done more and more (I believe anonymous inner classes were encouraged by Guy Steele, one of the guys behind the Scheme language).  Functional programmers probably wouldn't think twice about using lambdas where, while many Java developers would rather explicitly define classes than using anonymous inner classes all over the place.  I think functional language advocates would argue that using anonymous inner classes+interfaces is so useful that it should be made easier and its use should become more pervasive (which would eventually evolve into lambda/closures/HOF).

Variations of 'generics' were recently added to Java/C# and C++ several years ago (such parameterized types from the statically typed branch of FP).  I still see many Java/C# developers think of generics only in context of collections.  Functional programmers would be more willing to use them for things like: containers of concurrent computations (futures), ... actually I can't think of other simple and good examples right now :)

Mutable state is discouraged (as you read in the wikipedia article).  It may indeed be slower, but the promise of the 'functional way of programming' doesn't lie in performance.  It lies in the ability of the programmer to write code with less bugs, to program at a higher level and let the computer do more.  This is one of the reasons I am a fan of SQL.

Paul Graham talks quite a lot about macros.  This is one feature which doesn't exist in any modern and popular programming language.  C's macros are no where near as powerful.

There are plenty of other features attributed to branches of FP such as algebraic data types, pattern matching, currying, laziness, comprehensions, monads, message passing actors, etc., etc., etc.  Many of these features could be done or simulated in existing languages (with varying degrees of ease).  Each of these features changes a programmer's perspective slightly towards the problem they are trying to solve.  When a large number of these features are combined, functional programmers will try to convince you that the new perspective will make it easier to solve the same problems.

I think the best way to figure out if functional programming does indeed help is by doing two things:
1) Try to write a few programs in functional programming langauges
2) Go back to your current language and try to solve the same problems as you usually would

If you find yourself annoyed by your old language, then you are on you way to writing unnecessarily long posts on JoS defending FP :)

I wrote this post over several hours in the middle of a work day so I apologize for this message being disjointed.  I hope others, with more experience in FP, will clear up any confusions.
falcon Send private email
Thursday, November 01, 2007
 
 
"watch the MIT SICP video lecture"

That is an interesting series, particularly how in much of it the lecturer is talking about witchcraft and magic and fancying programmers as those who summon spirits.
David Copperfield
Saturday, November 03, 2007
 
 
"But JavaScript doesn't seem to support Blocks as effectively as Ruby, so there are differences. When one becomes an API author, such differences become rather annoying because one could wish JavaScript had full support to it just like Ruby does." --  Joao Pedrosa

Could you expand a bit on that, or provide a link to an explanation?
Troels Knak-Nielsen Send private email
Saturday, November 03, 2007
 
 
Objects should (maybe probably) have state.

Functions should not have state.

This is a simplification, maybe some objects should be stateless, and maybe some functions need state (caching?), but with functional programming you know that you are getting a ... function. It can be handy.
PeterR Send private email
Saturday, November 03, 2007
 
 
Here's an example in Ruby using the "break" command to break the block:

It returns this output:

retrieving 67...
retrieving 19...
19

For this code:

def retrieve_values
  [67, 19, 53, 31, 48].each{|e|
    puts "retrieving #{e}..."
    yield e
  }
end

def find_first_valid_integer
  r = nil
  retrieve_values{|e|
    if e >= 0 and e <= 50
      r = e
      break
    end     
  }
  r
end

puts find_first_valid_integer


I'm going to implement a version of it in JavaScript for comparison:

function retrieve_values(fn){
  var i, len, a, e;
  a = [67, 19, 53, 31, 48];
  for (i = 0, len = a.length; i < len; i++){
    e = a[i];
    console.log("retrieving " + e + "...");
    if (fn(e) === false){
      break;
    }
  }
}

function find_first_valid_integer(){
  var r;
  r = null;
  retrieve_values(function(e){
      if (e >= 0 && e <= 50){
        r = e;
        return false;
      }     
      });
  return r;
}

console.log(find_first_valid_integer());

There probably can be other solutions to the JavaScript one so it's a matter of standardization as well, which in Ruby is not an issue. Every method in Ruby already has built-in support for blocks so I didn't need to declare the function  and the "yield" command already makes use of it without having to access the variable. But the emphasis probably is in the break command and others like it (redo, next, retry...), even though I rarely use them myself. Break is more common I guess. Are there other differences? I don't know. I'm not a language lawyer by any means. :P
Joao Pedrosa Send private email
Sunday, November 04, 2007
 
 
Thanks, Joao.

So what you're saying is, that Ruby has some better built-in constructs for iterations? You can build higher level utility functions in Javascript, that does similar things, but clearly this gives standardisation problems. Having it supported from the language level, makes cooperation easier. This is a general problem in Javascript, which doesn't have anything to do with functional programming specifically.
Troels Knak-Nielsen Send private email
Sunday, November 04, 2007
 
 
If you can read Java, check out these related musings by someone else:
* Java 7 Example - Writing Your Own Foreach - http://rickyclarkson.blogspot.com/2007/11/java-7-example-writing-your-own-foreach.html

Another use of blocks in Ruby is in IO objects, which are responsible for setting up and cleaning up anything so we can concentrate on what matters. E.g.:

File.open('/home/dewd/t_/sample.txt', 'a'){|f|
  f.puts "Save this log message. #{Time.new}"
}

No hidden magic here aside from what you saw in my previous message, like this:

def log_file
  f = File.open('/home/dewd/t_/sample.txt', 'a')
  begin
    yield f
  ensure
    f.close
  end
end

log_file{|f|
  f.puts "Save this log message. #{Time.new}"
}

So it can be used beyond iteration.
Joao Pedrosa Send private email
Sunday, November 04, 2007
 
 
I thought I could read Java, until I read that. It looks like lisp with static types. Scary stuff! ;) Thanks for the link; Funny to see how Java -- the stronghold of class-based OOP -- is assimilating stuff like closures.

Regarding Ruby blocks; Is it possible to define your own functions, which takes blocks as arguments, or is it limited to the constructs provided by the language?
Troels Knak-Nielsen Send private email
Sunday, November 04, 2007
 
 
I should have read your post better. Disregard my last question.
Troels Knak-Nielsen Send private email
Sunday, November 04, 2007
 
 
The heart of the mater is the map example.  In OOP you abstract the data: you have an iterator that abstracts where you are in the list, you have the list that abstracts the collection, you then write functions to use your abstractions.  In the functional programming approach the basic abstraction is the function you abstract the function and focus on pushing data through your abstraction.  In this case you abstract looping over a collection.  Of course most oop languages provide a similar abstraction usually called foreach but we will ignore that for now.  So they turn out to be different sides of the same coin which do you prefer to  think abstractly about data or functions?
Brian
Tuesday, November 13, 2007
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz