[Haskell-cafe] Translating an imperative algorithm - negascout

Anton van Straaten anton at appsolutions.com
Thu Feb 26 23:19:01 EST 2009


Colin Paul Adams wrote:
> I want to implement the negascout algorithm for the game I'm writing.
> 
> Wikipedia gives the algorithm in imperative terms:
> 
> http://en.wikipedia.org/wiki/Negascout
> 
> I've tried to translate this into Haskell. I'm not sure if I'm going
> about it the right way, and if I am, if I've done it correctly.

Variable names like alpha'' can be seen as an indication that a function 
is too big and/or monolithic.  It's worth keeping in mind the lesson of 
the lambda calculus: that a local variable is just a function in disguise.

To exploit that, you can replace definitions and uses of variables like 
alpha' and alpha'' with calls to functions.  Wherever you have something 
like:

   let alpha' = <new value for alpha>
   in <body>

...try changing it to something like:

   foo <new value for alpha>
   ...
   where foo alpha = <body>

Instead of a monolithic deeply-nested conditional expression, this will 
naturally lead to a set of small functions which call other small 
functions.  As Toby Hutton said, you should also factor out any 
repetition into functions.

One advantage of defining small functions like this is that you can also 
take advantage of guard syntax, which used appropriately in Haskell can 
be cleaner and more readable than either 'case' or 'if'.

For example, to deal with the following pseudocode:

   a := -negascout (child, depth-1, -b, -alpha)
   if a > alpha
      alpha := a

You could write a function such as:

   newAlpha a | a > alpha = a
	     | otherwise = alpha

Note this function depends on 'alpha'.  To avoid having to pass extra 
parameters around all over the place, simply define this function using 
a 'where' clause, at the appropriate scope level so that the 'alpha' it 
needs is in scope.  In your example, it would probably be defined in 
your negascout' function, something like this:

   negascout' node depth beta' alpha beta rest =
     ...
     where
       newAlpha a | a > alpha = a
	         | otherwise = alpha

Note that newAlpha needs to be called with the result of a call to 
negascout.  While we're about it, notice that both of the recursive 
calls to negascout use the same first two arguments, so just for 
readability, we can define this:

   search a b = -negascout child (depth-1) (-b) (-a)

The pseudocode above can now be (mostly) replaced with a call 
'newAlpha', as follows:

   newAlpha (search alpha b)

However, this doesn't deal with what happens to the result of the call 
to newAlpha.  In the imperative pseudocode, alpha is mutated.  For a 
functional version, you can just repeat the above strategy: define a 
function which takes an argument alpha, and call that function with the 
new value for alpha, i.e. the result of the call to newAlpha, so 
something like:

   foo (newAlpha $ search alpha b)

The foo function will implement the remainder of the body of the foreach 
loop from the pseudocode.  (Coming up with a better name than 'foo' is 
left as an exercise...)

I tried this and the result was 13-16 lines, depending how you count, of 
code that is of similarly high level to the original pseudocode.

Anton



More information about the Haskell-Cafe mailing list