Option in method call not compiling - scala

In order to get height of a binary tree I use this code :
object optionfun {
println("Welcome to the Scala worksheet")
case class Node(var isVisited: Boolean, var leftNode: Option[Node], var rightNode: Option[Node], name: String) {
def this(name: String) = this(false, None, None, name)
}
val a = new Node("a")
val b = new Node("b")
val c = new Node("c")
a.leftNode = Some(b)
a.rightNode = Some(c)
def getHeight(root: Option[Node]): Int = {
//if root contains a None type then it should return 0, should I pattern match on the option type here?
Math.max(getHeight(root.leftNode),
getHeight(root.rightNode.get)) + 1
}
getHeight(a)
}
But I receive compiler error for line :
Math.max(getHeight(root.leftNode)
error is :
Multiple markers at this line - value leftNode is not a member of Option[optionfun.Node] - value
leftNode is not a member of Option[Node]
I've mixed up the types somehow but I don't know what to pass into getHeight method "root.leftNode" is of type Option ?

The problem with accepting root as an Option[Node] is that you can't just call root.leftNode, you have to map or match the Option.
You could re-write it without the Option like this:
def getHeight(root: Node): Int = {
Math.max(
root.leftNode.map(getHeight(_)).getOrElse(0),
root.rightNode.map(getHeight(_)).getOrElse(0)
) + 1
}
Or with Option:
def getHeight(root: Option[Node]): Int = root match {
case Some(r) => Math.max(getHeight(r.leftNode), getHeight(r.rightNode)) + 1
case None => 0
}

Related

Scala conditional None value assignment

I am trying to convert com.google.gson.JsonObject to case class object. Sometime some element in the record are missing in which case I want it to be assigned as None to case class member
object tmp {
case class person(name: String, age: Long)
def main(args: Array[String]): Unit = {
val parser = new JsonParser();
//PERSON 2
val record2 = parser.parse("""{"name":"xyz"}""").getAsJsonObject()
val record2_name = record2.get("name").getAsString.toLowerCase
val record2_age = if(record2.get("age") != null) record2.get("age") else None
val person2 = new person(record2_name,record2_age) //-> Type Mismatch, expected: Long, actual: Long
println(person2);
}
}
In your case record2_age is of type JsonElement which is simply of type java Object, but since you are assigning None to it, it becomes Any WHICH you are trying to assign to type Long.
Short answer would be ,
val person = new person(jsonObject.get("name").getAsString.toLowerCase,
Option(jsonObject.get("age")).map(_.getAsLong).getOrElse(0l))
This way if jsonObject.get("age") is null, Option(null) gives you None, or if present you get Option(28) = Some(28)
You probably want your age to be 0l if empty. If you want it to be None, then you can use Option[Long].
1) simpler way
class GsonCaseClassSpecs extends FunSpec {
describe("case class conversion") {
it("converts gson with name/age to case class") {
case class person(name: String, age: Long)
val parser = new JsonParser()
//case 1
val jsonObject : JsonObject = parser.parse("""{"name":"xyz"}""").getAsJsonObject
val age = jsonObject.get("age") match {
case null => None
case value => Some(value.getAsLong)
}
val person1 = new person(jsonObject.get("name").getAsString.toLowerCase, age.getOrElse(0l))
assert(person1.name == "xyz")
assert(person1.age == 0)
//case 2
val jsonObject2 : JsonObject = parser.parse("""{"name":"xyz", "age" : 28}""").getAsJsonObject
val age2 : Option[Long] = jsonObject2.get("age") match {
case null => None
case value => Some(value.getAsLong)
}
val person2 = new person(jsonObject2.get("name").getAsString.toLowerCase, age2.getOrElse(0l))
assert(person2.name == "xyz")
assert(person2.age == 28)
}
}
}
2) If you want to make the age to be Option[Long]. Option[T] can have Some(x) or None.
class CaseClassFunSpecs extends FunSpec {
it("converts json to case class with empty age"){
case class person(name: String, age: Option[Long])
val parser = new JsonParser()
val json = parser.parse("""{"name":"xyz"}""").getAsJsonObject()
val personInstance = new person(json.get("name").getAsString.toLowerCase,
Option(json.get("age")).map(_.getAsLong))
assert(personInstance.name == "xyz")
assert(personInstance.age == None)
// NOTE
// do not do personInstance.age.get which would throw
// None.get
// java.util.NoSuchElementException: None.get
// at scala.None$.get(Option.scala:347)
// at scala.None$.get(Option.scala:345)
//rather use pattern match
personInstance.age match {
case Some(x) => println("value = " + x)
case None => throw new RuntimeException("Age can not be empty.")
}
}
it("converts json to case class with non-empty age"){
case class person(name: String, age: Option[Long])
val parser = new JsonParser()
val json = parser.parse("""{"name":"xyz", "age" : 28}""").getAsJsonObject()
val personInstance = new person(json.get("name").getAsString.toLowerCase,
Option(json.get("age")).map(_.getAsLong))
assert(personInstance.name == "xyz")
assert(personInstance.age == Some(28))
assert(personInstance.age.get == 28) //.get gives you the value
}
}
You simply can't call a parameter of one type, age: Long in your case, with an argument of a different type Option[Long].
At least this is what your question seems to imply.
You need to define the attributes that could be missing as Options.
e.g. in your example
case class person(name: String, age: Option[Long])
and then when you extract the json value you can make use of the Option.apply method, that returns None if the argument is null
…
val record2_age = Option(record2.get("age")).map(_.getAsLong) // None if get("age) returns null
…
val person2 = new person(record2_name,record2_age) //There should be no more type mismatch now

