Functional Programming in Smalltalk?


[Update]Be sure to also read the comments to this article. There’s a lot of good stuff being discussed there – you’d miss the best part[/Update]

Functional Programming Languages like Scala, F# or Clojure have gained quite some momentum in the IT world over the last few years. The main reasons for this may be the fact that functional programming makes multithreading easier than classical programming models. Unfortunately, this leads some people to think that multithreading is much easier with a functional programming language that in others. But this is not completely true. When it comes to synchronisation between threads, you face the same problems as in other languages. But that’s not what I want to write about today.

You may find it interesting that Smalltalk provides many of the concepts of functional programming languages. While Smalltalk surely cannot be classified as a “purely functional language”, it supports the main constructs for functional programming.

Let’s take a look at the two most important concepts of functional programming languages:

  • Higher Order Functions (Closures)
  • Immutability of Data

On Wikipedia, we learn that

Higher-order functions are functions that can either take other functions as arguments or return them as results

Which is exactly what Smalltalk Blocks are: Functions in the form of an object. By implementing a Block like [:arg1 | Transcript show: arg1], we implement a function in Smalltalk. This Block can be passed to a method as an argument. So if you implement a method like this:

evaluateFunction: aFunction

^aFunction value: ‘Hello world’

You can hand in any one-argument block to this function and have it evaluated with the argument ‘Hello World’. So the result of handing the above-mentioned Block into the evaluateFunction: method will be the result of doing a Transcript show:.

Smalltalk’s mighty class library is in fact built heavily on the use of Blocks (Functions). Even the simplest language constructs like ifTrue:/IfFalse: or the mighty Collection classese are making heavy use of Functional Programming.

If you look at this expression:

#(c b a t q) asSortedCollection: [:a :b| a < b]

what you see is the use of a function to sort a list of characters. The function will be repeatedly applied to two neighboring elements of the list until the function returns true for all elements.

The second concept to look at is Immutability. Immutability means that a purely functional language does not support variables. There are only values and results of functions that use values to create new values, which in turn can be used in other functions, until some value is the desired result. In a functional language, you cannot change the value of a variable, because there are no variables. Most functional languages do not follow this rule, because for many programs the complete lack of variables would make things extremely hard.

Smalltalk (or at least most Smalltalk implementations) also follow the immutability rule to a certain extent: you typically cannot assign a new value to method or Block arguments, but if a Block argument is a Collection, nobody keeps you from adding to or removing from it. So even though Smalltalk supports Closures very well, it completely fails on being a purely functional language in this discipline.

With Higher-Order Functions and Immutability in place, it is relatively easy to make a program multithreading-enabled. If the data within a Closure is absolutely independent of the world around it, most of the synchronisation problems in typical multithreading applications become irrelevant. When a function is called, it works on values that it will under no circumstances change, and therefor nobody needs to protect herself from changes to values from another thread. So it’s easy to kick of a function and have it run in its own thread and come back with a new result value whenever it may have finished with what it does. It is guaranteed that a function will not modify any data that is not its own (or its own copy). This is what functional programmers mean if they speak of side-effects: A function does create ne values, but not modify any.

This immediately brings up the question whether a functional programming language can help make every program thread-safe at no-cost.

Well, you may have guessed it already: the answer is no.

Just by using Scala or F#, your program is not magically thread-safe and can run on all four cores of your machine at light-speed. If your problem is not dividable into completely separable threads that operate on their own set of objects with no need to synchronize with other threads, a functional language can still help a lot, but it will not take the burden of finding ways of synchronizing your data. The problem itself will still be hard.

Coming back to Smalltalk, the fact that a Block can access any data and modify it (with the exception of Block arguments) , disqualifies it for the term “purely functional language”.  But still Blocks make Smalltalk a very powerful and expressive language, in which you can achieve a lot with very little code. If you write Blocks that do not modify any objects, you have all the benefits of functional programming. The only problem here is that the language doesn’t really support you in writing Blocks that are free of side-effects. While functional languages keep you from modifying data outside of the function, Smalltalk lets you do that and will not even warn you.

So is it better to use a functional language or Smalltalk?

I don’t really know. I’d say the real hard problem is to make your domain problem dividable into functions that can operate completely independent of each other.  If you know exactly how to formulate a Function or Block that will not have any side-effects, you can surely do so in both Smalltalk and Scala.

A disadvantage of Smalltalk may be that it doesn’t support multithreading on the operating system level. While its green thread model is very lightweight and performant, it’s still running in one single OS process, so its scalability is somewhat limited as compared to OS threads when it comes to multicore processors etc.

On the other hand, the problems you run into when you need side-effects or synchronization between threads the use of a functional language will probably not give you much benefit over other languages. I am not saying that a functional language will do any harm in that case.

