/*
 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 Sample.scala'
 To run the example, type 'scala Sample'
 
 This is "Step 1" in David Pollak's Introducing Scala series
 
 Check out http://blog.lostlake.org for the latest rants and Scala stuff 
 from David Pollak
*/
 
// In Scala, comments can be single line with //
 
/*
Or they can be multi-line 
like
this.
 
It's like Java and C++
 
Scala is like Ruby in that ';' line endings are optional.
If a statement logically ends at the end of a line, there's no need
for ';'.  However, you can put multiple statements on a line and
separate them with ';'
*/
 
// we use HashMap to store our items
import scala.collection.mutable.HashMap 
 
// we need some classes from the XML package
// Note that multiple classes can be imported
// by enclosing them in curly braces
import scala.xml.{Node, NodeSeq, Elem, Text} 
 
/*
define a holder for items
case classes can do certain magic in Scala.  However, they are also good 
short-hand for classes that have pre-defined "toString" "hashValue" and
other methods.
 
The class defines "constructor" parameters that become read-only "attributes"
on the class.
 
Because it's non-obvious what the parameter types are, you have to explicitly
define them
*/
case class Item(name: String, pn: String, price: double) {
  // an internal variable (it can change, note that there's no need to
  // specify its type) 
  private var iqnty = 0 
 
  def qnty = iqnty // an attribute for reading current quantity of the item
 
  // add to the quantity, return the total quantity (note again, the 
  // parameter type has to be declared, but the return type is inferred
  def add(toAdd: int) = { 
    iqnty = iqnty + toAdd // increment inventory, no += in Scala
    qnty // return the current quantity
  }
 
  // Take as many units from the item as are available
  // return the amount taken and the remaining inventory
  def take(howMany: int) = {
    // we can't cause inventory to go negative
    // This could also be written as Math.max(howMany, qnty)
    // note that 'if' returns a value.  This
    // substitutes for Java's bool ? v1 : v2
    val toTake = if (howMany > qnty) qnty else howMany 
 
    // Q: Why not iqnty = iqnty - toTake ? Why are we using qnty here vs. add()?
    iqnty = qnty - toTake // decrement inventory
 
    // return the number "taken" and the remaining inventory
    // In Scala 2.3.2 and future, this could be written as
    // {toTake, qnty}
    Pair(toTake, qnty) 
  }
 
  /*
   convert this item to XML
   Scala allows you to embed XML right in the language
   This creates an XML node
   Shorthand for:
   new Elem(
         "",
         "item",
         new UnprefixedAttribute("name", name) ++
         new UnprefixedAttribute("pn", pn) ++
         new UnprefixedAttribute("price", price.toString) ++
	 new UnprefixedAttribute("qnty", qnty.toString),
	 null)
  */
  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 Sample extends Application {
 
  // create mock XML inventory updates
  // note that XML is part of the syntax of Scala
  val inv1 = <update>
  <item pn='a' qnty='25' />
  </update>
 
  val inv2 = <update>
  <item pn='o' qnty='45' />
  <item pn='b' qnty='4' />
  </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))
  
}
 
 
// The singleton "inventory server"
object Server {
  /*
    create a list of our invetory items
    This is alternate syntax for:
    List(Item("Apple", "a", 0.25), Item("Banana"...), ...)
  */
  val itemList = Item("Apple", "a", 0.25) ::
    Item("Banana", "b", 0.40) :: 
    Item("Orange", "o", 0.15) :: Nil
 
  // for fast lookup, we put it into a hash with the
  // part number as the key (note that the type of the hash is
  // determined by the constructor)
  val items = new HashMap[String, Item]
 
  // for each of the items in the list, add them to the hash
  itemList.foreach {i => items(i.pn) = i}
 
  def update(in: Elem) = {
    eachItem(in) { // call a function that will iterate over the items and
                   // run the code block (aka anonymous function)
                   // for each XML <item>
                   // Anonymous functions automatically become closures if
                   // there are any "free" variables in the enclosing function
      (e, pn, qnty) => // we get passed the <item> element, 
                       // the pn field and the qnty field
	items.get(pn) match { // look up the inventory item
          case None => Text("") // if we don't have that part number, ignore it (we could throw an exception)
          case Some(item) => <tmp>{item.add(qnty).toString}</tmp>
	}
    }
    currentInventory // return the current inventory
  }
 
  // place an order and return an <order>...</order> XML block
  def order(in: Elem) = {
    <order>
    {
      eachItem(in) { // find all the items
        (e, pn, qnty) => 
          items.get(pn) match { // get the part
            case None => <not_found pn={pn}/> // if there's no matching part, 
                                              // generate a <not_found/> tag
            case Some(item) => {
              val took = item.take(qnty) // take the qnty from the item
	      // generate the XML tag
              <shipped pn={pn} ordered={qnty.toString} 
                  shipped={took._1.toString} 
                  cost={(item.price * took._1).toString}/> 
            }
          }
      }
    }
    </order>
  }
 
  // This is perhaps the toughest piece of code in the example.
  // This function takes an XML element and a "function" as parameters.
  // It "maps" the XML result of applying a function to each element into a
  // sequence of XML objects.
  // The function 'f' is declared as a function that takes and "Elem" 
  // (XML element) a String (part number) and an int (qnty) and returning an 
  // XML Node
  private def eachItem(in: Elem)(f: (Node, String, int) => Node) : 
    NodeSeq = {
    in.child.map { // for each node
      node =>
	node match { // that has an item tag and certain attributes, 
                     // call the passed function
 
          // 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("")} 
	}
    }
  }
 
  // generate the XML of the current inventory
  def currentInventory = {
    // the inventory value is the sum of price * qnty
    val invValue = items.values.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}>{
      // only show items that we have inventory for
      items.values.filter{i => i.qnty > 0}. 
                   // convert to XML and prepend some pretty-printing
                   map {i => Text("\n     ") concat i.toXml}. 
                   toList // convert to a list -- makes for better XML
    }
    </inventory>
  }
 
}
 
/* (c) 2007 WorldWide Conferencing, LLC.  
    Distributed under an Apache V2.0 licnese 
    Version 1.1 */
 
 
 
code/step-1.txt · Last modified: 2007/11/24 17:38 by babo
 
Recent changes RSS feed Valid XHTML 1.0 Driven by DokuWiki