Wednesday, March 31, 2010
Variant Positions 2
This is a continuation of post: Variant Positions 1
...
My first attempt at the Verified was to make it a mutable object (my Java tourettes kicking in). But it cannot be covariant and mutable. Look at the code to see why:
For Verified to be mutable A must be invariant. If you look at the Mutable collections in Scala they are all invariant.
Here is an interesting example of both invariant and covariant type parameters in a class hierarchy:
This example is perfectly legal because no matter how X[Any] is used no illegal assignment in Y can occur. The interesting thing is that the object can be used in covariant usecases when only X is required. This is now the collections in Scala can work.
Here is a little example of collections invariance and covariance in action. In List the parameter is covariant but in Buffer it is invariant
...
My first attempt at the Verified was to make it a mutable object (my Java tourettes kicking in). But it cannot be covariant and mutable. Look at the code to see why:
- class Verified[+A <: V,V](assertion : (V) => Boolean, private var value : A){
- assert(assertion(value))
-
- def a = value
- // update is illegal. See the example below
- def update[ B >: A <: V](a : B) = value = a
- }
- def notNull(obj : AnyRef) = obj != null
- val v = new Verified(notNull, "hi")
- /*
- Up to this point everything looks ok but the next line
- will assign an object to value which is a reference to a String
- */
- v update (new Object())
For Verified to be mutable A must be invariant. If you look at the Mutable collections in Scala they are all invariant.
Here is an interesting example of both invariant and covariant type parameters in a class hierarchy:
- scala> class X[+A](val x :A)
- defined class X
- scala> class Y[A](var a: A) extends X[A](a)
- defined class Y
- scala> val x: X[Any] = new Y[String]("hi")
- x: X[Any] = Y@1732a4df
- scala> x.asInstanceOf[Y[String]].a="ho"
This example is perfectly legal because no matter how X[Any] is used no illegal assignment in Y can occur. The interesting thing is that the object can be used in covariant usecases when only X is required. This is now the collections in Scala can work.
Here is a little example of collections invariance and covariance in action. In List the parameter is covariant but in Buffer it is invariant
- scala> def printList(l : List[Any]) = print(l mkString " :: ")
- printList: (l: List[Any])Unit
- scala> val buffer = Buffer(1,2,3)
- buffer: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3)
- scala> printList(buffer)
- 1 :: 2 :: 3
- /*
- ++ is part of Iterable. Since Iterable is covariant ++
- returns a new buffer it does not modify the existing buffer
- All mutators are only defined on invariant traits
- */
- scala> buffer ++ List(4)
- res16: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3, 4)
- scala> res16 eq buffer
- res17: Boolean = false
- /*
- buffer defines += to add an element to the buffer
- so res27 is the same buffer with an extra element
- */
- scala> buffer += 10
- res27: buffer.type = ArrayBuffer(1, 2, 3, 10)
- scala> res27 eq buffer
- res28: Boolean = true
Labels:
collections,
covariance,
intermediate,
invariance,
Scala,
variance
Tuesday, March 30, 2010
Variant Positions 1
An additional topic on variance to finish up the major points on the topic. The previous two posts: contain required information for following this post.
In-, co- and contra- variance are the three types of variance expressible in Scala. I showed how this affects assignments and arguments being pass to methods in the last two topics. This looks at how the different types of variance influences how classes can be defined. In the last post we saw how the compiler complained about a method definition in a covariant class because the compiler recognized that such a definition was inherently dangerous and must be prohibited. The example was:
For an class like Output it does not make sense to have A be covariant so we changed A to be contravariant. However suppose we have a collection type class.
The previous definition is not legal because value and a in the parameter of a_= "occur in a contravariant position." What to do? Making A contravariant isn't an option:
In-, co- and contra- variance are the three types of variance expressible in Scala. I showed how this affects assignments and arguments being pass to methods in the last two topics. This looks at how the different types of variance influences how classes can be defined. In the last post we saw how the compiler complained about a method definition in a covariant class because the compiler recognized that such a definition was inherently dangerous and must be prohibited. The example was:
- scala> class Output[+A] {def write(a : A) = () /*do write*/ }
- < console>:5: error: covariant type A occurs in contravariant position in type A of value a
- class Output[+A] {def write(a : A) = () /*do write*/ }
- ^
For an class like Output it does not make sense to have A be covariant so we changed A to be contravariant. However suppose we have a collection type class.
- class Verified[+A] (assertion : (A) => Boolean, value : A){
- assert(assertion(value))
-
- def a = value
- def a_=(a : A) = new Verified(assertion, a)
- }
The previous definition is not legal because value and a in the parameter of a_= "occur in a contravariant position." What to do? Making A contravariant isn't an option:
- class Verified[+A <: V,V](assertion : (V) => Boolean, val value : A){
- assert(assertion(value))
- /*
- this is the key. Restrict possible types of
- A Since B is a super (or equal) type of A
- */
- def update[ B >: A <: V](a : B) = new Verified(assertion, a)
- }
- // example useage
- scala> def notNull(obj : AnyRef) = obj != null
- notNull: (obj: AnyRef)Boolean
- scala> val v = new Verified(notNull, "hi")
- v: Verified[java.lang.String,AnyRef] = Verified@307b37df
- scala> val newV = v update (new Object())
- newV: Verified[java.lang.Object,AnyRef] = Verified@36f72f09
- // 3 is not legal because the type parameter 'V' is AnyRef. Int is a subclass of Any NOT AnyRef
- scala> val newV = v update (3)
- < console>:8: error: inferred type arguments [Any] do not conform to method update's type parameter bounds [B >: java.lang.String <: AnyRef]
- val newV = v update (3)
- ^
Labels:
contravariance,
covariance,
intermediate,
invariance,
Scala,
variance
Saturday, March 27, 2010
Contravariance
Continuing on with variance and type parameters, this topic will discuss contravariance. See the post In- and Co- variance of type parameters for the intro material required for this topic.
Covariant parameters allow for an additional dimension of type compatibility:
Contravariance provides the opposite:
As covariance is indicated by a '+' before the type contravariance is indicated by a '-'
I can almost hear the "cool... but why?". Following the lead in the Programming In Scala book. Consider OutputStream first and a method in a Collection second. (The following code is illegal but consider it)
The previous example (if it would compile) would explode because an Output that can only write lists is passed to the method. In the example a String is written to the Output object. The Output[List[String]] cannot handle that.
Fortunately the compiler sees the definition of the class and recognizes this is an error waiting to happen and makes it illegal:
Consider the implications of making A contravariant?
In this example Output[Any] can be passed to the method. This makes sense. If the Output object knows how to write Any oject then it knows how to write an Object; its all good.
Covariant parameters allow for an additional dimension of type compatibility:
- val l : List[Object] = List("this is legal")
Contravariance provides the opposite:
- // If the type parameter of list was contravariant this would be legal:
- val l : List[String] = List(new Object())
As covariance is indicated by a '+' before the type contravariance is indicated by a '-'
- scala> class X[-A]
- defined class X
- scala> val l : X[String] = new X[Object]
- l: X[String] = X@66201d6d
I can almost hear the "cool... but why?". Following the lead in the Programming In Scala book. Consider OutputStream first and a method in a Collection second. (The following code is illegal but consider it)
- class Output[+A] {def write(a : A) = () /*do write*/ }
- def writeObject(out : Output[Object]) = out.write("hello")
- /*
- Uh oh you this only is for outputting lists not Objects
- (certainly not the String that is actually written)
- Runtime error for sure!
- */
- writeObject(new Output[List[String]])
The previous example (if it would compile) would explode because an Output that can only write lists is passed to the method. In the example a String is written to the Output object. The Output[List[String]] cannot handle that.
Fortunately the compiler sees the definition of the class and recognizes this is an error waiting to happen and makes it illegal:
- scala> class Output[+A] {def write(a : A) = () /*do write*/ }
- < console>:5: error: covariant type A occurs in contravariant position in type A of value a
- class Output[+A] {def write(a : A) = () /*do write*/ }
- ^
Consider the implications of making A contravariant?
- // The definition of object is now legal
- class Output[-A] {def write(a : A) = () /*do write*/ }
- // this is now a safe method definition since the parameter of Output must be a Object or a super class
- def writeObject(out : Output[Object]) = out.write("hello")
- // Now this is illegal as it should be
- scala> writeObject(new Output[List[String]])
- < console>:8: error: type mismatch;
- found : Output[List[String]]
- required: Output[java.lang.Object]
- writeObject(new Output[List[String]])
-
- // this is legal...
- scala> writeObject(new Output[Any])
In this example Output[Any] can be passed to the method. This makes sense. If the Output object knows how to write Any oject then it knows how to write an Object; its all good.
Labels:
contravariance,
covariance,
intermediate,
Scala,
variance
Wednesday, March 24, 2010
In- and Co- variance of type parameters
In Java most parameterized types are considered to be "invariant". What does that mean? Here is an example to explain:
Assignment compatibility has multiple dimensions: the object type and the types of the parameters. Unlike object type the compatibility of the type-parameters can be covariant, contravariant and invariant. Java has invariant parameters and that is demonstrated by the previous example. Covariant parameters allow subclassing. Contravariant parameters need their own topic.
- /*
- This is an example of a parameterized class that with an invariant parameter B
- In both Scala and Java parameters are invariant by default.
- */
- scala> class Invariant[B]
- defined class Invariant
- scala> var x : Invariant[Object] = new Invariant[Object]
- x: Invariant[java.lang.Object] = Invariant@2e0c5575
- /*
- Note: Invariant[String] cannot be assigned to Invariant[Object]
- even though logically it seems like it should be.
- This is the effect of invariance. Covariant parameters do not have
- this restriction.
- */
- scala> var x : Invariant[Object] = new Invariant[String]
- < console>:6: error: type mismatch;
- found : Invariant[String]
- required: Invariant[java.lang.Object]
- var x : Invariant[Object] = new Invariant[String]
- ^
- scala> class Sub[A] extends Invariant[A]
- defined class Sub
- /*
- Since Sub is a subclass of Invariant it can be assigned
- (but not Sub[String])
- */
- scala> val x : Invariant[Object] = new Sub[Object]
- x: Invariant[java.lang.Object] = Sub@26ced1a8
Assignment compatibility has multiple dimensions: the object type and the types of the parameters. Unlike object type the compatibility of the type-parameters can be covariant, contravariant and invariant. Java has invariant parameters and that is demonstrated by the previous example. Covariant parameters allow subclassing. Contravariant parameters need their own topic.
- // The '+' indicates the parameter is covariant
- scala> class Covariant[+B]
- defined class Covariant
- scala> var x : Covariant[Object] = new Covariant[Object]
- x: Covariant[java.lang.Object] = Covariant@315cb235
- // Now this is legal
- scala> var x : Covariant[Object] = new Covariant[String]
- x: Covariant[java.lang.Object] = Covariant@26e2e276
- /*
- Warning: The following is not legal because
- you cannot supply an invariant parameter
- with a covariant value.
- */
- scala> class Sub[+A] extends Invariant[A]
- < console>:7: error: covariant type A occurs in invariant position in type [+A]Invariant[A] with ScalaObject{def this(): Sub[A]} of class Sub
- class Sub[+A] extends Invariant[A]
- ^
- scala> class Sub[+A] extends Covariant[A]
- defined class Sub
- scala> class Sub[A] extends Covariant[A]
- defined class Sub
Labels:
covariance,
invariance,
Scala,
variance
Monday, March 22, 2010
Implicit '=' operator
Continuing on with operators, There is a special type of operator in Scala. It is an operator that ends with =. If a class has operation (methods with an operator identifer) the = can be appended to the effectively creating a new method. In truth a new method is not created instead the compiler rewrites the line.
For example. If a method (like Int) defines + then a method call += can be used. It can be used to mutate a variable:
To illustrate this is not a special case for Int the next example defines several operations and demonstrates in place variable mutation.
Here are several more examples using existing classes in Scala. They are all immutable examples.
Note: assignment operators can also be defined as methods to mutate an object
For example. If a method (like Int) defines + then a method call += can be used. It can be used to mutate a variable:
- scala> var i = 1
- i: Int = 1
- scala> i += 1
- scala> i
- res3: Int = 2
To illustrate this is not a special case for Int the next example defines several operations and demonstrates in place variable mutation.
- scala> case class MyClass(i:Int) {
- | def +(j:Int) = new MyClass(j + i)
- | def -(j:Int) = new MyClass(i - j)
- | def ^(j:Int) = MyClass(j)
- | def +|(j:Int) = new MyClass(j + i / 3)
- | }
- defined class MyClass
- scala> var c = MyClass(1)
- c: MyClass = MyClass(1)
- scala> c+=6
- scala> c
- res5: MyClass = MyClass(7)
- scala> c -= 2
- scala> c
- res7: MyClass = MyClass(5)
- scala> c ^= 10
- scala> c
- res23: MyClass = MyClass(10)
- scala> c +|= 5
- scala> c
- res25: MyClass = MyClass(8)
Here are several more examples using existing classes in Scala. They are all immutable examples.
- scala> var l = Set(1,2,3)
- l: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
- scala> l += 10
- scala> l
- res7: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 10)
- scala> var seq = Seq(5,6,3)
- seq: Seq[Int] = List(5, 6, 3)
- scala> seq :+= 10
- scala> seq
- res9: Seq[Int] = List(5, 6, 3, 10)
- scala> seq +:= 10
- scala> seq
- res11: Seq[Int] = List(10, 5, 6, 3, 10)
- scala> var list = List(32)
- list: List[Int] = List(32)
- scala> list ::= 12
- scala> list
- res13: List[Int] = List(12, 32)
Note: assignment operators can also be defined as methods to mutate an object
- scala> case class MyClass(var i:Int) {
- | def += (j:Int) = { i+=j ; this }
- | }
- defined class MyClass
- scala> val m = MyClass(6)
- m: MyClass = MyClass(6)
- scala> m += 7
- res0: MyClass = MyClass(13)
- scala> m += 9
- res1: MyClass = MyClass(22)
- scala> res1 eq m
- res2: Boolean = true
Labels:
assignment,
intermediate,
operator,
Scala
Friday, March 19, 2010
Operators
Since Scala allows one to define the behavior of operators there are some rules involving operators and assignment like +=. One of the standard method calls in most languages is
Since
These characters can be method names but they cannot be combined with other identifier characters.
Update: These characters can be combined with other identifier characters if there is an under score so:
However these characters are special because they can be combined in a special way with '=' for a special assignment construct as shown in the next post.
(end update)
i += 1.
Since
i+=1
(no spaces) is also valid, Scala has some rules regarding how statements like i+=1
should be broken up. Obviously we know it should be 'i' '+=' and '1'. So there is a special class of characters called operators. I don't know all of them but a few are: + - ^ * / % ! | & =
( ':' is sort of part of this group but has some special properties as well). These characters can be method names but they cannot be combined with other identifier characters.
Update: These characters can be combined with other identifier characters if there is an under score so:
- def x+ = 3 // not valid
- def x_+ = 3 // valid
- def +x = 3 // not valid
However these characters are special because they can be combined in a special way with '=' for a special assignment construct as shown in the next post.
(end update)
- scala> case class MyClass(i:Int) {
- | def +(j:Int) = new MyClass(j + i)
- | def -(j:Int) = new MyClass(i - j)
- | def ^(j:Int) = MyClass(j)
- | def +|(j:Int) = new MyClass(j + i / 3)
- | }
-
- scala> val c = MyClass(3)
- c: MyClass = MyClass(3)
- scala> c + 4
- res26: MyClass = MyClass(7)
- scala> c-2
- res27: MyClass = MyClass(1)
- scala> c -6
- res28: MyClass = MyClass(-3)
- scala> c ^ 3
- res29: MyClass = MyClass(3)
- scala> c+|5
- res31: MyClass = MyClass(6)
Labels:
assignment,
beginner,
operator,
Scala
Wednesday, March 17, 2010
Geoscript.scala
This is a bit of a departure from the standard daily-scala format, but it is one of the elements I had always invisioned. This topic takes a look at Geoscript.scala. (Github repo is at: http://github.com/dwins/geoscript.scala)
Geoscript has been given a Scala implementation. It is based on the Geotools Java library and thus far provides a script interface to access some of the basic spatial functions.
For setting up the console with the required libraries follow the instructions at: http://geoscript.org/scala/quickstart.html#quickstart
Here is an example of Geoscript in action:
Geoscript has been given a Scala implementation. It is based on the Geotools Java library and thus far provides a script interface to access some of the basic spatial functions.
For setting up the console with the required libraries follow the instructions at: http://geoscript.org/scala/quickstart.html#quickstart
Here is an example of Geoscript in action:
- scala> import org.geoscript.GeoScript._
- import org.geoscript.GeoScript._
- scala> import org.geoscript.geometry._
- import org.geoscript.geometry._
- scala> val line = LineString((10, 10), (20, 20), (30, 40))
- line: org.geoscript.geometry.LineString = LINESTRING (10 10, 20 20, 30 40)
- /*
- create a polygon by buffering the line (essentially expanding the line by 10 units that is degrees if not otherwise specified)
- */
- scala> val poly = line buffer 10
- poly: org.geoscript.geometry.Geometry = POLYGON ((11.781455848733053 25.923591472464004, 21.05572809000084 44.47213595499958, 22.100060210309515 46.13114600374718, 23.447982586398712 47.55453954995706, 25.04769531727891 48.68761637789669, 26.837722339831622 49.48683298050514, 28.74927391943886 49.921475911950004, 30.708890200906794 49.97484208812642, 32.64126422950409 49.6448806768120...
- // query the area of the polygon
- scala> poly.area
- res0: Double = 1041.9912814842407
- // get the centroids of the polygon and line
- scala> line.centroid
- res1: org.geoscript.geometry.Point = POINT (21.12574113277207 24.188611699158105)
- scala> poly.centroid
- res2: org.geoscript.geometry.Point = POINT (20.79088988611118 24.43096430943361)
- /*
- Obviously the polygon and line intersect since the polygon is a buffer of the line
- */
- scala> poly.intersects(line)
- res3: Boolean = true
- scala> val poly2 = Geometry.fromWKT("POLYGON ((10 10, 10 20, 20 20, 20 15, 10 10))")
- poly2: org.geoscript.geometry.Geometry = POLYGON ((10 10, 10 20, 20 20, 20 15, 10 10))
- // less trivial intersects operation
- scala> poly intersects poly2
- res3: Boolean = true
- // not make a new geometry from the intersection of the two geometries
- scala> val intersecting = poly intersection poly2
- intersecting: org.geoscript.geometry.Geometry = POLYGON ((10 10, 10 20, 20 20, 20 15, 10 10))
- scala> intersecting.area
- res6: Double = 75.0
- scala> import org.geoscript.projection._
- import org.geoscript.projection._
- /*
- None of the previous geometries has a projection associated.
- A new geometry can have one created with a projection by using the in(Projection) method
- */
- scala> val latLongPoly = poly2 in Projection("epsg:4326")
- latLongPoly: org.geoscript.geometry.Geometry = POLYGON ((10 10, 10 20, 20 20, 20 15, 10 10))
- // now reproject the latlong projection to a french projection
- scala> latLongPoly in Projection("epsg:21781")
- res12: org.geoscript.geometry.Geometry = POLYGON ((950650.7690658928 -4203986.192880551, 900363.7533498043 -2900002.601715782, 2061411.5566836582 -2774908.8442438124, 2174910.791185147 -3393231.5380846346, 950650.7690658928 -4203986.192880551))
Labels:
geoscript,
intermediate,
project,
Scala
Tuesday, March 16, 2010
Assert, Require, Assume
Very simple but useful are the methods assert, require and assume which are built into the Predef object. As you might expect they are methods for performing certain checks during runtime to verify certain conditions. They do not use the Java assert framework and therefore are always evaluated regardless of whether or not assertions are enabled.
Update: Scala 2.8 has an annotation called elidable that will (when 2.8 is complete) allow one to remove method calls at compile time by setting a compiler flag. The assert, etc... methods are all marked with this flag and as a result can be removed at compile time for production environments.
Scala2.8
scala 2.7.7
Update: Scala 2.8 has an annotation called elidable that will (when 2.8 is complete) allow one to remove method calls at compile time by setting a compiler flag. The assert, etc... methods are all marked with this flag and as a result can be removed at compile time for production environments.
Scala2.8
- scala> var called = 0
- called: Int = 0
- scala> called
- res0: Int = 0
- /*
- assert, require and assume have call by name parameters so the message is only
- calculated when the assertion fails.
- */
- scala> assert (called == 0, {called += 1; println("called is not 0")})
- scala> require (called == 0, {called += 1; println("called is not 0")})
- scala> assume (called == 0, {called += 1; println("called is not 0")})
- scala> called = 1
- called: Int = 1
- // demonstrating that the method is in fact called when the assertion fails
- scala> assert (called == 0, {called += 1; println("called is not 0")})
- called is not 0
- java.lang.AssertionError: assertion failed: ()
- at scala.Predef$.assert(Predef.scala:95)
- ...
- scala> called
- res4: Int = 2
- /*
- Require is intended to be used as a precondition of a method so
- it throws an IllegalArgumentException, not an AssertionError
- */
- scala> require (called == 0, {called += 1; println("called is not 0")})
- called is not 0
- java.lang.IllegalArgumentException: requirement failed: ()
- at scala.Predef$.require(Predef.scala:117)
- ...
- scala> called
- res6: Int = 3
- scala> assume (called == 0, {called += 1; println("called is not 0")})
- called is not 0
- java.lang.AssertionError: assumption failed: ()
- at scala.Predef$.assume(Predef.scala:107)
- ...
- scala> called
- res8: Int = 4
scala 2.7.7
- /*
- In Scala 2.7 the parameter is evaluated before the
- method is called so the side effect of the message causes
- the assertion to fail
- */
- scala> assert (called == 0, {called += 1; println("called is not 0")})
- called is not 0
- scala> called
- res2: Int = 1
Monday, March 15, 2010
Unzip
_ Scala 2.8 only tip _
Unzip is a handy companion to partition.
- Partition divides a traversable into two traversables by a boolean predicate.
- Unzip divides a traversable into two by dividing each element into two parts (each becomes an element in one traversable). If an element is a Tuple2 then each tuple is divided into two otherwise a function is required to divide an element into two.
Unzip is a handy companion to partition.
- Partition divides a traversable into two traversables by a boolean predicate.
- Unzip divides a traversable into two by dividing each element into two parts (each becomes an element in one traversable). If an element is a Tuple2 then each tuple is divided into two otherwise a function is required to divide an element into two.
- // basic usage
- scala> List((1,2),(2,3)).unzip
- res2: (List[Int], List[Int]) = (List(1, 2),List(2, 3))
- /*
- tuples can be of different types
- and the resulting traversables reflect the differing types
- */
- scala> List((2,"a"),(3,"b")).unzip
- res3: (List[Int], List[java.lang.String]) = (List(2, 3),List(a, b))
- // Maps are Traversable[Collection] so unzip works with them
- scala> Map(1 -> 2, 3 -> 4).unzip
- res1: (scala.collection.immutable.Iterable[Int], scala.collection.immutable.Iterable[Int]) = (List(1, 3),List(2, 4))
- // Of course sets result in sets and duplicates are collected to a single element
- scala> Set((1,2),(2,2)).unzip
- res7: (scala.collection.immutable.Set[Int], scala.collection.immutable.Set[Int]) = (Set(1, 2),Set(2))
- /*
- Arbitrary elements can be unziped if a method is provided to decompose each element
- */
- scala> List("one word", "another word").unzip {e => (e takeWhile {_ != ' '}, e dropWhile {_ != ' '})}
- res6: (List[String], List[String]) = (List(one, another),List( word, word))
- /*
- The following shows the same function
- applied with map. It results in a single
- list of Tuples rather than two lists of single elements
- */
- scala> List("one word", "another word").map {e => (e takeWhile {_ != ' '}, e dropWhile {_ != ' '})}
- res8: List[(String, String)] = List((one, word), (another, word))
Labels:
2.8,
beginner,
Scala,
traversable,
unzip
Friday, March 12, 2010
Multiple Argument Implicit Conversions
Suppose you are creating a DSL and you want to implicitly convert 2 values to a particular object:
This is easily attained. In this example we will support the previous example as well as the standard 1 object to another implicit conversion.
Example:
- val v : SomeObject = (2, 3)
This is easily attained. In this example we will support the previous example as well as the standard 1 object to another implicit conversion.
- val v : SomeObject = 2
Example:
- // first lets define a class
- scala> case class Randomly(x : Int, y : Double)
- defined class Randomly
- // the normal conversion
- scala> implicit def intToRandomly(i : Int) = new Randomly(i,0.0)
- intToRandomly: (i: Int)Randomly
- /*
- now a tuple for the other conversion.
- Important: The two conversions must have different names. At least that is the case in Scala 2.8
- */
- scala> implicit def tupleToRandomly(i : (Int, Double)) = new Randomly(i._1, i._2)
- tupleToRandomly: (i: (Int, Double))Randomly
-
- scala> val r1 : Randomly = 4
- r1: Randomly = Randomly(4,0.0)
- scala> val r2 : Randomly = (4, 6.0)
- r2: Randomly = Randomly(4,6.0)
- /*
- Suppose you want to do
- val r : Randomly = (4,4)
- you might think to implicitly convert from in to double
- */
- scala> implicit def intToDouble(i : Int) = i.toDouble
- intToDouble: (i: Int)Double
- // implicit chaining is not permitted
- scala> val r3 : Randomly = (4, 6)
- < console>:10: error: type mismatch;
- found : (Int, Int)
- required: Randomly
- val r3 : Randomly = (4, 6)
- // Here is the legal option
- scala> implicit def intTupleToRandomly(t: (Int,Int)) = new Randomly(t._1,t._2.toDouble)
- intTupleToRandomly: (t: (Int, Int))Randomly
- scala> val r3 : Randomly = (4, 6)
- r3: Randomly = Randomly(4,6.0)
Labels:
case-classes,
implicit,
intermediate,
Scala
Wednesday, March 10, 2010
How to reverse a map
Suppose you wish to take a map and swap the keys with values. The stackoverflow question Elegant way to revers a map in scala offers some good suggestions
- scala> val nodupes = Map(1 -> "a", 2-> "b", 3 -> "c")
- nodupes: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,a), (2,b), (3,c))
- // Scala 2.8+
- scala> nodupes map {_.swap}
- res4: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,1), (b,2), (c,3))
- // Scala 2.7
- scala> Map() ++ (nodupes map {case (k,v) => (v,k)})
- res5: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,1), (b,2), (c,3))
- // watch out if the values have duplicates you will loose information:
- scala> val dupes = Map(1 -> "a", 2-> "b", 3 -> "b")
- dupes: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,a), (2,b), (3,b))
- scala> dupes map {_.swap}
- res6: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,1), (b,3))
- // a way to not loose any data
- scala> dupes groupBy {_._2} map {case (key,value) => (key, value.unzip._1)}
- res12: scala.collection.Map[java.lang.String,scala.collection.immutable.Iterable[Int]] = Map((a,List(1)), (b,List(2, 3)))
- // I wanted to do the following for performance:
- scala> dupes.view groupBy {_._2} map {case (key,value) => (key, value.unzip._1)}
- java.lang.UnsupportedOperationException: IterableView((1,a), (2,b), (3,b)).newBuilder
- at scala.collection.TraversableViewLike$class.newBuilder(TraversableViewLike.scala:40)
- at scala.collection.IterableLike$$anon1ドル.newBuilder(IterableLike.scala:363)
- at scala.collection.TraversableLike$$anonfun$groupBy1ドル.apply(TraversableLike.scala:370)
- // but as you can see a view cannot yet be grouped. Perhaps in the future.
Labels:
groupby,
intermediate,
Map,
Scala,
swap
Monday, March 8, 2010
Lazy man's random test data
A quick tip for generating some random testdata.
Note: This is a poor man's solution to using ScalaCheck. If you can handle the dependency I would really recommend using that library.
Note: This is a poor man's solution to using ScalaCheck. If you can handle the dependency I would really recommend using that library.
- scala> object Options extends Enumeration {
- | val ONE, TWO, THREE, FOUR = Value
- | }
- defined module Options
- /*
- Randomly select zero or more elements from the options enumeration
- */
- scala> Options.values.filter {_ => util.Random.nextBoolean} mkString ", "
- res2: String = TWO, FOUR
- /*
- Select a random string.
- Warning: there is no restriction on the characters so control characters are likely
- */
- scala> util.Random.nextString(10)
- res5: String = ??????????
- /*
- ASCII string is oftern more useful for test data. This selects a random string up to 13 characters long
- */
- scala> util.Random.nextASCIIString(13)
- res6: java.lang.String = RVPD\#_HqJ8:o
- /*
- This creates a sequence of 10 random strings
- */
- scala> 1 to 10 map {_ => util.Random.nextASCIIString(13)}
- res7: scala.collection.immutable.IndexedSeq[java.lang.String] = IndexedSeq(;E8|Q8H8RI;Q=, vM-X;"ksBr\:c, SKyz{uXNQ5E]X, =Jd8_ll08)s%e, gRCs)6wj%C-YF, `x;2Zru?l*c%@, XE*/Rx9:qPfpm, s|u,e.un+-Xm(, M,TpX9Dq-6$+^, w;exER�|}Ya)
Saturday, March 6, 2010
Blocks within if statements
his is another topic that examines the consistency of Scala. This topic examines blocks in if statements. It is related to Blocks within for comprehensions and Temporary Variables during object creation.
- // standard if
- if(1 > 2) -1 else 0
- // since blocks return a value you can use a block within the if statement
- // (not sure when you would want to but...)
- if ({ val x = 1
- val y = 2
- x == y }) 1 else 2
Thursday, March 4, 2010
Zip with a constant value
A simple tip for zipping a List (or other collection) with a single value.
- scala> Stream.continually("h") zip List(1,2,3,4)
- res2: scala.collection.immutable.Stream[(java.lang.String, Int)] = Stream((h,1), ?)
- scala> res2 mkString ","
- res3: String = (h,1),(h,2),(h,3),(h,4)
- scala> List(1,2,3,4) zip Stream.continually("h")
- res4: List[(Int, java.lang.String)] = List((1,h), (2,h), (3,h), (4,h))
Labels:
collections,
Scala,
stream,
zip
Wednesday, March 3, 2010
Functions using case statements
A further tip regarding using case statements to construct functions. If a case statement is assigned to a Function it will construct a Function object not a PartialFunction.
I suppose the question is why do you care about this since PartialFunction is a Function. The fact is that a PartialFunction is a Function1. But using a case statement you can construct a Function4 very easily.
I suppose the question is why do you care about this since PartialFunction is a Function. The fact is that a PartialFunction is a Function1. But using a case statement you can construct a Function4 very easily.
- scala> def run(f : Function1[Any,Unit]) = println(f.isInstanceOf[PartialFunction[_,_]])
- run: (f: (Any) => Unit)Unit
- /*
- since run expects a Function calling run as shown here will make a
- Function object not a PartialFunction Object
- */
- scala> run({case f => ()})
- false
- scala> def pf(f : PartialFunction[Any,Unit]) = println(f.isInstanceOf[PartialFunction[_,_]])
- pf: (f: PartialFunction[Any,Unit])Unit
- // Now a PartialFunction will be created
- scala> pf({case f => ()})
- true
- scala> def run(f : Function2[Int,String,Unit]) = f(1,"2")
- run: (f: (Int, String) => Unit)Unit
- /*
- This demonstrates why it is important that a case creates a Function
- when assigned to a Function. PartialFunctions are Function1 objects
- but the following statement is creating a Function2 object.
- */
- scala> run({
- | case (1,b) => println(b)
- | case (a,b) => println(a,b)
- | })
- 2
Labels:
case,
function,
intermediate,
partial-function,
Scala
Tuesday, March 2, 2010
Methods on PartialFunction
This topic inspects the methods defined in the PartialFunction Object.
- scala> type PF = PartialFunction[Int,Int]
- defined type alias PF
- // the two partial functions that we will use for the examples
- scala> val pf1 : PF = {case 1 => 2}
- pf1: PF = < function1>
- scala> val pf2 : PF = {case 2 => 3}
- pf2: PF = < function1>
- /*
- As is well known, when a PartialFunction is called with a value
- it must be defined at that value or bad things will happen
- */
- scala> pf1 isDefinedAt 1
- res14: Boolean = true
- scala> pf1 isDefinedAt 2
- res15: Boolean = false
- scala> pf1(2)
- scala.MatchError: 2
- at $anonfun1ドル.apply(< console>:5)
- at $anonfun1ドル.apply(< console>:5)
- at .< init>(< console>:7)
- ...
- scala> pf1(1)
- res5: Int = 2
- /*
- It is possible to compose two partial functions so first one partialFunction is called and then the next
- */
- scala> (pf1 andThen pf2) isDefinedAt 2
- res16: Boolean = false
- scala> (pf1 andThen pf2) isDefinedAt 1
- res17: Boolean = true
- scala> (pf1 andThen pf2)(2)
- scala.MatchError: 2
- at $anonfun1ドル.apply(< console>:5)
- at $anonfun1ドル.apply(< console>:5)
- at scala.PartialFunction$$anon2ドル.apply(PartialFunction.scala:59)
- at .< init>(< console>:8)
- ...
- scala> (pf1 andThen pf2)(1)
- res8: Int = 3
- /*
- An alternative way of combining PartialFunctions is to 'or' them
- */
- scala> (pf1 orElse pf2) isDefinedAt 1
- res18: Boolean = true
- scala> (pf1 orElse pf2) isDefinedAt 2
- res19: Boolean = true
- scala> (pf1 orElse pf2) isDefinedAt 3
- res20: Boolean = false
- scala> (pf1 orElse pf2)(1)
- res9: Int = 2
- scala> (pf1 orElse pf2)(2)
- res10: Int = 3
- /*
- Finally a PartialFunction can be easily converted to a function that returns
- an Option
- */
- scala> pf1.lift
- res21: (Int) => Option[Int] = < function1>
- scala> pf1.lift(1)
- res11: Option[Int] = Some(2)
- scala> pf1.lift(2)
- res12: Option[Int] = None
Labels:
case,
intermediate,
partial-function,
Scala
Monday, March 1, 2010
NullPointer when mixed traits (Warning)
This tip is mainly to document a 'GOTCHA' that I got caught by recently. It basically goes like this:
Trait Y extends(or has self-type) X. Trait X defines some abstract method 'm'. The initialization code in Y accesses 'm'. Creation of an object new X with Y results in: *Boom* NullPointerException (on object creation).
The example in code:
At a glance it seems that x should override the abstract value x in trait X. However the order in which traits are declared is important. In this case first Y is configured then X. Since X is not yet configured Y throws an exception. There are several ways to work around this.
Option 1:
Option 2:
Option 3:
Option 4:
Two more warnings. First, the same error will occur whether 'x' is a def or a val or a var.
Second warning: In complex domain models it is easy to have a case where Y extends X but the final object is created as: new X with Y{...}.
You will get the same error here because (I think) the compiler recognized that Y is being mixed in with X and therefore the X will be initialized as after Y instead of before Y.
First the code:
If the code instantiated new Y{...} the initialization would be X then Y. Because X can only be initialized once, the explicit declaration of new X with Y forces Y to be initialized before X. (X can only be initialized once even when it appears twice in the hierarchy).
This is a topic called linearization and will be addressed in the future.
Trait Y extends(or has self-type) X. Trait X defines some abstract method 'm'. The initialization code in Y accesses 'm'. Creation of an object new X with Y results in: *Boom* NullPointerException (on object creation).
The example in code:
- scala> trait X { val x : java.io.File }
- defined trait X
- scala> trait Y {self : X => ; val y = x.getName}
- defined trait Y
- scala> new X with Y { val x = new java.io.File("hi")}
- java.lang.NullPointerException
- at Y$class.$init$(< console>:5)
- at $anon1ドル.< init>(< console>:7)
- ...
At a glance it seems that x should override the abstract value x in trait X. However the order in which traits are declared is important. In this case first Y is configured then X. Since X is not yet configured Y throws an exception. There are several ways to work around this.
Option 1:
- trait X {val x : java.io.File}
- trait Y {self : X => ; val y = x.getName}
- /*
- Declaring Y with X will work because Y is initialized after X
- but remember that there may
- be other reasons that X with Y is required.
- Method resolution is one such reason
- */
- new Y with X { val x = new java.io.File("hi")}
Option 2:
- trait X { val x : java.io.File }
- trait Y {self : X => ; def y = x.getName}
- /*
- Since method y is a 'def' x.getName will not be executed during initialization.
- */
- scala> new X with Y { val x = new java.io.File("hi")}
- res10: java.lang.Object with X with Y = $anon1ドル@7cb9e9a3
Option 3:
- trait X { val x : java.io.File }
- trait Y {self : X => ; lazy val y = x.getName}
- /*
- 'lazy val' works for the same reason 'def' works: x.getName is not invoked during initialization
- */
- scala> new X with Y { val x = new java.io.File("hi")}
- res10: java.lang.Object with X with Y = $anon1ドル@7cb9e9a3
Option 4:
- trait X {val x : java.io.File }
- trait Y extends X {def y = x.getName}
- /*
- if Y extends X then a new Y can be instantiated
- */
- new Y {val x = new java.io.File("hi")}
Two more warnings. First, the same error will occur whether 'x' is a def or a val or a var.
- trait X { def x : java.io.File }
- trait Y {self : X => ; val y = x.getName}
- new X with Y { val x = new java.io.File("hi")}
Second warning: In complex domain models it is easy to have a case where Y extends X but the final object is created as: new X with Y{...}.
You will get the same error here because (I think) the compiler recognized that Y is being mixed in with X and therefore the X will be initialized as after Y instead of before Y.
First the code:
- trait X { def x : java.io.File }
- trait Y extends X { val y = x.getName}
- new X with Y { val x = new java.io.File("hi")}
If the code instantiated new Y{...} the initialization would be X then Y. Because X can only be initialized once, the explicit declaration of new X with Y forces Y to be initialized before X. (X can only be initialized once even when it appears twice in the hierarchy).
This is a topic called linearization and will be addressed in the future.
Labels:
inheritance,
intermediate,
linearization,
Scala,
traits,
with
Subscribe to:
Comments (Atom)