[[code:gps]]
 

Lisp code for comparison by Peter Norvig here.

package gps
object gps2 {
  import Utilities._ // debug stuff and port of Lisp "some" function
  
  /** a condition that holds or does not hold
   *  In the Lisp version there are two types of conditions: regular and executing.
   *
   *  Regular conditions are assumed to be atoms (i.e. not lists, usually symbols)
   *  while executing conditions are list of the form ('Executing 'operator-name)
   *
   *  I personally find the convention quite confusing and it doesn't mix well
   *  at all with static typing in Scala.  One solution would be to make them both lists
   *  of Symbols and then pattern match cond :: Nil to be
   *  a regular condition and 'Executing :: op :: Nil to be an executing
   *  condition.  Scala could support this very nicely (assuming ops are symbols).
   *
   *  I don't particularly like that solution either, for a variety of reasons.
   *  So instead, I make Condition a trait and use case classes to
   *  represent normal conditions and executing conditions.  This free the programmer
   *  from remembering patterns and allows the compiler to check for correctness and
   *  fall-through conditions.
   */
  abstract class Condition {
     val name: String
     final override def toString = name
   }
  /** factory object that constructs the basic implementation of condition*/
  object Condition {
    /** make a condition
     *  @param name the name of the Condition
     *  @return a Condition with the specified name
     */
    def apply(name: String) = BasicCondition(name)
  }
  
  /** a normal condition with a specified name */
  case class BasicCondition(val name: String) extends Condition
  /** A condition that represents the the execution of an operator */
  case class ExecutedOpCondition(val op: Operator) extends Condition {
    /** the name is derived from the name of the operator */
    val name = "Executing: " + op.name
  }
 
  /** an action that can be performed to achieve goals
   *  corresponds to the op struct in the Lisp version
   *  some functions related to the operators have been moved directly
   *  into the class
   */
  case class Operator (val name: String, 
                       val preconditions: List[Condition],
                       val baseAdd: List[Condition],
                       val del: List[Condition]) { 
    /** the condition to be added to the state when this operator is executed */
    val executedCondition = ExecutedOpCondition(this) 
    /** the add list for this operator, including the executed condition */
    val add = baseAdd ::: executedCondition :: Nil
    /** test whether this operator is appropriate to achieve the specified goal
     *  @param goal the desired goal
     *  @return true if this op will achieve the goal, false if not
     */
    def appropriate(goal: Condition) = add contains goal 
    override def toString = name
  }
  /** Operator representing the first step  of a solution */
  val Start = Operator("Start", Nil, Nil, Nil)
 
  /** enables typed-checked expression of preconditions using loose types in DSL
   *  Huh?  Type-checked use of loose types?
   *  The idea is to make the DSL interpretter-friendly.  Typing things like
   *  Condition("foo") :: Condition("bar") :: Nil takes a while, and is not
   *  visually different from expressing the addList, delList, etc.
   *  
   *  So this class, along with addList and delList, force preconds to be used
   *  in the proper place when defining an Operator while allowing the user
   *  to enter strings, symbols, Conditions, actual conditions, or anything
   *  else that hopefully is easy to type.
   */
  case class preconds(val conds: AnyRef*)
  /** enables typed-checked expression of the addList in DSL*/
  case class addList(val conds: AnyRef*)
  /** enables typed-checked expression of delList in DSL*/
  case class delList(val conds: AnyRef*)
  
  /** utility function for converty a bunch of AnyRefs into a list of Conditions */
  private def mcl(values: Seq[AnyRef]): List[Condition] = {
    values.toList.map((e) => e match {
      case e: Condition => e
      case _ => Condition(e.toString)
    })
  }
  
  /** utility function for specifying Operators in the DSL
    * @param name the name of the Operator
    * @param preconds the preconditions for the Operator
    * @param addList the addList for the Operator
    * @param delList the delList for the Operator
    * @return an Operator
    */
  def Op(name: String, p: preconds, a: addList, d: delList) = 
    Operator(name, mcl(p.conds), mcl(a.conds), mcl(d.conds))
  /** overloaded version of Op enabling name to be a Symbol */
  def Op(name: Symbol, p: preconds, a: addList, d: delList): Operator =
    Op(name.toString, p, a, d)
  
  /** simulated function for problem-definition DSL
    * an object is used instead of a function because functions
    * with * arguments cannot be partially applied
    * to create aliases used in the DSL 
    */
  object statemaker { 
    def apply(values: AnyRef*): List[Condition] = mcl(values)
  }
  /** condition list factory for DSL to express the initial state*/
  val startState = statemaker
  /** condition list factory for DSL to express the goal state*/
  val goalState = statemaker
 