But if you have a problem that can be formulated in functions, there is nothing keeping you from using a functional programming style in Smalltalk. The most important building Blocks (what a nice word play ;-) ) are right there in the class library and have been in use for decades.

About these ads

9 thoughts on “Functional Programming in Smalltalk?

  1. Hi,

    I’ve been “returning” to OOP with Smalltalk in a very light way for the last year or so, but fairly heavily the last couple months. My go-to language for personal computing projects has been Haskell for about four years.

    I like your premise, but I don’t agree with it altogether. Parallelism is the sheep’s clothing of FP. The wolf inside is terseness, elegance, and (if you’re using Haskell or SML) strong guarantees about the validity of your code. What we give up to achieve those attributes is (IMO) a portion of our humanity. That’s what brings me back to Smalltalk actually, is the idea that I’m a human and perhaps, even though Haskell has tremendous advantages, perhaps I would rather be a human in my hobbyist projects.

    If you look at parallelism in Haskell with STM or with Erlang, it’s clear they don’t solve the problems of concurrency in a shared-state, multiple-threads paradigm. Haskell, in keeping with its nature, instead chooses to provide you with a restricted view of reality, STM. Erlang foists certain requirements and assumptions on you and leaves you to figure it out. Neither solution is intended to eek the maximum performance out of your SMP machine; rather, they’re both intended to afford casual, low-cost parallelism to the laity, by removing some power. This is the same reason we use languages with garbage collection. 90% of the time, it’s good enough.

    Coming back to Smalltalk from Haskell, it’s easy for me to get shocked or alarmed by just the sheer amount of state. But I see a big part of the point of OOP being, rather than minimizing state, let’s minimize access to state by putting up walls around it and let everybody manage their own chunk. It is messier and leads to more bugs, but I can’t honestly look at a monad transformer stack and say “mmm, elegant!” At least with Smalltalk, the overall level of abstraction is higher than Java or C++, so I don’t need to manage as much state and most of the system was not built for performance over readability and stability. Smalltalk in particular seems much less wedded to the obnoxious design patterns of the enterprise (I don’t know of any logging framework selection frameworks for Smalltalk, yet.)

    I think the #1 reason FP has been gaining ground in the last few years is not because of parallelism so much as because it’s the opposite of what was trendy. The promises of OOP came from Smalltalk, but the war on the ground was won with Java, which meant huge concessions in (at least) readability and reusability. Java still hasn’t caught up and I don’t think it can. When you design a language, it seems to me at some point you have to make hard decisions about whether you’re more interested in modularity or correctness, or whether you’d rather prevent errors or detect and recover from errors, and so forth. Haskell and Smalltalk are on opposite sides of most of these questions, but they’re both powerful, useful tools that can solve many of the same problems. I don’t see this as a reason for them to grow towards each other.

    • Hi Daniel,

      thanks for your thoughtful and long comment.

      Parallelism is the sheep’s clothing of FP. The wolf inside is terseness, elegance, and (if you’re using Haskell or SML) strong guarantees about the validity of your code.

      Well, you may be right. But I guess people care about the outcome: all of these guarantees make parallelism easy. I’d say that if you were a Smalltalk programmer, the lack of terseness and elegance were not on the list of things you missed ;-)

      I still am quite convinced that the promise of easy support for multithreading in FP is one of the main factors of FPs success today. FP has been around for as long as at least 3 decades, but almost nobody cared until we see an urgent need for more multiprocessing support in our multicore machines. Not that there’s anything wrong about that: we have lots of horsepowers in our machines, but still have little software that can really take advantage of it.

      It is, however, an illusion to think that multithreading will be for free by just switching the programming language. Even in FP, it all boils down to designing programs in a way that there is as little shared data between threads or processes as possible. Isolating stuff in a Block of code that can kicked off and forgotten about until it’s done makes parallelism easy. FP languages have some concepts that guarantuee the isolation of Functions, but the design of the Functions is up to the programmer.

      So what I tried to say is that in Smalltalk you’ll find all that’s needed for the implementation of Functions/Closures in Smalltalk Blocks, but no built in support for Immutability. Both Smalltalk on one side and Scala, Clojure or F# on the other completely fail to make up the right isolation design for you. In fact, that’s the hard part anyways. I’d even go so far as to say that you can do functional programming in Smalltalk, and in fact the control structures and Smalltalk’s famous Collection class library are excellent examples of FP. I remember hearing somebody say that Smalltalk was one of the first implementations of a functional programming language (I think it was in a (German) podcast from heise.de)…

      You are of course right if you say that there’s always a trade-off between correctness und ease of use in the design of programming languages, and nobody has come up with a perfect solution yet. If a language warrants that I do not break out a processing unit’s isolation, this is indeed a strong guarantuee. But on the other hand, if putting the pieces together is hard in an FP language, the price is probably highe than I’d like.

      • I agree, and no, I haven’t been missing the terseness or elegance of Haskell from Smalltalk. :)

        I also think high performance and low overhead FP languages are a modern novelty of the last twenty years or so. OOP was still starting to ascend when FP started to become viable; it has had many years in the underground and academia to become more powerful in the interim. If much of the interest in them is over parallelism, they’re also much more viable than they were when people last seriously considered them in the 70s.

  2. A would also say Traits [1] in Smalltalk (or other OO languages) goes in more functional way, because it offers to start design by composing related functions together. It is much better then class-structure-design.

    [1] http://scg.unibe.ch/research/traits

  3. I find I write more and more Smalltalk code these days strictly using blocks, with class definitions really almost an afterthought on how to organizing my code. For me changing the language is not important. I find the syntax is almost as minimal as one could hope, but still very useful in coming up with new ways to think about things.

    But there are drawbacks in some of the idioms. Envy especially makes it difficult to extend classes without making “design decisions” that are unrelated to a particular computational task. There is definitely a quandary on how one can explore more productive abstractions without creating a yet another new niche language.

  4. I partly agree with you: it’s asking for a lot of pain to break several decades worth of code, so I don’t propose changing the core libraries. I guess a point I was not saying out loud was that it’s hard to write in a “purely” functional way with non-functional libraries.

    Giving up closures wouldn’t make Smalltalk any more functional, since both Haskell and Scheme support closures. On the contrary, losing closures ruins the most functional part of Smalltalk, the Collection API (#collect:, #select:, etc.) , and all the control structures.

    I was going to reply in length, but then I reread your comments and realised I was just going to violently agree, regarding the problem of multithreading, and share-nothing, and so on. (It’s precisely this kind of thing that I’m aiming for: with no side effects, and no mutating state, it’s trivial to share things (as long as you have strong guarantees on immutability, like a VM might provide)).

    Regarding persistence, I’m interested in applying things like zippers, and particularly interesting in how they will change the design of an application. I don’t have answers at this stage, just questions.

  5. I’ve been wrestling with these exact issues recently, and been blogging my tentative experiments.

    In particular, I’ve been drawing heavily on Matthias Felleisen’s and William Cook’s work.

    The thing about immutability and (Squeak) Smalltalk seems to be one of discipline: it’s up to you to have setters that only ever return new objects (rather than mutating the object) and such. The Collections library’s really functional, except for method like #add: and friends – in a “properly functional” library, (OrderedCollection with: 1) add: 2 would return a whole new collection containing both 1 and 2. Of course one could always write a functional wrapper, so FunctionalOrderedCollection>>add: would return a whole new FunctionalOrderedCollection.

    • Frank,

      so I just added your blog to my reading list. ;-)

      I am not totally decided whether I think we should try and make Smalltalk everybody’s darling by adding “functional-copliant” methods to Collections or anything. Changes are, we forget to use the right ones. And I also think it’s not an option to change Smalltalk to be fully functional by replacing existing methods either. We’d possibly break 30 years worth of code.

      But I think it is well worth trying to look at FP from the right perspective: FP can help tremendously once you’ve found a problem domain that allows for unsynchronized parallelism. And there are many cool features in Scala, one of which is the fact that it runs on the JVM (once you’ve figured out which startup parameters to feed into it).

      On the other hand, an important think to keep in mind is that Mult-Threading or Multi-Processing is not for free in FP. It comes at a cost.

      I see the same kind of discussion around the question whether Smalltalk vendors should finally ship 64 bit, native OS-threaded VMs. People seem to forget (or not know) that this will only help if they design their programs in a way that they can run multithreaded. And it’s not really new that this is hard. Unless you seperate the threads to an extend where they don’t spit into each others’ soup (a German saying), meaning where the share no data. This is where an FP Language helps you alot because it doesn’t allow sharing by always copying values.

      There are, however, problems to this copying approach: what if I need object Identity, like for an ORM framework? I’d have to build (or get) some mechanics for keeping track of what belongs where before I decide whether to update or insert to the database. So there must be some root object which keeps track of what copies it handed to which function and collect all the results back to the original. Not that this cannot be solved, but it will result in a different kind of program. This may be good for a certain class of programs, it may be bad for others, but it sure means a major paradigm shift in designing and implementing software.

    • There’s something I forgot in my first answer to your comment: Most FP languages do support variables, because without them, some things can be very complicated.

      If we wanted to move Smalltalk a bit more into the “pure FP” direction, we could possibly build a new BlockClosure sibling that copies its input values and cannot access anything from its outer context. Maybe such a (strange) thing could be called a Function. It could have a polymorphic Interface shared with BlockClosure (Maybe it could be a sub or superclass of it).

      But I am not sure it would be all that helpful, other than restricting a programmer in a similar way a static type system does.

Comments are closed.