Search code examples
haskelltemplate-haskell

Haskell: I think I could really use Lisp-like macros here


The AI code for my little soccer game basically works like this: There is a function that derives facts that describe the current situation on the pitch:

deriveFacts :: GameState -> [Fact]

... where the facts look kind of like this:

data Fact =
    FactCanIntercept ObjId ObjId
  | FactBestPosition Team Spot Spot
  | FactKickOff
  | FactBallCarrier ObjId
  | FactBestShootingVector Velocity3
  | FactBestPassingVector Velocity3
  | ...

... and there are rules that look uniformly like this:

rule_shoot facts = do
    FactBallCarrier ballCarrier <- checkBallCarrier facts
    FactBestShootingVector goalVector <- checkBestShootingVector facts
    return [message (ballCarrier, shoot goalVector)]

And then there is a rule engine that runs all the rules and gathers up the resulting messages.

That's fine so far and works quite nicely. What's kind of annoying, though: In this approach, I need an "accessor" function like this for every Fact:

checkBestShootingVector :: [Fact] -> Maybe Fact
checkBestShootingVector facts = 
   listToMaybe [e | e@(FactBestShootingVector {}) <- facts]

This approach leads to a lot of ugly boilerplate code. My question: Is there an elegant solution that removes the need for creating these accessor functions by hand?


Solution

  • You could refactor most of your Fact data as a record object

    data FactRec = FR {
       canIntercept :: [(ObjId,ObjId)], -- use lists for things you previously had multiple times
       factBestPosition :: [(Team,Spot,Spot)], -- not sure about this one, maybe two entries not one list 
       kickOff :: Bool,
       ballCarrier :: ObjID,
       factBestShootingVector :: Velocity3, 
       factBestPassingVector :: Velocity3,
       .....
       }
    

    Which gives you bulti-in accessor functions

    if kickOff fr then something else somethingelse
    

    So you wouldn't need to write all the check functions, but instead do

    rule_shoot facts = message (ballCarrier facts, shoot $ goalVector facts)
    

    If there are facts that genuinely might or might not be there, they could be of type Maybe something, and if there are facts that can be there an arbitrary number of times, they can be lists.