  private var _ops: List[Operator] = _
  /** convenience implicit def for interactive use so that ops doesn't have
   *  to be repeatedly specified by the user
   *  @return the default of set Operators
   */
  implicit def ops = _ops
  /** set the default set of operators
    * @param operators the new default set of Operators
    * @return the number of operators specified
    */
  def use(operators: List[Operator]): Int = {
    _ops = operators
    _ops.size
  }  
  
  
  /** solve the specified problem
   *  The Lisp version of this function simply returns a list special "executed" conditions,
   *  which are really just lists of symbols that conform to a specific convention.  This funcion
   *  returns the actual Operator objects that must be applied to solve the problem.
   *
   *  I believe returning a list of Operators is substantially more useful,
   *  because the operators are more fit for usage by a calling function.
   *
   *  @param state the current set of conditions
   *  @param goals the set of goal conditions
   *  @return ordered list of operators, starting with Start, on Success; Nil on failure 
   */
  def GPS(state: List[Condition], goals: List[Condition])(implicit ops: List[Operator]): List[Operator] = {
    achieveAll(Start.executedCondition :: state, goals, Nil).flatMap((c) => c match {
      case eoc: ExecutedOpCondition => eoc.op :: Nil
      case _ => Nil
    })
  }
  
  /** achieve all the specified goals
   *  achieveAll works by attempting to achieve each goal until
   *  either one fails or all have succeeded and then return the resulting state
   *
   *  achieveAll is called by GPS with the initial conditions
   *  and state, as well as by applyOp with the preconditions of each
   *  Operator that is attempted.  Note that the invokation by
   *  applyOp means that achieveAll utlimately recurses on itself.
   *
   *  @param state the current list of conditions 
   *  @param goals the set of goal conditions
   *  @param goalStack the set of goals currently being examined
   *  @param ops implicit parameter for the set of available Operators
   *  @return the resulting list of Conditions with embedded operation conditions
   *  @todo failure and a Nil state are not distinguished from one another
   */
  private def achieveAll(state: List[Condition], goals: List[Condition], goalStack: List[Condition])(implicit ops: List[Operator]): List[Condition] = {
    def achieveGoal(state: List[Condition], goal: List[Condition]): List[Condition] = {
      goal match {
        case Nil => state
        case g :: rest => {
          achieve(state, g, goalStack) match {
            case Nil => Nil
            case newState => achieveGoal(newState, rest)
          }
        }
      }
    }
    achieveGoal(state, goals)
     
    /*  Below is a slightly more direct port of the Lisp version which utilizes mutable state
    var currentState = state  //uhoh...mutable state...at least it's hidden in the function
    if (goals.forall((g) => {
      currentState = achieve(currentState, g, goalStack)
      currentState contains g // achieve actually returns Nil so this could be currentState == Nil,
                              // but achieved changed so it no longer returned Nil on failure then
                              // testing for Nil would break this function, while testing for the
                              // goal would still work
    })) currentState else Nil */
  }
  
  /** achieve the specified goal from the state using Operators
   *  achieve works by finding all of the Operators
   *  that can potentially achieve the specified goal, and trying to apply each
   *  one until one succeeds.
   *
   *  achieve recurses on itself because it calls applyOp,
   *  which called achieveAll on the operator's preconditions, which in
   *  turn invokes achieve on each of those preconditions.
   *
   *  @param state the list of current conditions
   *  @param goal the goal condition to be satisfied
   *  @param goalStack the set of goals currently being considered
   *  @param ops implicit get of operators to use to satisfy goal
   *  @return new state if successful, Nil on failure
   */
  private def achieve(state: List[Condition], goal: Condition, goalStack: List[Condition])(implicit ops: List[Operator]): List[Condition] = {
    if (state contains goal) { // goal is already satisfied, return state unchanged
      dbgIndent(goalStack.size, "Goal: " + goal.name + " (already in state)")
      state
    } else if (goalStack contains goal) { // recursive goal encountered, return failure
      dbgIndent(goalStack.size, "Goal: " + goal.name + " (already on stack, failing)")
      Nil
    } else { // goal has yet to be achieved, try operators until one succeeds or all fail
      dbgIndent(goalStack.size, "Goal: " + goal.name)
    	val updatedGoalStack = goal :: goalStack // in the Lisp version this was done in applyOp
      // some is a port of the Lisp some function
      // filter ops down down to operators that are appropriate to those goals
      //   attempt to apply each operator until one succeeds
      //   return the result of applying that operator
    	some(ops.filter((op) => op appropriate goal), applyOp(state, _: Operator, updatedGoalStack))
    }
  }
  
   /** attempt to apply the specified operator to achieve the goal
    *  applyOp attempt to achieveAll the preconditions for the
    *  Operator then applies it.
    *  Applying the Operator consists of removing its delete list from the
    *  state and adding its add list to the state
    *  This method recurses on itself through achieveAll to achieve.  
    *  @param state the current state
    *  @param op the operator to be applied
    *  @param goalStack the current list of goals under consideration
    *  @param ops implicit parameter containing the operators
    *  @return the updated state on success, Nil on failure
    */
  private def applyOp(state: List[Condition], op: Operator, goalStack: List[Condition])(implicit ops: List[Operator]): List[Condition] = {
    dbgIndent(goalStack.size, "Consider: " + op.name)
    achieveAll(state, op.preconditions, goalStack) match {
      case Nil => Nil
      case state2 => {
        dbgIndent(goalStack.size, "Action: " + op.name)
        state2.remove((c) => op.del contains c) ::: op.add
      } // case achieveAll was successful
    } // match on result of achieveAll
  }
} // object gps2
 
object SchoolProblem {
  import gps2._
  val basicStartState = startState("son at home","car needs battery", "have money", "have phone book")
  val basicGoalState = goalState("son at school")
  
  def main(args : Array[String]) : Unit = {
    Console.println("The Get-Son-to-School Problem")
    Console.println("Initial: " + basicStartState.mkString(", "))
    Console.println("Goal: " + basicGoalState.mkString(", "))
    Console.println("Solving...")
    val r = GPS(basicStartState, basicGoalState)(operators)
    Console.println("Done.")
    Console.println("Solution: ")
    Console.println(r.mkString("\n"))
  }
  
  val operators = 
      Op("drive son to school",
            preconds("son at home", "car works"), 
            addList("son at school"),
            delList("son at home")) ::
      Op("shop installs battery",
            preconds("car needs battery", "shop knows problem", "shop has money"),
            addList("car works"),
            delList()) ::
      Op("tell shop problem",
            preconds("in communication with shop"),
            addList("shop knows problem"),
            delList()) ::
      Op("telephone shop",
            preconds("know phone number"),
            addList("in communication with shop"),
            delList()) ::
      Op("ask phone number",
            preconds("in communication with shop"),
            addList("know phone number"),
            delList()) ::
      Op("look up number",
            preconds("have phone book"),
            addList("know phone number"),
            delList()) ::
      Op("give shop money",
            preconds("have money"),
            addList("shop has money"),
            delList("have money")) :: Nil
}
 
object MonkeyProblem {
  import gps2._
  
  val basicStartState = startState("at door", "on floor", "has ball", "hungry", "chair at door")
  val basicGoalState = goalState("not hungry")
  
  def main(args : Array[String]) : Unit = {
    Console.println("The Monkey-and-Bananas Problem")
    Console.println("Initial: " + basicStartState.mkString(", "))
    Console.println("Goal: " + basicGoalState.mkString(", "))
    Console.println("Solving...")
    val r = GPS(basicStartState, basicGoalState)(operators)
    Console.println("Done.")
    Console.println("Solution:")
    Console.println(r.mkString("\n"))
  }
  
  val operators =
    Op("climb on chair",
        preconds("chair at middle of room", "at middle of room", "on floor"),
        addList("at bananas", "on chair"),
        delList("at middle of room", "on floor")) ::
    Op("push chair from door to middle of room",
        preconds("chair at door", "at door"),
        addList("chair at middle of room", "at middle of room"),
        delList("chair at door", "at door")) :: 
    Op("walk from door to middle of room",
        preconds("at door", "on floor"),
        addList("at middle of room"),
        delList("at door")) ::
    Op("grasp bananas",
        preconds("at bananas", "empty handed"),
        addList("has bananas"),
        delList("empty handed")) ::
    Op("drop ball",
        preconds("has ball"),
        addList("empty handed"),
        delList("has ball")) ::
    Op("eat bananas",
        preconds("has bananas"),
        addList("empty handed", "not hungry"),
        delList("has bananas", "hungry")) :: Nil
}
 
object Utilities {
  var dbg = true
  def dbgIndent(depth: Int, msg: String): Unit = {
    if (dbg) {
      def indent(depth: Int): Unit = {
        if (depth <= 0) None
        else {
          Console.print(" ")
          indent(depth - 1)
        }
      }
      indent(depth)
      Console.println(msg)
    } else None
  }
  
  /** emulate the Lisp some function
   *  @param list the list to be processed
   *  @param f the function to be applied to each element
   *  @return the first non-empty list returned by f
  */
  def some[A, B](list: List[A], f: (A) => List[B]): List[B] = {
    list match {
      case first :: rest => f(first) match {
        case Nil => some(rest, f)
        case result => result
      }
      case _ => Nil
    }
  }
}
 
code/gps.txt · Last modified: 2007/08/30 03:25 by eengbrec
 
Recent changes RSS feed Valid XHTML 1.0 Driven by DokuWiki