Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>It is a common myth that Haskell doesn't offer mutation at all. In reality, it offers a very special kind of mutation: a value can mutate exactly once, from un-evaluated to evaluated. The art of taking advantage of this special kind of mutation is called <a href="http://www.haskell.org/haskellwiki/Tying_the_knot" rel="noreferrer">tying the knot</a>. We will start with a data structure just like your one from C++:</p> <pre><code>data Vector -- held abstract data Point = Point { position :: Vector , v, w :: Double , neighbors :: [Point] } </code></pre> <p>Now, what we're going to do is build an <code>Array Point</code> whose <code>neighbors</code> contain pointers to other elements within the same array. The key features of <code>Array</code> in the following code are that it's spine-lazy (it doesn't force its elements too soon) and has fast random-access; you can substitute your favorite alternate data structure with these properties if you prefer.</p> <p>There's lots of choices for the interface of the neighbor-finding function. For concreteness and to make my own job simple, I will assume you have a function that takes a <code>Vector</code> and a list of <code>Vectors</code> and gives the indices of neighbors.</p> <pre><code>findNeighbors :: Vector -&gt; [Vector] -&gt; [Int] findNeighbors = undefined </code></pre> <p>Let's also put in place some types for <code>computeV</code> and <code>computeW</code>. For the nonce, we will ask that <code>computeV</code> live up to the informal contract you stated, namely, that it can look at the <code>position</code> and <code>neighbors</code> fields of any <code>Point</code>, but not the <code>v</code> or <code>w</code> fields. (Similarly, <code>computeW</code> may look at anything but the <code>w</code> fields of any <code>Point</code> it can get its hands on.) It is actually possible to enforce this at the type level without too many gymnastics, but for now let's skip that.</p> <pre><code>computeV, computeW :: Point -&gt; Double (computeV, computeW) = undefined </code></pre> <p>Now we are ready to build our (labeled) in-memory graph.</p> <pre><code>buildGraph :: [Vector] -&gt; Array Int Point buildGraph vs = answer where answer = listArray (0, length vs-1) [point pos | pos &lt;- vs] point pos = this where this = Point { position = pos , v = computeV this , w = computeW this , neighbors = map (answer!) (findNeighbors pos vs) } </code></pre> <p>And that's it, really. Now you can write your</p> <pre><code>newPositions :: Point -&gt; [Vector] newPositions = undefined </code></pre> <p>where <code>newPositions</code> is perfectly free to inspect any of the fields of the <code>Point</code> it's handed, and put all the functions together:</p> <pre><code>update :: [Vector] -&gt; [Vector] update = newPositions &lt;=&lt; elems . buildGraph </code></pre> <p>edit: ...to explain the "special kind of mutation" comment at the beginning: during evaluation, you can expect when you demand the <code>w</code> field of a <code>Point</code> that things will happen in this order: <code>computeW</code> will force the <code>v</code> field; then <code>computeV</code> will force the <code>neighbors</code> field; then the <code>neighbors</code> field will mutate from unevaluated to evaluated; then the <code>v</code> field will mutate from unevaluated to evaluated; then the <code>w</code> field will mutate from unevaluated to evaluated. These last three steps look very similar to the three mutation steps of your C++ algorithm!</p> <p>double edit: I decided I wanted to see this thing run, so I instantiated all the things held abstract above with dummy implementations. I also wanted to see it evaluate things only once, since I wasn't even sure I'd done it right! So I threw in some <code>trace</code> calls. Here's a complete file:</p> <pre><code>import Control.Monad import Data.Array import Debug.Trace announce s (Vector pos) = trace $ "computing " ++ s ++ " for position " ++ show pos data Vector = Vector Double deriving Show data Point = Point { position :: Vector , v, w :: Double , neighbors :: [Point] } findNeighbors :: Vector -&gt; [Vector] -&gt; [Int] findNeighbors (Vector n) vs = [i | (i, Vector n') &lt;- zip [0..] vs, abs (n - n') &lt; 1] computeV, computeW :: Point -&gt; Double computeV (Point pos _ _ neighbors) = sum [n | Point { position = Vector n } &lt;- neighbors] computeW (Point pos v _ neighbors) = sum [v | Point { v = v } &lt;- neighbors] buildGraph :: [Vector] -&gt; Array Int Point buildGraph vs = answer where answer = listArray (0, length vs-1) [point pos | pos &lt;- vs] point pos = this where { this = Point { position = announce "position" pos $ pos , v = announce "v" pos $ computeV this , w = announce "w" pos $ computeW this , neighbors = announce "neighbors" pos $ map (answer!) (findNeighbors pos vs) } } newPositions :: Point -&gt; [Vector] newPositions (Point { position = Vector n, v = v, w = w }) = [Vector (n*v), Vector w] update :: [Vector] -&gt; [Vector] update = newPositions &lt;=&lt; elems . buildGraph </code></pre> <p>and a run in ghci:</p> <pre><code>*Main&gt; length . show . update . map Vector $ [0, 0.25, 0.75, 1.25, 35] computing position for position 0.0 computing v for position 0.0 computing neighbors for position 0.0 computing position for position 0.25 computing position for position 0.75 computing w for position 0.0 computing v for position 0.25 computing neighbors for position 0.25 computing v for position 0.75 computing neighbors for position 0.75 computing position for position 1.25 computing w for position 0.25 computing w for position 0.75 computing v for position 1.25 computing neighbors for position 1.25 computing w for position 1.25 computing position for position 35.0 computing v for position 35.0 computing neighbors for position 35.0 computing w for position 35.0 123 </code></pre> <p>As you can see, each field is computed at most once for each position.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload