Haskell vs GC'd imperative languages, threading, parallelizeability (is that a word? :-D )

Haskell vs GC'd imperative languages
===========================
On 8/10/07, Thomas Conway
Well, C++ is not really competitive with Haskell, because C++ does not have a GC, and it's trivial to corrupt the stack/heap.
Beg to differ. I offer the following proof by contradiction. :-)
In my current job, I had a version-1 implementation in Python which had severe performance problems, and was not amenable to concurrency (The Python interpreter has a global lock, so you can only execute python bytecodes from one thread at a time. :-(). The natural alternative implementation language was C++, but I argued successfully that a Haskell implementation would be significantly easier to make concurrent.
Well, Python is comparable to Haskell to the extent that it has a GC. OTOH it is interpreted, and, well, slow. It's also got a Big Lock around the core system libraries. Trying to avoid naming it by name, because there's a lot of elitism against using mainstream languages that are used by "stupid people", but the fastest imperative GC'd language that I know of is C#. Even the opensource mono version is wickedly fast. The Microsoft version shaves another 20% off execution speed or so. See, I get the feeling that a lot of people are using Haskell not because they compared it to C# and found it better ,but because they compared it to: - C -> Haskell is easier - C++ -> Haskell is easier - Python -> Haskell is faster C# -> fast and easy ;-) Threading ======= I'm not trolling, despite strong appearances to the contrary ;-) My primary objective/goal is to find a way to make threading easy. Thread management today is like memory management in the early 90s. We kindof had tools (new, delete in C++ for example) to do it. At some point we started to use things like Smart Pointers to make it easier, but none of it really worked. Issues such as circular referential loops caused this not to work very well. At some point, someone came out with the idea of a garbage collector, which is really very simple once you know: all it does is walk the graph of variables, starting with those we know are definitely used, and delete all those objects that didnt get walked on. Simple, easy, magic. We should do the same thing for threads. Threading is going to become a major issue soon, maybe not tomorrow, but there is a GPL'd Niagara 2 out with 64 threads (I think?), so the time is now. Haskell solves a huge issue with threads, which is locking, which is a total PITA in imperative languages, it's of the same order of magnitude of problem as eliminating memory leaks, without deleting something twice etc, used to be in pre-GC C++. Nevertheless, one cannot be interested in threads without being interested in performance. There's no point in running a program in 1024 threads, if the single-core C# version runs faster than that, and often it does! Parallelizeability ============ Now, I did have kindof a shootout thread with Don and Sebastien, calculating prime numbers, where Don managed to get to within an order of magnitude of C# performance (I think he got to about 70-80% of C# performance, cool!) -> BUT, and its a big but ;-), as far as I can tell, he did that by sacrificing the Haskell ability to be easily parallelized; his solution was essentially imperative.

hughperkins:
Now, I did have kindof a shootout thread with Don and Sebastien, calculating prime numbers, where Don managed to get to within an order of magnitude of C# performance (I think he got to about 70-80% of C# performance, cool!) ->
Despite my better judgement, I'll just point out that you stopped measuring the programs midway through. However, the shootout guys didn't, and they publish the results here: http://shootout.alioth.debian.org/gp4/benchmark.php?test=nsievebits&lang=all The GHC-compiled entry comes in at 1.2x C++, which I'm quite happy with. Strangely, despite your assertions, the C# entry lags well down the list. -- Don

On 8/10/07, Donald Bruce Stewart
http://shootout.alioth.debian.org/gp4/benchmark.php?test=nsievebits&lang=all
The GHC-compiled entry comes in at 1.2x C++, which I'm quite happy with. Strangely, despite your assertions, the C# entry lags well down the list.
Yeah, you somehow managed to slip around their rules without them noticing ;-) They specify to use a bit array, but you dont ;-) Bit arrays are optimal for space, but can be really slow.

hughperkins:
On 8/10/07, Donald Bruce Stewart <[1]dons@cse.unsw.edu.au> wrote:
[2]http://shootout.alioth.debian.org/gp4/benchmark.php?te st=nsievebits&lang=all The GHC-compiled entry comes in at 1.2x C++, which I'm quite happy with. Strangely, despite your assertions, the C# entry lags well down the list.
Yeah, you somehow managed to slip around their rules without them noticing ;-) They specify to use a bit array, but you dont ;-) Bit arrays are optimal for space, but can be really slow.
It's using bit arrays. -- Don

On 8/10/07, Donald Bruce Stewart
It's using bit arrays.
Well I'm a total Haskell newbie, and you're using Haskell to write imperative code, so it's really hard for me to read, but looking at your code, you have: (IOUArray Int Bool) -- an array of Bool Bool is a 32-bit value in Haskell AFAIK? (or possibly a machine-dependent sized value, but certainly not a bit?)

hughperkins:
On 8/10/07, Donald Bruce Stewart <[1]dons@cse.unsw.edu.au> wrote:
It's using bit arrays.
Well I'm a total Haskell newbie, and you're using Haskell to write imperative code, so it's really hard for me to read, but looking at your code, you have:
(IOUArray Int Bool) -- an array of Bool
Bool is a 32-bit value in Haskell AFAIK? (or possibly a machine-dependent sized value, but certainly not a bit?)
No, STUArray s Int Bool is a bit array. Look at the space use. Or try replacing Bool with Word32, and see what happens. -- Don

On 8/10/07, Donald Bruce Stewart
No, STUArray s Int Bool is a bit array. Look at the space use. Or try replacing Bool with Word32, and see what happens.
Fair enough. Well, the mono example in the shootout is lacking quite a few optimizations, eg its using the builtin BitArray (slowww....), and it's not checking for the value of the bits before writing them. I dont quite get as fast as your ghc version (yet ;-) ), but its within 10% on my machine, using Microsoft C#, and staying within the rules of the shootout. G:\dev\haskell>primeshootoutmono3 Primes up to 10240000 679461 elapsed time: 0.921875 G:\dev\haskell>primeshootoutghc Primes up to 10240000 679461 0.8280000000000001 class NSieveBits { public int nsieve(int m) { int[] isNotPrime = new int[((m+1) >> 5) + 1]; int count = 0; int wordindex = 0; int bitmask = 4; for (int i=2; i <= m; i++) { //Console.WriteLine( i + " " + wordindex + " " + bitmask ); if ( ( isNotPrime[wordindex] & bitmask ) == 0 ) { //Console.WriteLine("prime: " + i ); for (int k = (i << 1); k <= m; k+=i) { int _wordindex = k >> 5; int _bitmask = 1 << ( k & 31 ); int _thisword = isNotPrime[_wordindex]; if( ( _thisword & _bitmask ) == 0 ) { isNotPrime[ _wordindex ] = _thisword | _bitmask; } } count++; } if( bitmask == ( 1 << 31 ) ) { wordindex++; bitmask = 1; } else { bitmask <<= 1; } } return count; } } It'd be more interesting to run these things in a multicore environment (16 cores would be ok, 64 would be better). Are there any utilities out there to do this? (vmware???)

On Fri, Aug 10, 2007 at 04:00:01PM +0800, Hugh Perkins wrote:
On 8/10/07, Donald Bruce Stewart
wrote: It's using bit arrays.
Well I'm a total Haskell newbie, and you're using Haskell to write imperative code, so it's really hard for me to read, but looking at your code, you have:
(IOUArray Int Bool) -- an array of Bool
Bool is a 32-bit value in Haskell AFAIK? (or possibly a machine-dependent sized value, but certainly not a bit?)
Bool is 32 bits, but Don is using UArray. UArray is not parametric in the element type, which means it's less generally useful (no UArray of Complex Double, for instance), but conversely it is able to use more efficient representations depending on the type. from Data.Array.Base: instance MArray (STUArray s) Bool (ST s) where ... unsafeRead (STUArray _ _ marr#) (I# i#) = ST $ \s1# -> case readWordArray# marr# (bOOL_INDEX i#) s1# of { (# s2#, e# #) -> (# s2#, (e# `and#` bOOL_BIT i#) `neWord#` int2Word# 0# #) } ... Okay, you probably can't read that (it's too ugly for me to *want* to read it), but the use of and# should stand out. bOOL_BIT computes a bitmask, bOOL_INDEX divides by 8. Stefan

On 8/10/07, Stefan O'Rear
Bool is 32 bits, but Don is using UArray. UArray is not parametric in the element type, which means it's less generally useful (no UArray of Complex Double, for instance), but conversely it is able to use more efficient representations depending on the type.
Ok, interesting :-)

