Nice... just too many for loops for my liking ;).
Note the many times that you used

1
2
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do 

To get totally immersed in the functional style it would be much better to abstract that out into a higher-order function.
let iter_matrix f = Array.iter (Array.iter f);;
let iteri_matrix f = Array.iteri (fun i -> Array.iteri (f i));;
As an aside Array.create_matrix produces something of type 'a array array which doesn't appear compatable with 'a [,] ... otherwise we could have used the Array2 module functions iter and iteri.
Note the use of partial application. I come from the Haskell side of things ;).
In module Prisoners...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let makeGrid size payoff =
  let dummyCell = { x=0; y=0;
neighbours=[| |]; score=0; plan=alwaysDefect; first=true;
last=Cooperate; temp=Cooperate } in
  let grid =
    { size = size;
      cells = Array.create_matrix size size dummyCell; changed=[]; pay=payoff } in
  for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j) <- { x=i; y=j; neighbours=[| |];
        score=0; plan=alwaysDefect; first=true; last=Cooperate; temp=Cooperate }
    done;
  done;
  for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j).neighbours <- Array.of_list (map (getCell grid) (neighbours size (i,j)))
    done;
  done;
  grid

becomes...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let makeGrid size payoff =
  let dummyCell = { x=0; y=0;
                    neighbours=[| |]; score=0; 
                    plan=alwaysDefect; first=true;
                    last=Cooperate; temp=Cooperate } in
  let grid = { size = size;
               cells = Array.create_matrix size size dummyCell;
               changed=[]; pay=payoff } in
  iteri_matrix (fun i j _ ->
                 grid.cells.(i).(j) <- { x=i; y=j; neighbours=[| |];
                                         score=0; plan=alwaysDefect; 
                                         first=true; last=Cooperate; temp=Cooperate }) 
               grid.cells;
  iteri_matrix (fun i j e ->
                 e.neighbours <- Array.of_list (map (getCell grid) (neighbours size (i,j))))
               grid.cells;
  grid

There is some improvement here.. but minimal
However the resetGrid function can become more succinct...

1
2
3
4
5
6
7
let resetGrid grid =
  grid.changed <- [];
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      (getCell grid (i,j)).score <- 0
    done;
  done

becomes...

1
2
3
let resetGrid grid =
  grid.changes <- [];
  iter_matrix (fun x -> x.score <- 0) grid.cells

You get the idea...
the funtion step is in the most need of using iter_matrix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let step grid =
  resetGrid grid;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          me.score <- me.score + fst (cellPayoff (grid, me, nb))
      done;
    done;
  done;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
        me.last <- me.temp
    done;
  done;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      let max = ref (-1, -1) in
      let score = ref me.score in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          if nb.score > !score then
            begin
              score := nb.score;
              max := (nb.x, nb.y)
            end
      done;
      if fst !max >= 0 then
        let c = (getCell grid !max) in
          me.plan <- c.plan;
          grid.changed <- { coordinate = (i,j); value = me.plan }::grid.changed
    done;
  done;
  grid.changed

we can get rid of all those nasty for loops and also make it a bit more purely function by turning the inner most loop on the 3rd group of for loops, into a fold_left. None of those references to update in the loop. Yet all of the performance with tail recursion.
Here is where we really save on typing and visual complexity.
Personally, not that big a fan of C# / Java style languages. No higher-order functions!!
I feel it's much clearer to see what function is being applied to each element... rather than having to construct all of the looping and accessor code each time!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let step grid =
  resetGrid grid;
  iter_matrix (fun cell -> Array.iter 
                             (fun nb -> cell.score <- cell.score + 
                                                      fst (cellPayoff (grid, cell nb)))
                             cell.neighbours)
              grid.cells;
  iter_matrix (fun cell -> cell.last <- cell.temp) grid.cells;
  iteri_matrix 
    (fun i j cell ->
       let ((mi,mj),_) = Array.fold_left
                           (fun (max, score) nb ->
                              if nb.score > score 
                              then ((nb.x, nb.y), nb.score) 
                              else (max, score))
                           ((-1,-1), cell.score)
                           cell.neighbours 
       in
         if mi >= 0 then
           let c = (getCell grid (mi,mj)) in
             cell.plan <- c.plan;
             grid.changed <- { coordinate = (i,j); value = cell.plan }::grid.changed)
    grid.cells;
  grid.changed

Disclaimer... It's too late in the evening to integrate these changes into the original source and typecheck/debug them. They're correct enough and supplied for visual enjoyment (yes functional programming is just that good).

By on 5/6/2006 4:57 PM ()

Any idea how do we get this article to stay up on the home page [link:cs.hubfs.net?]

Thanks

Don

By on 3/31/2006 2:54 PM ()

An adaption of the ConcurrentLife code I had not anticipated!

This is an absolutely brilliant article. Perhaps a shade too long? Maybe add fun section headings that indicate what new language features are being learnt, e.g. 'Tuples Galore', 'Signatures (think 'Interface!)' or something - this will help people come back to the right place in the article when they try to apply what they learnt?

The formatting toward the end looks a little out. And perhaps OS and co could add 'and' to the list of colorized keywords?

Also, you mention 'let ... and ...' has undefined evaluation order - in reality it is defined to be left-to-right, though it would be exceptionally bad taste to rely on that :-)

By on 3/23/2006 3:31 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper