/* 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 */