Well, managed to shave 25% of C# execution time by writing my own bit array. For now, I will concede that, under the conditions of the shoot, bitarrays in c# are slower than bitarrays in Haskell. I'll let you know if I get any new ideas on this. Getting back to the original problem, which is: threading. Donald, one of the things that is very interesting about Haskell is it's potential for automatic threading, ie you write a trivial algorithm that looks like it runs in a single thread, and the runtime splits it across multiple cores automatically. It's fairly safe to say that maps, foldrs, foldls, and their derivatives are safe to parallelize? (For example, hand-waving argument, a foldr of (/) on [1,5,7,435,46,2] can be split into a foldr on [1,5,7] and a foldr on [435,46,2], then their results combined). To what extent is the technology you are using in your algorithm parallizable? (I actually cant tell, it's a genuine question). In the case that it is parallelizable, to what extent is it trivial for a runtime to know this? (Again, I dont have enough information to tell)

On Fri, Aug 10, 2007 at 04:51:49PM +0800, Hugh Perkins wrote:
Well, managed to shave 25% of C# execution time by writing my own bit array. For now, I will concede that, under the conditions of the shoot, bitarrays in c# are slower than bitarrays in Haskell. I'll let you know if I get any new ideas on this.
Getting back to the original problem, which is: threading. Donald, one of the things that is very interesting about Haskell is it's potential for automatic threading, ie you write a trivial algorithm that looks like it runs in a single thread, and the runtime splits it across multiple cores automatically.
It's fairly safe to say that maps, foldrs, foldls, and their derivatives are safe to parallelize? (For example, hand-waving argument, a foldr of (/) on [1,5,7,435,46,2] can be split into a foldr on [1,5,7] and a foldr on [435,46,2], then their results combined).
Not really, because Haskell isn't powerful enough to express the fact that (/) is associative. (It isn't, but I'm ignoring that fact since I'm pretty sure it's beside the point). It is possible to do it using an explicit foldb, however, and to a 1st approximation this is what lies behind NDP. On the other hand, division is *much* faster than sparking threads, especially once factors invisible in the code (eg, cache-line ping-pong on scheduler data structures) is taken into account. In the past, optimistic researchers have developed automatic parallelisation algorithms. The best of these were several times slower than decent sequential algorithms. The explicit case, using `par` or equivalent, is more interesting. `par` gives you enough control to implement efficient threading, but is semantically extremely lightweight - you can ignore par using the rule x `par` y === y. Adding or removing par following that rule cannot possibly introduce bugs, far better than the equivalent situation with global mutable data and forkIO!
To what extent is the technology you are using in your algorithm parallizable? (I actually cant tell, it's a genuine question). In the case that it is parallelizable, to what extent is it trivial for a runtime to know this? (Again, I dont have enough information to tell)
Something tells me the To: field of your message doesn't correspond with the person you're actually addressing :) Stefan

On Friday 10 August 2007 03:51:49 Hugh Perkins wrote:
Well, managed to shave 25% of C# execution time by writing my own bit array. For now, I will concede that, under the conditions of the shoot, bitarrays in c# are slower than bitarrays in Haskell. I'll let you know if I get any new ideas on this.
Getting back to the original problem, which is: threading. Donald, one of the things that is very interesting about Haskell is it's potential for automatic threading, ie you write a trivial algorithm that looks like it runs in a single thread, and the runtime splits it across multiple cores automatically.
It's fairly safe to say that maps, foldrs, foldls, and their derivatives are safe to parallelize? (For example, hand-waving argument, a foldr of (/) on [1,5,7,435,46,2] can be split into a foldr on [1,5,7] and a foldr on [435,46,2], then their results combined).
Yes, the semantics of Haskell allow us to run pretty much any operation in parallel. However, the major problem is that lists are a fundamentally sequential data structure. It's quite expensive to chop up parts of a list to eg. send them to a parallel map function. I think the upcoming NDP arrays have a much better chance here.
To what extent is the technology you are using in your algorithm parallizable? (I actually cant tell, it's a genuine question). In the case that it is parallelizable, to what extent is it trivial for a runtime to know this? (Again, I dont have enough information to tell)
The program doesn't have much chance for parallelism as written. It uses the imperative ST monad and destructive update extensively. Spencer Janssen

On Friday 10 August 2007 03:51:49 Hugh Perkins wrote:
Getting back to the original problem, which is: threading. Donald, one of the things that is very interesting about Haskell is it's potential for automatic threading, ie you write a trivial algorithm that looks like it runs in a single thread, and the runtime splits it across multiple cores automatically.
In the interests of clarity, it is useful and important to distinguish between threading (i.e. concurrency) and parallelism. The former relates to the programmer's view of the world, and the latter relates to the computer's model of execution. As nomenclature, it is not universal, but is very common, and helps avoid conflating two quite different things. cheers, Tom -- Dr Thomas Conway drtomc@gmail.com Silence is the perfectest herald of joy: I were but little happy, if I could say how much.

Stefan O'Rear wrote:
Bool is 32 bits, but Don is using UArray. UArray is not parametric in the element type, which means it's less generally useful (no UArray of Complex Double, for instance), but conversely it is able to use more efficient representations depending on the type.
Would be nice if it *could* somehow be parametric... but I have absolutely no idea how you'd do that. The transformation is self-evident enough to a human, but how do you explain it to a machine? (I somewhat suspect you'd have to bake this into the compiler itself it you wanted *arbitrary* types. Otherwise you'd have to write some special class that knows how to pack and unpack the data from the array, perhaps as a bundle of Word8s or something? Doesn't sound hugely fast...)

