Monday, August 31, 2009
Java vs Scala Control Structures
This topic is mainly for completeness. We will quickly cover the standard control structures you find in Java and see how they are the same or different in Scala.
The first thing to note is that in Scala 2.7 there is no break keyword. In Scala 2.8 there is a break control structure but it is slightly different than the Java break keyword. We will encounter that topic in a later lesson. The control structures I will quickly cover are: do-while, while, for and if.
For information about the Java case statement take a look at the several matching topics covered now and in the future.
Note: The Java ternary if statement does not exist in Scala instead the standard if statement is to be used. It is slightly more verbose but returns a value in the same way as a ternary if statement.
The first thing to note is that in Scala 2.7 there is no break keyword. In Scala 2.8 there is a break control structure but it is slightly different than the Java break keyword. We will encounter that topic in a later lesson. The control structures I will quickly cover are: do-while, while, for and if.
For information about the Java case statement take a look at the several matching topics covered now and in the future.
Note: The Java ternary if statement does not exist in Scala instead the standard if statement is to be used. It is slightly more verbose but returns a value in the same way as a ternary if statement.
- scala> var i = 0;
- i: Int = 0
- scala> while( i<3 ){
- | println( i )
- | i += 1
- | }
- 0
- 1
- 2
- scala> i = 0
- i: Int = 0
- scala> do {
- | println( i )
- | i += 1
- | } while (i<3)
- 0
- 1
- 2
- scala> for(j <- 0 until 3) println (j)
- 0
- 1
- 2
- scala> if (i<3)
- more
- scala> val result = if (i<3)
- result: Int = 10
- scala> println (result)
- 10
- scala> if (i>10) println(1)
- scala> if (i<10)
- 1
- // Note that the return value is (). You can only get a meaningful return value if there is an else-clause.
- scala> val r = if (i<10)>
- r: Unit = ()
- scala> println(r)
- ()
Java Conversions
The collections APIs in Scala are completely separate from those in Java, the primary reason is because they are designed from the ground up to be functional in nature. However, in keeping with Scala's goal of effortless Java integration there are implicits that convert between Java and Scala collections.
The object that needs to be imported has changed between Scala 2.7.x and 2.8 but the usage is the same. In 2.8 all conversions from Java to Scala objects (be it collections, concurrent constructs or others) are in objects call JavaConversions. For example in the August 31 nightly build there are 2 JavaConversions objects. scala.collection.JavaConversions and scala.concurrent.JavaConversions. As time progresses I expect that the types of JavaConversions will expand.
In scala 2.7 you can not convert Scala collections to Java collections only Java to Scala. In 2.8 you can do both.
So here is how to use conversions in 2.7.x:
And in 2.8:
The object that needs to be imported has changed between Scala 2.7.x and 2.8 but the usage is the same. In 2.8 all conversions from Java to Scala objects (be it collections, concurrent constructs or others) are in objects call JavaConversions. For example in the August 31 nightly build there are 2 JavaConversions objects. scala.collection.JavaConversions and scala.concurrent.JavaConversions. As time progresses I expect that the types of JavaConversions will expand.
In scala 2.7 you can not convert Scala collections to Java collections only Java to Scala. In 2.8 you can do both.
So here is how to use conversions in 2.7.x:
- scala> val jmap = new java.util.HashMap[String,Int]()
- jmap: java.util.HashMap[String,Int] = {}
- scala> jmap.put("one", 2)
- res0: Int = 0
- scala> jmap.get("one")
- res1: Int = 2
- scala> import scala.collection.jcl.Conversions._
- import scala.collection.jcl.Conversions._
- scala> jmap("one")
- res3: Int = 2
- scala> jmap("one") = 1
- scala> jmap("one")
- res5: Int = 1
And in 2.8:
- scala> val jmap = new java.util.HashMap[String,Int]()
- jmap: java.util.HashMap[String,Int] = {}
- scala> jmap.put("one", 2)
- res0: Int = 0
- scala> jmap.get("one")
- res1: Int = 2
- scala> import scala.collection.JavaConversions._
- import scala.collection.JavaConversions._
- scala> jmap("one")
- res3: Int = 2
- scala> jmap("one") = 1
- scala> jmap("one")
- res5: Int = 1
Labels:
beginner,
collections,
conversion,
implicit,
import,
java,
Scala
Friday, August 28, 2009
Apply-update methods
The apply and update methods have special meaning in Scala. They allow a developer to define semantics like java array access for an arbitrary class. For example:
As you can see you can invoke the apply method in a similar way that [] are used on arrays in Java. When you call '
Similarly '
This makes sense because if apply has many arguments representing the key then the same key must work for the update method for assignment. If you have many values to assign try:
- scala> class Phonebook {
- | val numbers = scala.collection.mutable.Map[String,String]()
- | def apply(name:String):String = numbers(name)
- | def update(name:String, number:String) = numbers(name) = number
- | }
- defined class Phonebook
- scala> val book = new Phonebook()
- book: Phonebook = Phonebook@1e406b09
- scala> book("jesse") = "123 456 7890"
- scala> println (book("jesse"))
- 123 456 7890
As you can see you can invoke the apply method in a similar way that [] are used on arrays in Java. When you call '
obj(param)
' the call is translated by the compiler to: 'obj.apply(param)
'. As such apply can have any number of arguments.Similarly '
obj(param) = value
' is compiled to 'obj.update(param,value)
'. Again the update method can have as many arguments as you wish. However only the last argument is treated as a value. So:- scala> class Echo {
- | def update(n:String, n2:String, n3:String ) = println(n,n2,n3)
- | }
- defined class Echo
- scala> val e = new Echo()
- e: Echo = Echo@2fa847df
- scala> e("hello", "hi") = "bonjour"
- (hello,hi,bonjour)
- scala> e("hello") = ("salut","bonjour")
:7: error: wrong number of arguments for method update: (String,String,String)Unit - e("hello") = ("salut","bonjour")
- ^
This makes sense because if apply has many arguments representing the key then the same key must work for the update method for assignment. If you have many values to assign try:
- scala> class Phonebook {
- | val numbers = scala.collection.mutable.Map[String, (Int, Int)]()
- | def apply(name:String) = numbers(name)
- | def update(name:String, number:(Int,Int)) = numbers(name) = number
- | }
- defined class Phonebook
- scala> val book2 = new Phonebook()
- book2: Phonebook = Phonebook@7a120cb3
- scala> book2("jesse") = (123, 4567890)
- scala> val (areaCode, local) = book2("jesse")
- areaCode: Int = 123
- local: Int = 4567890
Thursday, August 27, 2009
XPath Style XML Selection
The xml API in scala allows xpath like (although not true xpath) queries. In combination with matching this makes it very easy to process XML documents. I am only going to discuss xpath style selection now. The code section is very long but primarily because the results are often quite lengthy.
- scala> val address = <address>
- | <CI_Address>
- | <deliveryPoint>
- | <CharacterString>Viale delle Terme di Caracalla
- | </CharacterString>
- | </deliveryPoint>
- | <city>
- | <CharacterString>Rome</CharacterString>
- | </city>
- | <administrativeArea>
- | <CharacterString />
- | </administrativeArea>
- | <postalCode>
- | <CharacterString>00153</CharacterString>
- | </postalCode>
- | <country>
- | <CharacterString>Italy</CharacterString>
- | </country>
- | <electronicMailAddress>
- | <CharacterString>jippe.hoogeveen@fao.org
- | </CharacterString>
- | </electronicMailAddress>
- | </CI_Address>
- | </address>
- address: scala.xml.Elem =
- <address>
- <CI_Address>
- ...
- // create a pretty printer for writing out the document nicely
- scala> val pp = new scala.xml.PrettyPrinter(80, 5);
- pp: scala.xml.PrettyPrinter = scala.xml.PrettyPrinter@6d87c12a
- // select the city
- scala> println( pp.formatNodes( address \ "CI_Address" \ "city" ) )
- <city>
- <gco:CharacterString>Rome</gco:CharacterString>
- </city>
- // a second way to select city
- scala> println( pp.formatNodes( address \\ "city" ) )
- <city>
- <gco:CharacterString>Rome</gco:CharacterString>
- </city>
- // select all characterStrings and print then one per line (unless there is a \n in the text)
- scala> (address \\ "CharacterString").mkString( "\n" )
- res2: String =
- <CharacterString>Viale delle Terme di Caracalla
- </CharacterString>
- <CharacterString>Rome</CharacterString>
- <CharacterString></CharacterString>
- <CharacterString>00153</CharacterString>
- <CharacterString>Italy</CharacterString>
- <CharacterString>jippe.hoogeveen@fao.org
- </CharacterString>
- // iterate over the city node and all of its child nodes.
- scala> println( pp.formatNodes( address \\ "city" \\ "_"))
- <city>
- <CharacterString>Rome</CharacterString>
- </city><CharacterString>Rome</CharacterString>
- // similar as above but iterate over all CI_Address nodes and each of its children
- scala>println( pp.formatNodes( address \\ "CI_Address" \\ "_"))
- <CI_Address>
- <deliveryPoint>
- <CharacterString>Viale delle Terme di Caracalla </CharacterString>
- </deliveryPoint>
- <city>
- <CharacterString>Rome</CharacterString>
- </city>
- <administrativeArea>
- <CharacterString></CharacterString>
- </administrativeArea>
- <postalCode>
- <CharacterString>00153</CharacterString>
- </postalCode>
- <country>
- <CharacterString>Italy</CharacterString>
- </country>
- <electronicMailAddress>
- <CharacterString>jippe.hoogeveen@fao.org </CharacterString>
- </electronicMailAddress>
- </CI_Address><deliveryPoint>
- <CharacterString>Viale delle Terme di Caracalla </CharacterString>
- </deliveryPoint><CharacterString>Viale delle Terme di Caracalla </CharacterString><city>
- <CharacterString>Rome</CharacterString>
- </city><CharacterString>Rome</CharacterString><administrativeArea>
- <CharacterString></CharacterString>
- </administrativeArea><CharacterString></CharacterString><postalCode>
- <CharacterString>00153</CharacterString>
- </postalCode><CharacterString>00153</CharacterString><country>
- <CharacterString>Italy</CharacterString>
- </country><CharacterString>Italy</CharacterString><electronicMailAddress>
- <CharacterString>jippe.hoogeveen@fao.org </CharacterString>
- </electronicMailAddress><CharacterString>jippe.hoogeveen@fao.org </CharacterString>
- // print all text
- scala> address.text
- res4: String =
-
-
- Viale delle Terme di Caracalla
-
-
-
- Rome
-
-
-
-
-
- 00153
-
-
- Italy
-
-
- jippe.hoogeveen@fao.org
-
- // print all character string text
- scala> (address \\ "CharacterString").text
- res3: String =
- Viale delle Terme di Caracalla
- Rome00153Italyjippe.hoogeveen@fao.org
-
- // print all character string text one per line
- scala> (address \\ "CharacterString").map( _.text ).mkString("\n")
- res6: String =
- Viale delle Terme di Caracalla
-
- Rome
- 00153
- Italy
- jippe.hoogeveen@fao.org
- // find the longest character string
- scala> (address \\ "CharacterString").reduceRight(
- | (elem, longest) => {
- | if( elem.text.length > longest.text.length ) elem
- | else longest
- | })
- res8: scala.xml.Node =
- <CharacterString>Viale delle Terme di Caracalla
- </CharacterString>
- // find the alphabetically first characterstring
- scala> (address \\ "CharacterString").reduceRight( (elem, longest) => {
- | if( elem.text > longest.text ) elem
- | else longest
- | })
- res9: scala.xml.Node =
- <CharacterString>jippe.hoogeveen@fao.org
- </CharacterString>
Labels:
intermediate,
Scala,
select,
xml,
xpath
Tuesday, August 25, 2009
Named and default arguments
NOTE: This topic only works with the up-coming Scala 2.8.0. A beta is expected at the end of September but the nightly builds are quite good.
Named and default arguments are a very nice way to reduce boilerplate code. There are two ideas here.
Default Arguments
This allows one to provide a default value to a method argument so that the caller is not required to provide a value if the default is acceptable. This reduces boilerplate code but also allows the signatures of methods to change and not break existing code (although because of the JVM implementation I think a recompile may be required. I have not verified whether that is the case or not).
This is how it reduces boilerplate code. The java way:
2 methods are required and as the number of optional argument increase the number of methods increase exponentially and you will likely need several similarly named methods if the parameters do not have differing type.
In Scala 2.8+ you can assign a argument a default value which allows you to optionally provide the value or ignore it.
Named Arguments
The second part of the equation is named arguments. Suppose you have the method and all arguments have different values:
How can you provide a argument for p3 only? Named arguments to the rescue.
Using named arguments you can declare which arguments you are assigning which value. This works with methods with no default argument as well but is particularly important in conjunction with default arguments.
More examples:
Named and default arguments are a very nice way to reduce boilerplate code. There are two ideas here.
Default Arguments
This allows one to provide a default value to a method argument so that the caller is not required to provide a value if the default is acceptable. This reduces boilerplate code but also allows the signatures of methods to change and not break existing code (although because of the JVM implementation I think a recompile may be required. I have not verified whether that is the case or not).
This is how it reduces boilerplate code. The java way:
- int method( int required, int notRequired) { return required + notRequired; }
- int method( int required) { method(required,9); }
2 methods are required and as the number of optional argument increase the number of methods increase exponentially and you will likely need several similarly named methods if the parameters do not have differing type.
- def method( required:Int, notRequired:Int=0) = required + notRequired
In Scala 2.8+ you can assign a argument a default value which allows you to optionally provide the value or ignore it.
Named Arguments
The second part of the equation is named arguments. Suppose you have the method and all arguments have different values:
- scala> def bigMethod( p1:Int=1, p2:Int=2, p3:Int=3, p4:Int=4, p5:Int=5) = p1 +p2 + p3+ p4 + p5
- bigMethod: (p1: Int,p2: Int,p3: Int,p4: Int,p5: Int)Int
- scala> bigMethod() // you have to provide () when default params are used
- res10: Int = 15
How can you provide a argument for p3 only? Named arguments to the rescue.
- scala> bigMethod( p3 = 10 )
- res0: Int = 22
Using named arguments you can declare which arguments you are assigning which value. This works with methods with no default argument as well but is particularly important in conjunction with default arguments.
More examples:
- scala> def bigMethod( p1:Int=1, p2:Int=2, p3:Int=3, p4:Int=4, p5:Int=5) = p1 +p2 + p3+ p4 + p5
- bigMethod: (p1: Int,p2: Int,p3: Int,p4: Int,p5: Int)Int
- scala> bigMethod( p3 = 10 )
- res0: Int = 22scala> bigMethod( p3 = 10, p1=11 )
- res1: Int = 32
- scala> bigMethod( 10,10,10 )
- res3: Int = 39
- scala> bigMethod( 10,10,10, p5 = -100 )
- res5: Int = -66
- scala> def anotherMethod( p1:Int, p2:Boolean) = println( p1, p2 )
- anotherMethod: (p1: Int,p2: Boolean)Unit
- scala> anotherMethod(1,false)
- (1,false)
- scala> anotherMethod(p2=false, p1=10)
- (10,false)
- // anotherMethod does not have default args so you must declare all args
- scala> anotherMethod(p2=false)
:6: error: not enough arguments for method anotherMethod: (p1: Int,p2: Boolean)Unit. - Unspecified value parameter p1.
- anotherMethod(p2=false)
- ^
Labels:
default argument,
intermediate,
method,
named argument,
Scala
Equals
One welcome change in Scala is the change of semantics of the == operator. In Scala it is the same as doing a .equals comparison in Java. If you want identity comparison you can use the eq method (and ne for the not equal comparator).
Examples:
Examples:
- scala> case class Name(first:String, last:String)
- defined class Name
- scala> val jesse = Name("Jesse","Eichar")
- jesse: Name = Name(Jesse,Eichar)
- scala> val jesse2 = Name("Jesse","Eichar")
- jesse2: Name = Name(Jesse,Eichar)
- scala> val jody = Name("Jody","Garnett")
- jody: Name = Name(Jody,Garnett)
- scala> val jJames = Name("Jesse","James")
- jJames: Name = Name(Jesse,James)
- scala> jesse == jesse2
- res0: Boolean = true
- scala> jesse == jody
- res1: Boolean = false
- scala> jesse ne jesse2
- res2: Boolean = true
- scala> jesse eq jesse2
- res3: Boolean = false
Sunday, August 23, 2009
Enumerations
Scala does not have a enum keyword like java so enumerations are not quite as smooth. Depending on your requirements there are two ways to make enumerations.
In the following examples notice the use of the sealed keyword when defining the abstract class MyEnum. Sealed specifies that the heirarchy is sealed. Only classes defined in the same file can extend the class.
Also notice in the case object example that the enumeration values are "case object" not "case class". The two are similar except that there is only one instance of a case object. However all the same boiler plate code is generated and you can still match in the same way.
- Create an object that extends the Enumeration class
- Create a case-object hierarchy.
- If you only need discrete and related values without custom behaviour create and object that extends Enumeration
- If each value has custom information associated with it use case-objects
In the following examples notice the use of the sealed keyword when defining the abstract class MyEnum. Sealed specifies that the heirarchy is sealed. Only classes defined in the same file can extend the class.
Also notice in the case object example that the enumeration values are "case object" not "case class". The two are similar except that there is only one instance of a case object. However all the same boiler plate code is generated and you can still match in the same way.
- // Note: MyEnum is an object NOT a class
- scala> object MyEnum extends Enumeration("one", "two", "three") {
- | type MyEnumType = Value
- | val One, Two, Three = Value
- | }
- defined module MyEnum
- scala> MyEnum.One
- res1: MyEnum.Value = one
- scala> def printEnum( value:MyEnum.MyEnumType ) = println( value.toString )
- printEnum: (MyEnum.MyEnumType)Unit
- scala> printEnum(MyEnum.Two)
- two
- // If you don't want to prefix enums with MyEnum. Then you
- // can import the values. This is similar to static imports in java
- scala> import MyEnum._
- import MyEnum._
- scala> def printEnum( value:MyEnumType ) = println( value.toString )
- printEnum: (MyEnum.MyEnumType)Unit
- scala> printEnum(Three)
- three
- // Similar but with cases objects
- // Notice MyEnum is 'sealed' and the parameters have the 'val' keyword so they are public
- scala> abstract sealed class MyEnum(val name:String, val someNum:Int)
- defined class MyEnum
- scala> case object One extends MyEnum("one", 1)
- defined module One
- scala> case object Two extends MyEnum("two", 2)
- defined module Two
- scala> case object Three extends MyEnum("three", 3)
- defined module Three
- scala> def printEnum(value:MyEnum) = println(value.name, value.someNum)
- printEnum: (MyEnum)Unit
- scala> printEnum(One)
- (one,1)
Labels:
beginner,
case-object,
enum,
Enumeration,
object,
Scala
Thursday, August 20, 2009
Object
Scala does not have static methods in the same way that Java does. As a replacement Scala has "objects" and object is a singleton object whose methods can be called in the same manner one would call a static method in Java. The big differentiator is that objects are complete objects and can extent abstract classes and traits.
Objects are sometime referred to as modules as well. See next section for more on modules. In addition there is a special situation where a class has what is called a companion object. That is a topic for another day. Finally you can have case objects, also a topic for another day. case objects will be address as part of the Enumeration topic.
Objects are also a good way to modularize projects. You can define classes and other objects within an object
Objects are sometime referred to as modules as well. See next section for more on modules. In addition there is a special situation where a class has what is called a companion object. That is a topic for another day. Finally you can have case objects, also a topic for another day. case objects will be address as part of the Enumeration topic.
- scala> abstract class SuperClass {
- | def method = "hi"
- | val value = 10
- | }
- defined class SuperClass
- scala> object MyObject extends SuperClass {
- | override def method = "Hello"
- | def anotherMethod = "other"
- | }
- defined module MyObject
- scala> MyObject.method
- res0: java.lang.String = Hello
- scala> MyObject.value
- res1: Int = 10
- scala> MyObject.anotherMethod
- res2: java.lang.String = other
Objects are also a good way to modularize projects. You can define classes and other objects within an object
- scala> object Outer {
- | case class Data(name:String)
- |
- | def print(data:Data) = Console.println(data.name)
- |
- | object Factory {
- | def defaultData = Data("defaultName")
- | }
- | }
- defined module Outer
- scala> val data = Outer.Factory.defaultData
- data: Outer.Data = Data(defaultName)
- scala> Outer.print(data)
- defaultName
Labels:
beginner,
case-object,
object,
Scala
Wednesday, August 19, 2009
Implicit Methods
Scala is designed to be a scalable language and one if the features that assist in realizing this goal are implicit methods. I have seen it occasionally referred to as static duck typing (although I personally think that describes structural typing better).
The idea is to be able to extend an existing class with new methods in a type safe manner. Essentially what happens is a developer defines an implicit method (or imports an implicit method) which converts one object from one type to another type. Once the implicit method is within scope all methods are available from both types.
Note: The conversion is one way. If you want bi-directional conversion you need 2 methods.
The idea is to be able to extend an existing class with new methods in a type safe manner. Essentially what happens is a developer defines an implicit method (or imports an implicit method) which converts one object from one type to another type. Once the implicit method is within scope all methods are available from both types.
Note: The conversion is one way. If you want bi-directional conversion you need 2 methods.
- scala> class MyInteger( i:Int ) {
- | def myNewMethod = println("hello from myNewMethod")
- | }
- defined class MyInteger
- // an integer is not a "MyInteger" so you cannot call the myNewMethod on it
- scala> 1.myNewMethod
:5: error: value myNewMethod is not a member of Int - 1.myNewMethod
- ^
- // define an implicit conversion and now we can call all MyInteger methods on integers
- scala> implicit def int2MyInt( i:Int ) = new MyInteger(i)
- int2MyInt: (Int)MyInteger
- scala> 1.myNewMethod
- hello from myNewMethod
- scala> class MyInteger2( val i:Int ) {
- | def inc = new MyInteger2( i+1 )
- | }
- defined class MyInteger2
- // you can define several implicits in an object and import later
- scala> object Conversions{
- | implicit def int2MyInt2(i:Int) = new MyInteger2(i)
- | implicit def myInt2Int( mI:MyInteger2) = mI.i
- | }
- defined module Conversions
- // you can import all implicits or just one (although interpreter only allows all for some reason)
- scala> import Conversions._
- import Conversions._
- // inc returns a MyInteger2 instance
- scala> val j = 1.inc
- j: MyInteger2 = MyInteger2@37f808e6
- // test the bi-directionality of it all so create a method
- // that takes and int
- scala> def takesInt( i:Int ) = println(i)
- takesInt: (Int)Unit
- // j is a MyInteger2 but thanks to the implicit it can be converted back to an int
- scala> takesInt( j )
- 2
- scala> takesInt ( 5.inc )
- 6
Labels:
implicit,
import,
intermediate,
object,
Scala
Imports
Scala's mechanism for importing is analogous to Java's import statements but provides more options. For example import statements can be anywhere in the file and only apply to the scope that they are declared in.
- scala> def lineCount = {
- | import scala.io._ // the '_' character is the wildcard in scala
- | Source.fromURL("http://www.google.com").getLines.foldRight(0)( (line, acc) => acc + 1 )
- | }
- lineCount: Int
- scala> lineCount
- res1: Int = 11
- scala> def lineCount = {
- | import scala.io.Source
- | Source.fromURL("http://www.google.com").getLines.foldRight(0)( (line, acc) => acc + 1 )
- | }
- lineCount: Int
- scala> lineCount
- res3: Int = 11
- scala> def lineCount = {
- | import scala.io.Source.fromURL // import the fromURL method, only methods from objects can be imported.
- | fromURL("http://www.google.com").getLines.foldRight(0)( (line, acc) => acc + 1 )
- | }
- lineCount: Int
- scala> lineCount
- res4: Int = 11
- scala> def lineCount = {
- | import scala.io.Source.{fromURL => url} // you can remap imports to another name
- | url("http://www.google.com").getLines.foldRight(0)( (line, acc) => acc + 1 )
- | }
- lineCount: Int
- scala> lineCount
- res5: Int = 11
- scala> import java.io.{File, FileWriter} // you can import multiple classes in one statement
- import java.io.{File, FileWriter}
- scala> println( classOf[File], classOf[FileWriter])
- (class java.io.File,class java.io.FileWriter)
Monday, August 17, 2009
Return values
As with most functional languages, most control structures ( if, for, try ) return values. The common java idiom:
can be replaced by
The benefit (other than less boiler plate code) is that name can now be a val instead of a var.
Another other point about returns: The return keyword is not required when returning a value from methods or control structures. The last value is always the return value. This is why you will get an error if the last line in a method or control structure is an assignment.
Examples:
- String name=null;
- if( xxx ) name="yyy";
- else name="zzz";
can be replaced by
- val name = if( xxx ) "yyy"; else "zzz";
The benefit (other than less boiler plate code) is that name can now be a val instead of a var.
Another other point about returns: The return keyword is not required when returning a value from methods or control structures. The last value is always the return value. This is why you will get an error if the last line in a method or control structure is an assignment.
Examples:
- scala> val name = if( 1==2 ) "Jesse" else "Mauricio"
- name: java.lang.String = Mauricio
- scala> println(name)
- Mauricio
- scala> val collection = for( i <- 1 to 100; if(i%20 == 3) ) yield i
- collection: Seq.Projection[Int] = RangeFM(3, 23, 43, 63, 83)
- scala> collection.foreach( i => print( i +" ") )
- 3 23 43 63 83
- scala> val someObj:AnyRef = "Hello"
- someObj: AnyRef = Hello
- scala> val choice = someObj match {
- | case _:java.io.File => "File"
- | case _:String => "String"
- | case _ => "Dunno"
- | }
- choice: java.lang.String = String
- scala> val result = try {
- | "two".toInt
- | }catch{
- | case e:NumberFormatException => -1
- | case _ => 0
- | }
- result: Int = -1
- scala> var i=0
- i: Int = 0
- // while and do-while do not have return values
- scala> while( i<4 ){
- | "22"
- | i += 2
- | }
- scala> println( if(i>0) "great" else "less" )
- great
- // code blocks return the last statement
- scala> val m = {
- | val x = 1
- | x + 2
- | }
- m: Int = 3
Sunday, August 16, 2009
Assignment and Parameter Objects
One of the principle design goals of Scala is to be "deep" not wide, which means the language attempts to have a small set of rules that can be applied in many different ways in different situations. Pattern matching is one of my favourite examples of this. Pattern matching is commonly seen in match { case... } statements but you will also see it frequently in exception handling, function declaration and, most important for this post, assignment.
Scala does not have multiple assignment like some languages. Instead it has tuples and matching. Tuples are a light-weight way of grouping data in a simple data object.
This is pattern matching.
Notice that in both cases you need the brackets around firstname, lastname. This instructs the compiler that you are matching against a Tuple.
Now the interesting use is with parameter objects. Tuples are a poor substitute for parameter objects because they do not have context. Changing:
to
Is not a good change because you loose context. What are the 3 strings? The information must go in the Javadocs. A better option:
The beauty is that you have an object that you can pass around easily. It is a case class therefore extracting the information is incredibly easy and unlike a tuple it has context and can have methods added easily.
Yes it is longer to write but if you need to reuse the data in several locations the trade off is well worth it in clarity.
Examples:
Scala does not have multiple assignment like some languages. Instead it has tuples and matching. Tuples are a light-weight way of grouping data in a simple data object.
(1,2.0,'c',"string", true)
. A simple example of a 5 element tuple. Tuples can be up to 22 elements long and can be homogenous or heterogenous. Using this for multiple assignement works something like:- val (firstname, lastname) = ("Jesse","Eichar")
This is pattern matching.
- scala> ("Jesse","Eichar") match {
- | case (firstname,lastname) => println(firstname,lastname)
- | }
- (Jesse,Eichar)
Notice that in both cases you need the brackets around firstname, lastname. This instructs the compiler that you are matching against a Tuple.
Now the interesting use is with parameter objects. Tuples are a poor substitute for parameter objects because they do not have context. Changing:
- def myMethod( firstname:String, middlename:String, lastname:String) = {...}
to
- def myMethod( name:(String,String,String)) = {...}
Is not a good change because you loose context. What are the 3 strings? The information must go in the Javadocs. A better option:
- case class Name(first:String, middle:String, last:String)
- def myMethod( name:Name ) = {
- val Name(first, middle, last) = name
- // do something with first middle last
- }
The beauty is that you have an object that you can pass around easily. It is a case class therefore extracting the information is incredibly easy and unlike a tuple it has context and can have methods added easily.
Yes it is longer to write but if you need to reuse the data in several locations the trade off is well worth it in clarity.
Examples:
- // define name data object.
- // Notice toString is a lazy val. This means value is calculated only once.
- scala> case class Name(first:String, middle:String, last:String) {
- | override lazy val toString="%s, %s %s" format (last, first,middle)
- | }
- defined class Name
- // toString formats name nicely.
- scala> Name("Jesse","Dale","Eichar")
- res1: Name = Eichar, Jesse Dale
- scala> def readName() = {
- | //maybe load from a file or database
- | Name("Jesse","Dale","Eichar") :: Name("Jody","","Garnett") :: Name("Andrea","","Antonello"):: Nil
- | }
- readName: ()List[Name]
- scala> def firstLastName(name:Name) = {
- | // we are putting _ because we don't care about middle name
- | val Name( first, _, last ) = name
- | (first, last)
- | }
- firstLastName: (Name)(String, String)
- // convert the list of Names to a list of tuples of first and last name
- scala> readName().map( firstLastName _ )
- res2: List[(String, String)] = List((Jesse,Eichar), (Jody,Garnett), (Andrea,Antonello))
- // print all first names starting with J
- scala> for( Name(first,_,_) <- readname; if (first.startsWith("J"))) println(first)
- Jesse
- Jody
- // print the first and middle parts of the first name in the list
- scala> readName() match {
- | // the :: _ indicates that we are matching against a list but we don't care about the rest of the list
- | case Name(f,m,_) :: _ => println( f, m)
- | case _ => println("Not a list of names")
- | }
- (Jesse,Dale)
Labels:
assignment,
case-classes,
data object,
intermediate,
matching,
Scala,
tuple
Thursday, August 13, 2009
Traits and inheritance
Scala provides two structures for inheritance. Classes (abstract or not) and traits. Traits are very similar to Ruby Mixins meaning that they can contain code like abstract classes but like interfaces multiple traits can be inherited from.
Like interfaces traits cannot have constructors but in Scala variables can be abstract and therefore provide an easy way to simulate a constructor.
There are no method resolution conflicts because method definitions are always resolved right to left:
If ABC and Y all have the method (doit) the method in C will be used. If C calls super.doit that will call B.doit and so on.
Note: If Y defines doit the A, B, and C must define doit with the override keyword:
In the above example ABC must be traits but Y can be a class or a trait. When inheriting you must always have one extends keyword which can optionally followed by one or more with clauses.
Like interfaces traits cannot have constructors but in Scala variables can be abstract and therefore provide an easy way to simulate a constructor.
There are no method resolution conflicts because method definitions are always resolved right to left:
- class X extends Y with A with B with C
If ABC and Y all have the method (doit) the method in C will be used. If C calls super.doit that will call B.doit and so on.
Note: If Y defines doit the A, B, and C must define doit with the override keyword:
- override def doit() = {...}
In the above example ABC must be traits but Y can be a class or a trait. When inheriting you must always have one extends keyword which can optionally followed by one or more with clauses.
- scala> abstract class Animal {
- | val legs:Int
- | val noise:String
- | def makeNoise() = println(noise)
- | }
- defined class Animal
- scala> trait Quadriped {
- | self:Animal =>
- | val legs = 4
- | }
- defined trait Quadriped
- scala> trait Biped {
- | self:Animal =>
- | val legs = 2
- | }
- defined trait Biped
- scala> class Dog extends Animal with Quadriped {
- | val noise = "Woof"
- | override def makeNoise() = println( noise+" "+noise)
- | }
- defined class Dog
- scala> new Dog().makeNoise()
- Woof Woof
- scala> abstract class GenericAnimal extends Animal{
- | val noise = "glup"
- | }
- defined class GenericAnimal
- scala> val quad = new GenericAnimal() with Quadriped
- quad: GenericAnimal with Quadriped = $anon1ドル@10bfb545
- scala> quad.makeNoise()
- glup
- scala> val biped = new GenericAnimal() with Biped
- biped: GenericAnimal with Biped = $anon1ドル@7669521
- scala> val biped = new GenericAnimal() with Biped{
- | override val noise = "Hello"
- | }
- biped: GenericAnimal with Biped = $anon1ドル@6366ce5f
- scala> biped.makeNoise()
- Hello
Labels:
beginner,
classes,
inheritance,
Scala,
traits
Wednesday, August 12, 2009
Properties
In scala all vars are properties. When compiled to bytecode each var is instead compiled to a getter and a setter (but not a java bean style getter and setter). You generate bean style getters and setters if you add the BeanProperty annotation (that is for another day).
The signature of the generated methods of an Int variable called property are:
Because of this you can declare the property as a var and in the future if you decide that you need behaviour in a setter and getter you can introduce those two methods in place of the single var and no client code is broken.
Notice C2 uses the same API to access C1.property.
The signature of the generated methods of an Int variable called property are:
- def property:Int
- def property_=( newVal:Int):Unit
Because of this you can declare the property as a var and in the future if you decide that you need behaviour in a setter and getter you can introduce those two methods in place of the single var and no client code is broken.
- scala> class C1( var property:Int )
- defined class C1
- scala> class C2( dependency:C1 ){
- | println(dependency.property)
- | dependency.property += 1
- | println(dependency.property)
- | }
- defined class C2
- scala> new C2(new C1(10))
- 10
- 11
- res0: C2 = C2@72e28a61
- scala> class C1( private var p:Int) {
- | def property = p
- | def property_=(newVal:Int) = p = newVal * 100
- | }
- defined class C1
- scala> class C2( dependency:C1 ){
- | println(dependency.property)
- | dependency.property += 1
- | println(dependency.property)
- | }
- defined class C2
- scala> new C2(new C1(10))
- 10
- 1100
- res0: C2 = C2@62c639ce
Notice C2 uses the same API to access C1.property.
Labels:
intermediate,
properties,
Scala
Tuesday, August 11, 2009
For-comprehensions
The for-comprehension construct is a very powerful way of iterating over collections. In its most basic form it is the java
for( var: collection){}
loop. As with all flow constructs in Scala, the scala for loop (or more correctly for-comprehension) can return a value. In the case of the for-comprehension it returns Unit (similar to void in Java terms) or a Sequence if yield is used.- scala> val range = 1 to 5
- range: Range.Inclusive = Range(1, 2, 3, 4, 5)
- // no return value if there is no 'yield' keyword
- scala> for( i <- 1 to 10 ) { i + 1 }
- // if there is a yield a collection is returned
- // the type of collection depends on the input
- // here a Range is returned
- scala> for( i <- range ) yield i+1
- res1: RandomAccessSeq.Projection[Int] = RangeM(2, 3, 4, 5, 6)
- // here a list is returned
- scala> for( i <- list( "a", "b", "c") ) yield "Word: "+i
- res1: List[java.lang.String] = List(Word: a, Word: b, Word: c)
- // you can filter the elements that visited in the loop
- scala> for( i <- range; if( i % 2 == 0) ) yield i
- res2: Seq.Projection[Int] = RangeFM(2, 4)
- // this is more about creating ranges than loops
- scala> for ( i <- 20 until (10,-2) ) yield i
- res3: RandomAccessSeq.Projection[Int] = RangeM(20, 18, 16, 14, 12)
- // you can string together multiple "generators"
- scala> for( i <- range; j <- range) yield (i,j)
- res4: Seq.Projection[(Int, Int)] = RangeG((1,1), (1,2), (1,3), (1,4), (1,5), (2,1), (2,2), (2,3), (2,4), (2,5), (3,1), (3,2), (3,3), (3,4), (3,5), (4,1), (4,2), (4,3), (4,4), (4,5), (5,1), (5,2), (5,3), (5\
- ,4), (5,5))
- // you can also declar variables as part of the loop declaration
- scala> for( i <- range; j <- 1 to i; k = i-j) yield k
- res5: Seq.Projection[Int] = RangeG(0, 1, 0, 2, 1, 0, 3, 2, 1, 0, 4, 3, 2, 1, 0)
- // with round brackets '(' and ')' multiple lines will require semi-colons
- scala> for (
- | i <- range;
- | j <- 1 to i;
- | k = i-j) yield k
- res6: Seq.Projection[Int] = RangeG(0, 1, 0, 2, 1, 0, 3, 2, 1, 0, 4, 3, 2, 1, 0)
- // with curly brackets '{' and '}' multiple lines you do not require semi-colons
- scala> for {
- | i <- range
- | j <- 1 to i
- | k = i-j}
- | yield{
- | k
- | }
- res7: Seq.Projection[Int] = RangeG(0, 1, 0, 2, 1, 0, 3, 2, 1, 0, 4, 3, 2, 1, 0)
- scala> for( i <- "enjoy" ) print(i)
- enjoy
Subscribe to:
Posts (Atom)