5

par is declared as:

par  :: a -> b -> b

Notice, that argument one is thrown away. In order to use par you need to play tricks like using the same expression multiple times.

If its purpose is to execute a and b in parallel, why wasn't it defined like this?:

par  :: (a, b) -> (a, b)

Taking a tuple of (unevaluated) expressions and returning the same expressions - while they are potentially being materialized on background threads.

It seems the latter model is simpler than the former. Why was the design chosen that way?

usr
  • 168,620
  • 35
  • 240
  • 369
  • 2
    I find your version more difficult to think about. The pair you pass to par might be unevaluated. Who evaluates it and when? – augustss Apr 16 '12 at 07:15

2 Answers2

8

In the former, you can easily spark more than two computations,

c1 `par` c2 `par` c3 `par` c4 `pseq` something c1 c2 c3 c4

which would be rather cumbersome in the latter.

Edward Kmett
  • 29,632
  • 7
  • 85
  • 107
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • The latter could be overloaded for up to 8 args and have a list version, too. – usr Apr 15 '12 at 22:26
  • Notice, that argument one is thrown away which means only c4 from your example will survive (without further tricks). – usr Apr 15 '12 at 22:28
  • How would you overload it? `class par a where` and instances for up to 8-tuples? Ugh. – Daniel Fischer Apr 15 '12 at 22:28
  • 5
    The trick is that you _use_ the results of the sparked computations in the final value, which gets a `pseq` too. – Daniel Fischer Apr 15 '12 at 22:30
  • @usr : haskell 'par' and 'pseq' features are tightly coupled to the evaluation model. You should have a look at the "lazy evaluation with sharing" model, as well as the garbage collection model. – Paul R Apr 16 '12 at 11:13
  • So I guess you could say that the Haskell compiler needs to recognize that the expression c1 is used multiple times and allocate a single "lazy evaluation slot" for both usages? If it didn't allocate one slot only, one copy would be calculated in parallel but thrown away. – usr Apr 16 '12 at 12:11
7

The tupled version you suggest can be found as parTuple2 in Control.Parallel.Strategies, with the type:

evalTuple2 :: Strategy a -> Strategy b -> Strategy (a, b)

As for why par was designed that way, par is 'higher level', as chapter 24 of Real World Haskell discusses, where they parallelize a quicksort:

These changes to our code are remarkable for all the things we have not needed to say.

  • How many cores to use.
  • What threads do to communicate with each other.
  • How to divide up work among the available cores.
  • Which data are shared between threads, and which are private.
  • How to determine when all the participants are finished.

In A Monad for Deterministic Parallelism, Marlow, Newton, and Peyton Jones write:

The par operator is an attractive language design because it capitalises on the overlap between lazy evaluation and futures. To implement lazy evaluation we must have a representation for expressions which are not yet evaluated but whose value may later be demanded; and similarly a future is a computation whose value is being evaluated in parallel and which we may wait for. Hence, par was conceived as a mechanism for annotating a lazy computation as being potentially profitable to evaluate in parallel, in effect turning a lazy computation into a future

ja.
  • 4,245
  • 20
  • 22