Scala Builder pattern with phantom types

Having the below builder pattern in Scala. To simplify it, I'm using 3 instances of A such that instance1 contains only field1 and has no connection to field2 or field3. The problem is that everywhere in the code I have to use val s = A.instance1.field1.get; doSomething(s), where the get call is not potentially safe. For example A.instance1.field2.get would fail on None.get. In order to guard it I have to match case against the option and deal with None cases:
object A {
val instance1 = new ABuilder().withField1("abc").build1
val instance2 = new ABuilder().withField1("abc").withField2("def").build2
val instance3 = new ABuilder().withField1("abc").withField3("def").build1
}
case class A(builder: ABuilder) {
val field1: Option[String] = builder.field1
val field2: Option[String] = builder.field2
val field3: Option[String] = builder.field3
}
class ABuilder {
var field1: Option[String] = None
var field2: Option[String] = None
var field3: Option[String] = None
def withField1(f: String): ABuilder = {
this.field1 = Some(f)
this
}
def withField2(f: String): ABuilder = {
this.field2 = Some(f)
this
}
def withField3(f: String): ABuilder = {
this.field3 = Some(f)
this
}
def build1: A = {
require(field1.isDefined, "field 1 must not be None")
A(this)
}
def build2: A = {
require(field1.isDefined, "field 1 must not be None")
require(field2.isDefined, "field 2 must not be None")
A(this)
}
}
Another solution would be to use parameterized types, also called phantom types. I found very few good tutorials on that subject, and could not find in any of them how to implement a type safe builder pattern in Scala with phantom types and actual data (or state) - all examples describe methods only.
How can I use phantom types in my example to avoid getting runtime None exceptions and get only nice type-mismatch exceptions? I'm trying to parameterize all the classes and methods mentioned and use sealed traits but had no success so far.
If you really want to use phantom types you could do
object PhantomExample {
sealed trait BaseA
class BaseAWith1 extends BaseA
final class BaseAWith12 extends BaseAWith1
object A {
val instance1 = new ABuilder().withField1("abc").build1
val instance2 = new ABuilder().withField1("abc").withField2("def").build2
}
case class A[AType <: BaseA](builder: ABuilder) {
def field1[T >: AType <: BaseAWith1] = builder.field1.get
def field2[T >: AType <: BaseAWith12] = builder.field2.get
}
class ABuilder {
var field1: Option[String] = None
var field2: Option[String] = None
def withField1(f: String): ABuilder = {
this.field1 = Some(f)
this
}
def withField2(f: String): ABuilder = {
this.field2 = Some(f)
this
}
def build1: A[BaseAWith1] = {
require(field1.isDefined, "field 1 must not be None")
A(this)
}
def build2: A[BaseAWith12] = {
require(field1.isDefined, "field 1 must not be None")
require(field2.isDefined, "field 2 must not be None")
A(this)
}
}
val x = A.instance1.field1 //> x : String = abc
val x2 = A.instance2.field1 //> x2 : String = abc
val x3 = A.instance2.field2 //> x3 : String = def
// This gives compilation error
//val x2 = A.instance1.field2
}
However, I don't recommend using this kind of code in production. I think it looks ugly, the compilation error seems cryptic, and IMHO is not the best solution. Think about it, if your instances are so different, maybe they are not even instances of the same concrete class?
trait BaseA {
def field1
}
class A1 extends BaseA { }
class A2 extends BaseA { ... def field2 = ... }
I'm not sure if this is what you want, but i think you can take it as a base.
First the A class:
case class A(field1: String = "",
field2: String = "",
field3: String = "")
The case class has default values of empty strings. This allow us to create any A object with any field value assigned without care of None values.
For example:
val b2 = A("abc", "def")
> b2: A = A(abc,def,)
val b1 = A("abc")
> b1: A = A(abc,,)
val notValidB = A(field2 = "xyz")
> notValidB: A = A(,xyz,)
As you can see b2 and b1 are valid objects, and notValidB is not valid since your object requires field1.
You can create another function that uses pattern matching to validate your A objects and then proceed with determinate actions.
def determineAObj(obj: A): Unit = obj match {
case A(f1, f2, _) if !f1.isEmpty && !f2.isEmpty => println("Is build2")
case A(f1, _, _) if !f1.isEmpty => println("Is build1")
case _ => println("This object doesn't match (build1 | build2)")
}
And then run:
determineAObj(b1)
> "Is build1"
determineAObj(b2)
> "Is build2"
determineAObj(notValidB)
> "This object doesn't match (build1 | build2)"