On Friday 10 August 2007 12:37:31 Andrew Coppin wrote:
Stefan O'Rear wrote:
Bool is 32 bits, but Don is using UArray. UArray is not parametric in the element type, which means it's less generally useful (no UArray of Complex Double, for instance), but conversely it is able to use more efficient representations depending on the type.
Would be nice if it *could* somehow be parametric... but I have absolutely no idea how you'd do that. The transformation is self-evident enough to a human, but how do you explain it to a machine?
Check out the various papers, slides, and talks on NDP/parrallel arrays. There's much discussion on schemes to efficiently pack data types into unboxed arrays. Cheers, Spencer Janssen
(I somewhat suspect you'd have to bake this into the compiler itself it you wanted *arbitrary* types. Otherwise you'd have to write some special class that knows how to pack and unpack the data from the array, perhaps as a bundle of Word8s or something? Doesn't sound hugely fast...)

Spencer Janssen wrote:
On Friday 10 August 2007 12:37:31 Andrew Coppin wrote:
Stefan O'Rear wrote:
Bool is 32 bits, but Don is using UArray. UArray is not parametric in the element type.
Would be nice if it *could* somehow be parametric... but I have absolutely no idea how you'd do that. The transformation is self-evident enough to a human, but how do you explain it to a machine?
Check out the various papers, slides, and talks on NDP/parrallel arrays. There's much discussion on schemes to efficiently pack data types into unboxed arrays.
NDP is like Christmas! So many boxes of goodies to open... but it takes so long to come around... :-(

Hello Andrew, Friday, August 10, 2007, 9:37:31 PM, you wrote:
Would be nice if it *could* somehow be parametric...
ForeignArray may hold any Storable values. look at http://haskell.org/haskellwiki/Modern_array_libraries - we have no less than 10 array constructors :)) -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Hugh Perkins wrote:
I'm not trolling, despite strong appearances to the contrary ;-) My primary objective/goal is to find a way to make threading easy. Thread management today is like memory management in the early 90s. We kindof had tools (new, delete in C++ for example) to do it. At some point we started to use things like Smart Pointers to make it easier, but none of it really worked. Issues such as circular referential loops caused this not to work very well.
At some point, someone came out with the idea of a garbage collector, which is really very simple once you know: all it does is walk the graph of variables, starting with those we know are definitely used, and delete all those objects that didnt get walked on. Simple, easy, magic.
We should do the same thing for threads. Threading is going to become a major issue soon, maybe not tomorrow, but there is a GPL'd Niagara 2 out with 64 threads (I think?), so the time is now.
Haskell solves a huge issue with threads, which is locking, which is a total PITA in imperative languages, it's of the same order of magnitude of problem as eliminating memory leaks, without deleting something twice etc, used to be in pre-GC C++.
Just to get the history right: garbage collectors have been around a _long_ time, since the '60s in Lisp systems. They only became known to most programmers through Java (which is one unarguable good thing that Java did). As for threading, in addition to Haskell's approach you might also look at Erlang, which has a quite different (and quite interesting) approach to the whole problem. I wonder if anyone has tried to implement a message-passing style of concurrency in Haskell. Mike

On 8/10/07, Michael Vanier
Just to get the history right: garbage collectors have been around a _long_ time, since the '60s in Lisp systems. They only became known to most programmers through Java (which is one unarguable good thing that Java did).
Ah interesting :-)
As for threading, in addition to Haskell's approach you might also look at Erlang, which has a quite different (and quite interesting) approach to the whole problem. I wonder if anyone has tried to implement a message-passing style of concurrency in Haskell.
Erlang message passing rocks :-) I'd love to see this working in Haskell. Note that Erlang's message passing is not perfect. Specifically, there is zero type or parameter checking. It will quite happily let the following compile: % initialization routines etc go here % ... % ping thread. this sends messages to pong ping() -> pong ! hello, pong ! hello, pong ! {hello}, % this does not throw a compile error pong ! hello, io:format("ping done~n" ). % pong thread, this listens for messages from pong pong() -> receive hello -> io:format("pong received hello~n"), pong() end. You can see that sending the tuple {hello} does not cause a compile time error (or even a runtime error for that matter), even though pong does not have a valid pattern for receiving it. Now, arguably the fact that we are pattern matching on the receiver at least means we dont do anything with the invalid data sent, but this is not rocket science: the standard technique to ensure decent compile time validation in rpc-type things is to use an interface. The interface defines the method names and parameters that you can send across. Both the receiver and the sender have access to the interface definition, and it is trivial to check it at compile time. (Caveat: havent looked enough into Erlang to know if there is a good reason for not using an interface?)

