Home > OS >  Haskell: parallel do versus list comprehension
Haskell: parallel do versus list comprehension

Time:01-25

To make a list comprehension run in parallel, it's easy to tack on using parList rdeepseq, but what if the list comprehension is expressed using do notation?

For example, this bit of code runs just fine in parallel.

    entry n = [ (rowSet,finalEV rowSet,rrDone rowSet,oneRRLeft rowSet,twoRRLeft rowSet) | rowSet <- getChoiceList 13 n] `using` parList rdeepseq
      where
          prevFinal = getFinalEVNEW $ entry (n-1)
          
          rrDone rowSet = (getRRDoneNEW prevFinal rowSet)
          
          doneStripped rowSet = map (\(v,_,ev) -> (v,ev)) (rrDone rowSet)
          oneRRLeft rowSet = getRRChoicesNEW (doneStripped rowSet)
          
          oneRRStripped rowSet = map (\(v,_,ev) -> (v,ev)) (oneRRLeft rowSet)
          twoRRLeft rowSet = getRRChoicesNEW (oneRRStripped rowSet)
          
          twoRRStripped rowSet = map (\(v,_,ev) -> (v,ev)) (twoRRLeft rowSet)
          finalEV rowSet = weightedEV theProbs (twoRRStripped rowSet)

But it's easier to read when expressed with do notation. The part above, expressed in do form, and with the broader context becomes:

monster :: [[([Bool],Double,[([Int],Int,Double)],[([Int],[Bool],Double)],[([Int],[Bool],Double)])]]

monster = [ entry i | i <- [0..13] ]
  where
    entry 0 = [(replicate 13 False,0,[],[],[])]
    
    entry n = go ( getFinalEVNEW ( entry (n-1) ) )
      where
        go prevFinal = do 
          
          rowSet <- (getChoiceList 13 n )
          
          let rrDone = (getRRDoneNEW prevFinal rowSet)
          let doneStripped = map (\(v,_,ev) -> (v,ev)) rrDone
          let oneRRLeft = getRRChoicesNEW doneStripped
          
          let oneRRStripped = map (\(v,_,ev) -> (v,ev)) oneRRLeft
          let twoRRLeft = getRRChoicesNEW oneRRStripped
          
          let twoRRStripped = map (\(v,_,ev) -> (v,ev)) twoRRLeft
          let finalEV = weightedEV theProbs twoRRStripped
          
          return (rowSet,finalEV,rrDone,oneRRLeft,twoRRLeft)

But it's unclear how to make the do form run in parallel.

Is there a straightforward way to transform a parallel list comprehension to an equivalent do form?

EDIT: The "do version" was expanded a bit for context.

A related issue is the fact the algorithm is recursive, and any attempt to parallelize entry n should wait until entry (n-1) is in normal form. Right?

This is a calculation for a solution to a game similar to Yahtzee. It's a "let's learn Haskell" exercise.

CodePudding user response:

The straightforward translation of the code you say works looks like this:

entry n = go ( getFinalEVNew ( entry (n-1) ) ) `using` parList rdeepseq

If you insist on attaching the using clause to the do block, you can either use parentheses:

go prevFinal = (do
  ...
  ) `using` parList rdeepseq

or use an indentation level between that of the where block and the do block:

go prevFinal = do
  ...
 `using` parList rdeepseq

or apply using in prefix:

go prevFinal = (`using` parList rdeepseq) $ do
  ...
  • Related