peval(fun pqsort/1, Left) ++ [Pivot] ++ peval(fun pqsort/1, Right).
I was hoping Erlang would be clever enough to execute the two functions in parallel, but that's a fairly unreasonable (and completely incorrect) assumption!
But how to fix it? There is a parallel implementation of the map function in Joe's book, and Montsamu has a variation of it on his blog:
-module(plists).
-export([pmap/2]).
pmap(F, L) ->
S = self(),
Pids = lists:map(fun(I) -> spawn(fun() -> pmap_f(S, F, I) end) end, L),
pmap_gather(Pids).
pmap_gather([H|T]) ->
receive
{H, Ret} -> [Ret|pmap_gather(T)]
end;
pmap_gather([]) ->
[].
pmap_f(Parent, F, I) ->
Parent ! {self(), (catch F(I))}.
The pmap_gather function is the interesting one. It will traverse the list of Pids, and wait for each to send back its result. Using pmap, we update pqsort as follows:
pqsort([]) -> [];
pqsort([Pivot]) -> [Pivot];
pqsort([Pivot|Rest]) ->
io:format("+", []),
Left = [X || X <- Rest, X < Pivot],
Right = [Y || Y <- Rest, Y >= Pivot],
[SortedLeft, SortedRight] = plists:pmap(fun pqsort/1, [Left, Right]),
io:format("-", []),
SortedLeft ++ [Pivot] ++ SortedRight.
And with a perfectly unbalanced (for quicksort) list to sort, we expect 1 process, then 3 as the first spawns 2 new ones, then down again to zero:
(emacs@21ccw.blogspot.com)47> pqsort:pqsort([4,2,1,3,6,5,7]).
+++---[1,2,3,4,5,6,7]
For the same example we had last time, sorting 100 random numbers, the number of concurrent process graph during execution looks like this:

Much better!