/*
 So, you've heard about Scala.  It's a cool functional language that compiles
 down to Java byte-code and looks and acts like a Java program.  You've looked
 at some documentation and saw stuff like "higher order functions" and other
 stuff that made Scala seem complex.
 
 Scala is simple.  You can code Scala almost like you code Java or Ruby (in 
 fact, IMHO, Scala is like the best of both languages.)  This example code
 demonstrates a bunch of Scala idioms, but should be readable by Java and Ruby
 developers alike.
 
 You can download Scala from http://scala-lang.org/
 This example was developed with Scala 2.3.1, but should work with any version
 of Scala >= 2.3.1
 
 To compile the example, cd into the directory that you've saved this file and
 type 'fsc Sample2.scala'
 To run the example, type 'scala Sample2'
 
 This is "Step 2" in David Pollak's Introducing Scala series
 
 This sample code introduces threadless concurrency (Actors) and
 using immutable data types (a staple of functional programming)
 
 Check out http://blog.lostlake.org for the latest rants and Scala stuff 
 from David Pollak
*/
 
// we use an immutable TreeMap to store our items
import scala.collection.immutable.TreeMap 
import scala.xml.{Node, NodeSeq, Elem, Text}
 
// We use actors to manage our inventory
// to allow for concurrency and remote access
import scala.actors.Actor
import scala.actors.Actor._
 
 
// define a holder for items
// It differs from Step 1's Item in that this implementation is non-mutable.
// It's like a Java string, once you create it
// you cannot change it.  To get a new one, you make a copy.
case class Item(name: String, pn: String, price: double, qnty: int) {
  // add to the quantity, return a new instance 
  // (like "123".substring(2) == "3")
  def add(toAdd: int) = { 
    itemWithQnty(qnty + toAdd) // create a new, immutable instance
  }
 
  // take a certain number out of the item
  // return the number that was taken and the new item
  def take(howMany: int) = {
    val toTake = Math.max(howMany, qnty)
    // return the number "taken" and the rebuilt item
    Pair(toTake, itemWithQnty(qnty - toTake)) 
  }
 
  // a private function that creates a new Item with the new quantity
  private def itemWithQnty(newQnty: int) = Item(name, pn, price, newQnty)
 
  // convert this item to XML
  def toXml = <item name={name} pn={pn} price={price.toString}
               qnty={qnty.toString} />
}
 
// an "object" (singleton) that extends the "Application"
// class will get run like a Java class with public static void main(String[])
object Sample2 extends Application {
 
  // create mock XML inventory updates
  val inv1 = <update>
  <item pn='a' qnty='25' name='Apple' price='0.25' />
  </update>
 
  val inv2 = <update>
  <item pn='o' qnty='45' name="Orange" price='0.4' />
  <item pn='b' qnty='4' name="Banana" price="0.15" />
  </update>
 
  // Send them to the server
  Console.println(Server.update(inv1))
  Console.println(Server.update(inv2))
 
  // create a mock XML purchase order (note the embedded XML)
  val purchase = <order>
  <item pn='a' qnty='20'/>
  <item pn='b' qnty='10'/>
  <item pn='o' qnty='35'/>
  <item pn='na' qnty='23'/>
  </order>
 
  // send the XML to the server and print the response
  Console.println(Server.order(purchase))
 
  // shut down the server so we can exit gracefully
  Server.shutdown
}
 
/*
 The singleton "inventory server"
 Implemented as an "Actor"
 Actors are "share nothing" constructs that receive messages and
 process them one by one.  Actors provide a concurrency model
 that (1) is deadlock free (or at least deadlock reduced) (2)
 has been proven in Erlang over 10+ years and (3) does not
 require "synchronizes" or other keywords
*/
object Server {
  // the server actor.
  // vals are created the first time they are used... singletons
  private val server = {
    val ret = new SafeServer
    ret.start
    ret
  }
 
  // shut the server down by sending it "exit"
  def shutdown = server ! "exit"
 
  def update(in: Elem) = {
    eachItem(in) { 
      (e, pn, qnty) => 
	// send an update message to our server
	server ! Update(pn, e.attribute("name").get.text, 
			java.lang.Double.parseDouble(e.attribute("price").
						     get.text), qnty)
      // no reason to return anything meaningful because we're not using
      // the XML return block
      Text("") 
    }
    currentInventory // return the current inventory
  }
 
  // place an order and return an <order>...</order> XML block
  def order(in: Elem) = {
    <order>
    {
      eachItem(in) { // for each item
        (e, pn, qnty) => 
	  // send the server an "Order" and wait for the response
          server !? Order(pn, qnty) match { 
 
	    // if there's no matching part, generate a <not_found/> tag
            case None => <not_found pn={pn}/> 
 
            case s: Some[Pair[int, Item]] => {// deconstruct the return value
              val item = s.get._2  // the item
	      val took = s.get._1 // the number of items taken
 
	      // generate the XML tag
              <shipped pn={pn} ordered={qnty.toString} shipped={took.toString} 
                  cost={(item.price * took).toString}/> 
            }
          }
      }
    }
    </order>
  }
 