Scala - Create a new Class on runtime based on dynamic data

I have an array of String which I will be receiving from an arbitrary function. I want to use the elements of the array to create a new class at runtime (not a new object of an existing class). Let me give you an example
val keyCounts = Array[String]
def newDomainPartioner(keyCounts : Array[Strings]) : DomainPartitioner{
return class DomainPartitioner with Serializable {
def getPartition(key: Any): Int = key match {
case <first element of keyCount> =>
1
case <second element of keyCount> =>
1
case <third element of keyCount> =>
1
case <forth element of keyCount> =>
1
case _ => 0
}
}
}
Is there a way to achieve the intended functionality ?
You can use reflection to generate a new class at runtime, see this question for more details:
Generating a class from string and instantiating it in Scala 2.10
However, it sounds like you would be better off having a single class that encompasses the behaviour you want, and returning an instance of that class, eg:
class DomainPartitioner(keyCounts: Array[String]) with Serializable {
def getPartition(key: Any): Int = keyCounts indexOf key match {
case 1 =>
1
case 2 =>
1
case 3 =>
1
case x if myConditionIsTrue(x) =>
1
case _ => 0
}
}
def newDomainPartioner(keyCounts : Array[Strings]) =
new DomainPartitioner(keyCounts)
val arrayStrings: Array[String] = Array("we","are","great")
def arrayCaseClass(schema:Array[String], className:String)
:String = {
def arrayElements(element:String):String = {
val types = element
element match {
case x if !x.isEmpty => s" $element:String"
case _ => s" $element:$types"
}
}
val fieldsName = schema.map(arrayElements).mkString(",\n ")
s"""
|case class $className (
| $fieldsName
|)
""".stripMargin
}
println(arrayCaseClass(arrayStrings, "ArrayCaseClass"))

Retrieve typed stored values from Map

I'd like to put some data into a HashMap and retrieve these as typed values using a function. The function takes the expected type and also a default value in case the value is not stored in the HashMap. Type erasure of the JVM makes this a tricky thing.
Q: How can I retrieve a typed value?
Code and results below.
abstract class Parameters(val name: String) {
val parameters = new HashMap[String, Any]()
def put(key: String, value: Any) = parameters(key) = value
def get(key: String) = parameters.getOrElse(key, None)
def remove(key: String) = parameters.remove(key)
def g0[T: TypeTag](key: String, defaultValue: T) = {
get(key) match {
case x: T => x
case None => defaultValue
case _ => defaultValue
}
}
def g1[T: ClassTag](key: String, defaultValue: T) = {
val compareClass = implicitly[ClassTag[T]].runtimeClass
get(key) match {
case None => defaultValue
case x if compareClass.isInstance(x) => x.asInstanceOf[T]
}
}
}
class P extends Parameters("AParmList") {
put("1", 1)
put("3", "three")
put("4", 4.0)
put("width", 600)
println(g0[Int]("width", -1))
println(g0[Int]("fail", -2))
println(g1[Int]("width", -3))
println(g1[Int]("fail", -4))
}
object TypeMatching {
def main(args: Array[String]) {
new P
}
}
The output is (comments in parenthesis):
600 (as expected)
None (expected -2)
and a match error (java.lang.Integer stored, Int required)

Merge two case class of same type, except some fields