Hugh Perkins wrote:
Now, arguably the fact that we are pattern matching on the receiver at least means we dont do anything with the invalid data sent, but this is not rocket science: the standard technique to ensure decent compile time validation in rpc-type things is to use an interface.
The interface defines the method names and parameters that you can send across. Both the receiver and the sender have access to the interface definition, and it is trivial to check it at compile time.
(Caveat: havent looked enough into Erlang to know if there is a good reason for not using an interface?)
Remember that Erlang is an untyped language and that you are allowed to send any kind of data. However, there is more to consider here: A certain amount of dynamism wrt the message content (high level protocol) is necessary for systems for which Erlang was designed, namely large distributed control systems with minimum down-times. For large distributed installations it is a matter of practicality to be able to upgrade one component w/o needing to recompile (and then re-start) all the other components that it communicates with -- for systems with expected down-times of 3 Minutes per year it is a matter of being able to meet the specifications. You'll have a hard time finding high-availability or large control systems which use an IDL approach for communication. Cheers Ben

On 8/11/07, Benjamin Franksen
A certain amount of dynamism wrt the message content (high level protocol) is necessary for systems for which Erlang was designed, namely large distributed control systems with minimum down-times. For large distributed installations it is a matter of practicality to be able to upgrade one component w/o needing to recompile (and then re-start) all the other components that it communicates with -- for systems with expected down-times of 3 Minutes per year it is a matter of being able to meet the specifications. You'll have a hard time finding high-availability or large control systems which use an IDL approach for communication.
Hmmm, that's interesting. I'd never considered lack of typing to be a good thing for system robustness before! Question: to what extent does interface versioning get around this problem? I assume the issue we're trying to address is to be able to upgrade clients/servers/peers independently, without losing connectivity with unupgraded systems? So, using versioned interfaces: Initially we have: client1 marketinterface1 server marketinterface1 client2 marketinterface1 Then, we upgrade the server with a new interface, marketinterface2. Significantly, we keep the old interface. So now we have: client1 marketinterface1 server marketinterface1, marketinterface2 client2 marketinterface1 The whole system continues to work: client1 and client2 continue to chat with server on marketinterface1. Now we upgrade client1: client1 marketinterface2 server marketinterface1, marketinterface2 client2 marketinterface1 ... and client2: client1 marketinterface2 server marketinterface1, marketinterface2 client2 marketinterface2 Finally, we deprecate/remove marketinterface1 from the server: client1 marketinterface2 server marketinterface2 client2 marketinterface2

On Aug 21, 2007, at 23:27 , Hugh Perkins wrote:
Hmmm, that's interesting. I'd never considered lack of typing to be a good thing for system robustness before!
The old watchphrase (before Netscape and Microsoft abused it beyond anyone's expectation) for Internet protocols was "be liberal in what you accept and conservative in what you send"; same idea. -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

Hugh Perkins wrote:
On 8/11/07, Benjamin Franksen
wrote: A certain amount of dynamism wrt the message content (high level protocol) is necessary for systems for which Erlang was designed, namely large distributed control systems with minimum down-times. For large distributed installations it is a matter of practicality to be able to upgrade one component w/o needing to recompile (and then re-start) all the other components that it communicates with -- for systems with expected down-times of 3 Minutes per year it is a matter of being able to meet the specifications. You'll have a hard time finding high-availability or large control systems which use an IDL approach for communication.
Hmmm, that's interesting. I'd never considered lack of typing to be a good thing for system robustness before!
I didn't use the term robustness, which is typically used to express how well a program handles unexpected run-time conditions. What I refered to is maintainability in the face of continually changing requirements and the need to gradually upgrade components. (And rest assured I'm normally all for static typing!)
Question: to what extent does interface versioning get around this problem? I assume the issue we're trying to address is to be able to upgrade clients/servers/peers independently, without losing connectivity with unupgraded systems?
Yes (to the latter), and 'to a certain extent' to the former.
So, using versioned interfaces:
Initially we have: client1 marketinterface1 server marketinterface1 client2 marketinterface1
Then, we upgrade the server with a new interface, marketinterface2. Significantly, we keep the old interface. So now we have:
client1 marketinterface1 server marketinterface1, marketinterface2 client2 marketinterface1
The whole system continues to work: client1 and client2 continue to chat with server on marketinterface1.
Now we upgrade client1:
client1 marketinterface2 server marketinterface1, marketinterface2 client2 marketinterface1
... and client2:
client1 marketinterface2 server marketinterface1, marketinterface2 client2 marketinterface2
Finally, we deprecate/remove marketinterface1 from the server:
client1 marketinterface2 server marketinterface2 client2 marketinterface2
Yes, you can somewhat reduce the impact of an IDL based approach if you introduce the new interface in addition to the old one (and only deprecate the old interface after all components have been upgraded). However, this approach has its limits, especially if you consider large installation with 10s to 1000s of servers and clients (many of them being both at the same time), especially if upgrades are very frequent due to changing physical parameters, new requirements, additional instrumentation, etc. Let us be clear on one point: It is of course not possible for a client to make use of new features offered by a server if they do not know about these features. With an IDL approach, knowledge about new features need re-compilation and usually also new code to handle the new information. With a dynamic approach, this becomes largely a matter of changing the /configuration/ of a client, which is a much less disruptive process. Cheers Ben

Hello Michael, Friday, August 10, 2007, 12:50:43 PM, you wrote:
As for threading, in addition to Haskell's approach you might also look at Erlang, which has a quite different (and quite interesting) approach to the whole problem. I wonder if anyone has tried to implement a message-passing style of concurrency in Haskell.
if you mean Erlang's sophisticated rules of which messages in queue to process first - this may be not yet implemented for Haskell. if you mean that program is just many threads which pass messages through channels to each other - it's easily accomplished in Haskell through Chan constructor. you can look at chameneos shootout entry, for example in my program (http://www.haskell.org/bz/FreeArc-sources.tar.gz) i run ~10 threads that process data sequentially - i.e. results of first thread are consumed by second one and so on. i glue them together with my own tiny threads library which somewhat mimics unix pipes: runP$ thread1 |> thread2 |> thread3 ... you can find this lib in Process.hs module. overall, i think that key to Haskell's simplicity of developing multithreaded programs is its immutable data, plus type inference which significantly improves gluing things together and using higher-order funcs -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On 8/10/07, Bulat Ziganshin
if you mean Erlang's sophisticated rules of which messages in queue to process first - this may be not yet implemented for Haskell. if you mean that program is just many threads which pass messages through channels to each other - it's easily accomplished in Haskell through Chan constructor. you can look at chameneos shootout entry, for example
Forgot to mention, a really cool thing about Erlang message-passing is that the other thread doesnt have to be on the same machine ;-)

Hugh Perkins wrote:
Threading is going to become a major issue soon, maybe not tomorrow, but there is a GPL'd Niagara 2 out with 64 threads (I think?), so the time is now...
I didn t know what was niagara 2, and by researching , I also found about tilera, a 64 CORE issued that August , for $435. affordable... short : http://hardware.slashdot.org/article.pl?sid=07/08/20/1830221 longuer : http://www.marketwire.com/mw/release.do?id=761947 -- View this message in context: http://www.nabble.com/Haskell-vs-GC%27d-imperative-languages%2C-threading%2C... Sent from the Haskell - Haskell-Cafe mailing list archive at Nabble.com.
participants (11)
-
Andrew Coppin
-
Benjamin Franksen
-
Brandon S. Allbery KF8NH
-
Bulat Ziganshin
-
dons@cse.unsw.edu.au
-
Hugh Perkins
-
luc.taesch
-
Michael Vanier
-
Spencer Janssen
-
Stefan O'Rear
-
Thomas Conway