Translating an imperative algorithm - negascout

Hello Haskellers, 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. Any comments on my effort here, are welcome: module Move (negascout ) where {-# contract negascout Ok -> {depth | depth >= 0} -> Ok -> Ok -> Ok #-} negascout :: Node -> Int -> Int -> Int -> Int negascout node depth alpha beta = case depth == 0 || is_terminal node of True -> evaluate node False -> let child:rest = children node b = beta -- initial window is (-beta, -alpha) in negascout' child (depth-1) (- b) (- alpha) beta rest -- Implementation {-# contract negascout' Ok -> {depth | depth >= 0} -> Ok -> Ok -> Ok -> Ok -> Ok #-} negascout' :: Node -> Int -> Int -> Int -> Int -> [Node] -> Int negascout' node depth beta' alpha beta rest = let a = negate $ negascout child depth beta' alpha in case a > (- alpha) of True -> let alpha' = a in case alpha' >= beta of True -> alpha' -- beta cut-off False -> case alpha' >= (- beta') of -- null window failed high? True -> let alpha'' = negate $ negascout child depth (- beta) (- alpha') -- full re-search in case alpha'' >= beta of True -> alpha'' -- beta cut-off False -> case rest of [] -> alpha'' child':rest' -> let b' = alpha'' + 1 in negascout' child' depth (- b') alpha'' beta rest' False -> case rest of [] -> alpha' child':rest' -> let b' = alpha' + 1 in negascout' child' depth (- b') alpha' beta rest' False -> case rest of [] -> alpha child':rest' -> let b' = alpha + 1 in negascout' child' depth (- b') alpha beta rest' -- Colin Adams Preston Lancashire

On Fri, Feb 27, 2009 at 7:54 AM, Colin Paul Adams
Hello Haskellers,
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.
In my opinion keeping the code looking vaguely like the pseudo-code is a good idea for when you revisit it weeks later and try to remember what the hell you were originally doing. But I don't want to try and refactor the code you've provided without some tests to ensure everything is correct. Rule number one for refactoring. :) But for starters I imagine it's far more readable to covert all the 'case boolExpr of' expressions to 'if then else'. Secondly there's a lot of repeated functionality in there that should be abstracted to smaller functions, which I think could still be done without sacrificing the correlation to the algorithm pseudocode. There's 3 parts which check if alpha is greater than beta which could be abstracted and there's the check if rest is empty else recurse which could be abstracted. negascout' would be far more readable if you did this as long as you gave the helper functions good names. :) Sorry to be so vague though. If you have some data and tests then I can help you out some more if you like.
Any comments on my effort here, are welcome:
module Move (negascout ) where
{-# contract negascout Ok -> {depth | depth >= 0} -> Ok -> Ok -> Ok #-} negascout :: Node -> Int -> Int -> Int -> Int negascout node depth alpha beta = case depth == 0 || is_terminal node of True -> evaluate node False -> let child:rest = children node b = beta -- initial window is (-beta, -alpha) in negascout' child (depth-1) (- b) (- alpha) beta rest
-- Implementation
{-# contract negascout' Ok -> {depth | depth >= 0} -> Ok -> Ok -> Ok -> Ok -> Ok #-} negascout' :: Node -> Int -> Int -> Int -> Int -> [Node] -> Int negascout' node depth beta' alpha beta rest = let a = negate $ negascout child depth beta' alpha in case a > (- alpha) of True -> let alpha' = a in case alpha' >= beta of True -> alpha' -- beta cut-off False -> case alpha' >= (- beta') of -- null window failed high? True -> let alpha'' = negate $ negascout child depth (- beta) (- alpha') -- full re-search in case alpha'' >= beta of True -> alpha'' -- beta cut-off False -> case rest of [] -> alpha'' child':rest' -> let b' = alpha'' + 1 in negascout' child' depth (- b') alpha'' beta rest' False -> case rest of [] -> alpha' child':rest' -> let b' = alpha' + 1 in negascout' child' depth (- b') alpha' beta rest' False -> case rest of [] -> alpha child':rest' -> let b' = alpha + 1 in negascout' child' depth (- b') alpha beta rest'
-- Colin Adams Preston Lancashire _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

"Toby" == Toby Hutton
writes:
Toby> But I don't want to try and refactor the code you've Toby> provided without some tests to ensure everything is correct. Toby> Rule number one for refactoring. :) A very good reminder. I'll write some simple (data, that is) test cases - I intend to package it as a small library anyway - with type classes so I can plug-in different search algorithms, and it's time I got to grips with HUnit and QuickCheck. Anton> It's worth keeping in mind the lesson of the lambda Anton> calculus: that a local variable is just a Anton> function in disguise. I'd never thought about that before. Thanks to both of you for your suggestions. Most helpful. -- Colin Adams Preston Lancashire

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

Am Donnerstag, den 26.02.2009, 20:54 +0000 schrieb Colin Paul Adams:
Hello Haskellers,
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.
Any comments on my effort here, are welcome:
A more systematic transformation may lead to a more efficient loop: In the pseudocode, I have replaced the first conditional with the max function and pulled the calculation of (depth - 1) out of the loop: function negascout(node, depth, alpha, beta) if node is a terminal node or depth = 0 return the heuristic value of node b := beta d := depth - 1 foreach child of node alpha := max(alpha, -negascout (child, d, -b, -alpha)) if alpha >= beta return alpha if alpha >= b alpha := -negascout(child, d, -beta, -alpha) if alpha >= beta return alpha b := alpha+1 return alpha In order to make this more Haskell-ish, we assume three self-explaining functions:
terminal :: Node -> Bool heuristicValue :: Node -> Int children :: Node -> [a]
(Idiomatic) Haskell does not use mutable state or conditionals without else-branch, so we make three modifications: 1) We use pattern matching for the first conditional 2) Every other conditional if c then a rest is rewritten as if c then do a rest else rest and do return x rest is abbreviated as return x (Here, 'return' is the imperative return statement, not the monadic unit) 3) We bring the program into single static assignment form. That is, we introduce new variables, so that every variable is assigned to only once. This is not always possible, because the loop would require us to introduce an unknown number of variables. Therefore, we allow reassignments at the end of an iteration. And there it is, written in Haskell-like pseudocode:
negascout :: Node -> Int -> Int -> Int -> Int negascout node depth alpha beta | terminal node || depth == 0 = heuristicValue node | otherwise = do b := beta d := depth - 1 foreach child of node do alpha' := max(alpha, - negascout child d (-b) (-alpha)) if alpha' >= beta then return alpha' else if alpha' >= b then do alpha'' := - negascout child d (-beta) (-alpha') if alpha'' >= beta then return alpha'' else do alpha := alpha'' b := alpha'' + 1 else do alpha := alpha' b := alpha' + 1 return alpha
Now we see that only alpha and b are modified by the loop. From that it follows how the loop can be turned into a recursive function: This function takes alpha, b and the list of remaining children as its argument:
negascout node depth alpha beta | terminal node || depth == 0 = heuristicValue node | otherwise = loop alpha beta (children node) where d = depth - 1 loop alpha b (c:cs) = let alpha' = max(alpha, - negascout c d (-b) (-alpha)) in if alpha' >= beta then alpha' else if alpha' >= b then let alpha'' = - negascout c d (-beta) (-alpha') in if alpha'' >= beta then alpha'' else loop alpha'' (alpha'' + 1) cs else loop alpha' (alpha' + 1) cs loop alpha _ [] = alpha
Now you can move around the declarations, introduce some nice guards, optimize the calls where b==alpha+1 or beta==alpha+1 and hunt for hidden folds. (Warning: I didn't test it)

Holger Siegel wrote:
loop alpha b (c:cs) = let alpha' = max(alpha, - negascout c d (-b) (-alpha)) in if alpha' >= beta then alpha' else if alpha' >= b then let alpha'' = - negascout c d (-beta) (-alpha') in if alpha'' >= beta then alpha'' else loop alpha'' (alpha'' + 1) cs else loop alpha' (alpha' + 1) cs loop alpha _ [] = alpha
Guard-izing and where-izing that for more clarity: > loop alpha _ [] = alpha > loop alpha b (c:cs) = result > where > alpha' = max(alpha, - negascout c d (-b) (-alpha)) > result > | alpha' >= beta = alpha' > | alpha' >= b = result' > | otherwise = loop alpha' (alpha' + 1) cs > where > result' > | alpha'' >= beta = alpha'' > | otherwise = loop alpha'' (alpha'' + 1) cs > where > alpha'' = - negascout c d (-beta) (-alpha') Which makes it obvious that the "result" function is something inlined into itself. Uninlining may make the code clearer still, or maybe not. -- Live well, ~wren
participants (5)
-
Anton van Straaten
-
Colin Paul Adams
-
Holger Siegel
-
Toby Hutton
-
wren ng thornton