If you have a case class like:
case class Foo(x: String, y: String, z: String)
And you have two instances like:
Foo("x1","y1","z1")
Foo("x2","y2","z2")
Is it possible to merge instance 1 in instance 2, except for field z, so that the result would be:
Foo("x1","y1","z2")
My usecase is just that I give JSON objects to a Backbone app through a Scala API, and the Backbone app gives me back a JSON of the same structure so that I can save/update it. These JSON objects are parsed as case class for easy Scala manipulation. But some fields should never be updated by the client side (like creationDate). For now I'm doing a manual merge but I'd like a more generic solution, a bit like an enhanced copy function.
What I'd like is something like this:
instanceFromDB.updateWith(instanceFromBackbone, excludeFields = "creationDate" )
But I'd like it to be typesafe :)
Edit:
My case class have a lot more fields and I'd like the default bevavior to merge fields unless I explicitly say to not merge them.
What you want is already there; you just need to approach the problem the other way.
case class Bar(x: String, y: String)
val b1 = Bar("old", "tired")
val b2 = Bar("new", "fresh")
If you want everything in b2 not specifically mentioned, you should copy from b2; anything from b1 you want to keep you can mention explicitly:
def keepY(b1: Bar, b2: Bar) = b2.copy(y = b1.y)
scala> keepY(b1, b2)
res1: Bar = Bar(new,tired)
As long as you are copying between two instances of the same case class, and the fields are immutable like they are by default, this will do what you want.
case class Foo(x: String, y: String, z: String)
Foo("old_x", "old_y", "old_z")
// res0: Foo = Foo(old_x,old_y,old_z)
Foo("new_x", "new_y", "new_z")
// res1: Foo = Foo(new_x,new_y,new_z)
// use copy() ...
res0.copy(res1.x, res1.y)
// res2: Foo = Foo(new_x,new_y,old_z)
// ... with by-name parameters
res0.copy(y = res1.y)
// res3: Foo = Foo(old_x,new_y,old_z)
You can exclude class params from automatic copying by the copy method by currying:
case class Person(name: String, age: Int)(val create: Long, val id: Int)
This makes it clear which are ordinary value fields which the client sets and which are special fields. You can't accidentally forget to supply a special field.
For the use case of taking the value fields from one instance and the special fields from another, by reflectively invoking copy with either default args or the special members of the original:
import scala.reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
import System.{ currentTimeMillis => now }
case class Person(name: String, age: Int = 18)(val create: Long = now, val id: Int = Person.nextId) {
require(name != null)
require(age >= 18)
}
object Person {
private val ns = new java.util.concurrent.atomic.AtomicInteger
def nextId = ns.getAndIncrement()
}
object Test extends App {
/** Copy of value with non-defaulting args from model. */
implicit class Copier[A: ClassTag : TypeTag](val value: A) {
def copyFrom(model: A): A = {
val valueMirror = cm reflect value
val modelMirror = cm reflect model
val name = "copy"
val copy = (typeOf[A] member TermName(name)).asMethod
// either defarg or default val for type of p
def valueFor(p: Symbol, i: Int): Any = {
val defarg = typeOf[A] member TermName(s"$name$$default$$${i+1}")
if (defarg != NoSymbol) {
println(s"default $defarg")
(valueMirror reflectMethod defarg.asMethod)()
} else {
println(s"def val for $p")
val pmethod = typeOf[A] member p.name
if (pmethod != NoSymbol) (modelMirror reflectMethod pmethod.asMethod)()
else throw new RuntimeException("No $p on model")
}
}
val args = (for (ps <- copy.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
(valueMirror reflectMethod copy)(args: _*).asInstanceOf[A]
}
}
val customer = Person("Bob")()
val updated = Person("Bobby", 37)(id = -1)
val merged = updated.copyFrom(customer)
assert(merged.create == customer.create)
assert(merged.id == customer.id)
}
case class Foo(x: String, y: String, z: String)
val foo1 = Foo("x1", "y1", "z1")
val foo2 = Foo("x2", "y2", "z2")
val mergedFoo = foo1.copy(z = foo2.z) // Foo("x1", "y1", "z2")
If you change Foo later to:
case class Foo(w: String, x: String, y: String, z: String)
No modification will have to be done. Explicitly:
val foo1 = Foo("w1", "x1", "y1", "z1")
val foo2 = Foo("w2", "x2", "y2", "z2")
val mergedFoo = foo1.copy(z = foo2.z) // Foo("w1", "x1", "y1", "z2")