  // iterate over each "item" node and call f
  private def eachItem(in: Elem)(f : (Node, String, int) => Node): NodeSeq = {
    in.child.map { // for each node
      node =>
	node match { 
	  // we've got an <item> tag and an 'pn' attribute
          case n @ <item/> if (!n.attribute("pn").isEmpty &&  
			       // and a 'qnty' attribute
                               !n.attribute("qnty").isEmpty) =>  
				  // call the function
				 f(n, n.attribute("pn").get.text, 
				   Integer.parseInt(n.attribute("qnty").get.
						    text))
 
	   // if there's no match, return a "no op" Node
          case _ => Text("")
	}
    }
  }
  
  // get a list of items from the server
  private def items: Iterator[Item] = {
    /*
     send an "items" message (note that you can pass Any object
     to an actor server and the server can response with Any object.
     This is a lot like "duck typing" or dynamic typing.  Actors
     either respond to the message or ignore it.
    */
    server !? "items" match {
      // if the response is an Iterotor[Item] return it
      case i: Iterator[Item] => i 
 
      // otherwise, return a zero length iterator
      case _ => new Array[Item](0).elements
    }
  }
 
  // generate the XML of the current inventory
  def currentInventory = {
    // save the queried inventory list 
    val inventory = items.toList
    // the inventory value is the sum of price * qnty
    val invValue = inventory.foldRight(0.0){(i,sum) => sum + i.price * i.qnty} 
    
    // return the XML including the inventory value and the XML of each node
    <inventory value={invValue.toString}>{
      inventory.filter{i => i.qnty > 0}. 
                   map {i => Text("\n     ") concat i.toXml}.
                   toList 
    }
    </inventory>
  }
}
 
/*
 Scala Actors are stand-alone units of computation.  They get messages
 sent to them and they do something (or nothing) with the messages.
 They may send an asynchronous response, or not.
 
 Erlang has a time proven, very successful model of distrubuted computing
 based on Actors.
 
 You can read more about Actors in 'Event-Based Programming without Inversion
 of Control' @ http://lampwww.epfl.ch/~odersky/papers/jmlc06.pdf
*/
class SafeServer extends Actor {
  // start the message receive loop
  // with an empty map of our inventory
  def act = loop(new TreeMap[String, Item])
 
  // this function continues to receive
  // messages and processes the messages
  def loop(info: TreeMap[String, Item]) {
 
    // receive a message that matches 
    // one of the cases
    receive {
 
      // if it's "items" reply to the sender with
      // the values of our inventory and then loop
      case "items" => {reply(info.values) ; loop(info)}
 
      // exit from listening if we get "exit"
      case "exit" => exit("done")
 
      // If the message is an "Update" object
      // process the update without responding
      // to the sender
      case Update(pn, name, price, qnty) =>
	{
	  /* If the Item is in our inventory,
	     update the quantity (remember that 
	     Item is immutable, so an inventory
	     update creates a new instance of Item.
	   
	     If the Item is not in inventory, create
             a new one */
	  val tmpItem = info.get(pn) match {
	    case None => Item(name, pn, price, qnty)
	    case Some(item) => item.add(qnty)
	  }
 
	  // loop back on ourself with an updated inventory
	  // tree
	  loop(info.update(tmpItem.pn, tmpItem))
	}
 
      // Process an "order" which requires a reply
      case Order(pn, qnty) =>
	{
	  // create a new info tree based on
	  // matching the inventory level
	  val tmpInfo = info.get(pn) match {
	    // the part number is not found, respond with
	    // None and the info tree does not change
	    case None => {reply( None); info}
 
	    // we've got a match
	    case Some(item) => {
	      // update the item
	      val res = item.take(qnty)
	      // reply
	      reply( Some(res)) 
	      // update the info tree
	      info.update(res._2.pn, res._2)
	    }
	  }
	  // continue looping 
	  loop(tmpInfo)
	}
    }
  }
}
 
// an order has a part number and a quantity
case class Order(pn: String, qnty: int)
 
// update has more
case class Update(pn: String, name: String, price: double, qnty: int)
 
/* (c) 2007 WorldWide Conferencing, LLC.  
    Distributed under an Apache V2.0 licnese 
    Version 1.0 */
 
 
code/step-2.txt · Last modified: 2010/02/11 09:10
 
Recent changes RSS feed Valid XHTML 1.0 Driven by DokuWiki