I'm going through Scala book.
Here is example:
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}
val x = null
println(toInt(x)) // < ---- why compiler does not complain about Null ?
toInt should allow only String as an argument, but compiler is ok with null.
Why ?
Such kind of behavior is disallowed in Rust and TypeScript.
Is there any flag or smth ?
Option can help because Option(null) == None
def toInt(s: String): Option[Int] =
Option(s).flatMap(_.toIntOption)
toInt(null) // None
but usually we anticipate it with signatures like
def toInt(s: Option[String]): Option[Int]
and wrap legacy APIs that can return null with a Scala layer
toInt(Option(legacyJavaApi()))
Also linters like Wartremover could help: https://www.wartremover.org/doc/warts.html#null
Occasionally an initialisation trick with var x: A = null.asInstanceOf[A] is useful where A is a type parameter because otherwise what would you assign to x?.
Related
I have the following code to cast value to the type of default:
def fct[T](value: Any, default: T): T = {
val result = value.asInstanceOf[T]
println(result, result.getClass.getName, result.isInstanceOf[T])
result
}
val res = fct("foo", 42)
Which result is:
(foo,java.lang.String,true)
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(Test.sc2.tmp)
at #worksheet#.res$lzycompute(Test.sc2.tmp:7)
at #worksheet#.res(Test.sc2.tmp:7)
at #worksheet#.#worksheet#(Test.sc2.tmp:7)
Question is: why the println is displayed? The cast should fail. I tried to try / catch the 3 lines but no exception are raised during the function call.
Because of type erasure, it's impossible to actually implement value.{as/is}InstanceOf[T] where T is a type parameter. Scala's designers decided it should still compile, but value.asInstanceOf[T] is actually a no-op (just like the equivalent (T) value in Java), while value.isInstanceOf[T] always returns true (in Java, value instanceOf T is a compiler error). (Since it never does what you want, I'd strongly like to see a warning instead, at least for isInstanceOf.)
But Scala allows to do what you want using ClassTag:
import scala.reflect.ClassTag
def fct[T](value: Any, default: T)(implicit tag: ClassTag[T]): T = {
val result = (tag match {
case ClassTag.Int => value.asInstanceOf[Int]
... same for other primitives
case _ => tag.runtimeClass.cast(value)
}).asInstanceOf[T]
println(result, result.getClass.getName, tag.runtimeClass.isInstance(result))
result
}
(You still need asInstanceOf[T] because tag.runtimeClass usually returns the same class T represents but not always, so its static return type has to be Class[_] and cast returns Any).
However, pattern-matching : T handles presence of a ClassTag automatically and already handles boxing, so
def fct[T](value: Any, default: T)(implicit tag: ClassTag[T]): T = value match {
case result: T => result
case _ => default // or whatever you want to do if value is not a T
}
is the better way to do this.
On many occasions I use parameters in form of the Option with None as default - like this:
def foo(bar:Option[String] = None)
This gets handy and allows me to easily omit nulls. However, if I do this, I need to change every invocation of the method to
foo(Some("bar"))
instead of simply
foo("bar")
This, however, looks a bit redundant, because it is obvious, that when I specify a value it is a full option. I'm pretty sure I can try to write some implicit converters to do such wrapping for me - unfortunately, I have no idea how.
Bonus - is this a sane thing to do? Is there any other way of dealing with a problem of "nullable" parameters?
I'll give you some options (pun intended).
Don't use an optional parameter, and instead use Option.apply. This is useful for when the parameter isn't necessarily optional, but you want to deal with possible null values being passed.
def foo(bar: String): ?? = Option(bar)... // operate on the Option[String]
The advantage of this is that Option.apply automatically converts null to None for you, so there's absolutely no need to use if/else.
Use an overload for non-optional parameters. This is more so for when the parameter is truly optional, but gives you the convenience of passing Option wrapped or unwrapped. It won't be possible to pass null here without knowing the type first, however.
def foo(bar: String): ?? = foo(Option(bar))
def foo(bar: Option[String]): ?? = ???
Example:
def foo(bar: String): Option[String] = foo(Option(bar))
def foo(bar: Option[String]): Option[String] = bar.map(_ + "aaa")
scala> foo("bbb")
res7: Option[String] = Some(bbbaaa)
scala> foo(null: String) // The String ascription is necessary here.
res9: Option[String] = None
scala> val str: String = null
scala> foo(str) // No ascription necessary, since we know the type.
res10: Option[String] = None
Implicitly convert anything to Option.
implicit def any2Opt[A](value: A): Option[A] = Option(value)
And keep the current definintion of
def foo(bar: Option[String]): ?? = ???
Implicitly converting to Option, however can result in some unexpected results, so be wary.
You can write the desired generic implicit as
implicit def wrapToOption[T](x: T) = Option[T](x)
Then you can do
def foo(bar: Option[String] = None) = println(bar)
foo("bar") //> Some(bar)
foo() //> None
def fooBar(bar: Option[Int] = None) = println(bar)
fooBar(2) //> Some(2)
About it being sane thing to do, I will say no(personal opinion). Implicits are notoriously hard to debug. A logic gone wrong in the implicits can make your life hell.
Also, each new addition to the team will have to taught about all these "magics" happening behind the scenes.
It is a perfectly reasonable approach to set the type of your parameters to Option[_], if they really can be optional. However, I do not recommend using implicits to convert directly to Option[_].
You can make the syntax a little easier on the eye by including Scalaz and using some:
foo("hello".some)
If you don't want to bring in Scalaz just for that it's very easy to write your own implicit to do so. This implicit is better because you explicitly call the some method to "trigger" the implicit, as opposed to a "magical" conversion.
Another alternative, if you often call a function with parameters set to Some[_] is to overload it (again, I'm not a fan of overloading either):
def foo(x: Option[String] = None, y: Option[Int] = None, z: Option[String] = None) { /* do stuff */ }
def foo(x: String, y: Int, z: Option[String] = None) = foo(x.some, y.some, z)
On a final note, I don't think there is anything wrong with wrapping your arguments in Some if the function clearly defines them as optional. I wouldn't worry to much about this syntax.
you can define an implicit converter as :
implicit def conv(str: String): Option[String] = {
if (str == null) None else Some(str)
}
def foo(bar: Option[String] = None) = {
bar
}
Output:
scala> foo("xyz")
res55: Option[String] = Some(xyz)
scala> foo()
res1: Option[String] = None
Implicitly converting everything to an Option is a dangerous game that I would avoid! You might prefer simply having some method overloads:
object Foos {
private def foo(bar: Option[String]): Unit = println(bar.getOrElse("foobar"))
def foo(bar: String): Unit = foo(Some(bar))
def foo(): Unit = foo(None)
}
And then you can do:
Foos.foo("barfoo") //prints "barfoo"
Foos.foo() //prints "foobar"
While still only really implementing the method once. Furthermore, you can hide the Option overload this way.
I currently have this:
def stringToOtherType[T: TypeTag](str: String): T = {
if (typeOf[T] =:= typeOf[String])
str.asInstanceOf[T]
else if (typeOf[T] =:= typeOf[Int])
str.toInt.asInstanceOf[T]
else
throw new IllegalStateException()
I would REALLY like to not have the .asInstanceOf[T] if possible (runtime). Is this possible? Removing the asInstanceOf gives me a type of Any, which makes sense, but since we are using reflection and know for sure that I am returning a value of type T, I don't see why we can't have T as a return type, even if we are using reflection at runtime. The code block there without asInstanceOf[T] is never anything but T.
You should not be using reflection here. Instead implicits, specifically the type-class pattern, provide a compile-time solution:
trait StringConverter[T] {
def convert(str: String): T
}
implicit val stringToString = new StringConverter[String] {
def convert(str: String) = str
}
implicit val stringToInt = new StringConverter[Int] {
def convert(str: String) = str.toInt
}
def stringToOtherType[T: StringConverter](str: String): T = {
implicitly[StringConverter[T]].convert(str)
}
Which can be used like:
scala> stringToOtherType[Int]("5")
res0: Int = 5
scala> stringToOtherType[String]("5")
res1: String = 5
scala> stringToOtherType[Double]("5")
<console>:12: error: could not find implicit value for evidence parameter of type StringConverter[Double]
stringToOtherType[Double]("5")
^
How is this possible:
import scala.util.{Try, Success}
import reflect._
case class Foo[A](x: A) extends Dynamic {
def get[T: ClassTag]: Option[T] = Try(x.asInstanceOf[T]) match {
case Success(r) => Some(r)
case _ => None
}
}
object Foo extends App {
val test = Foo("hi")
val wtf: Option[Int] = test.get[Int]
assert(wtf.isInstanceOf[Option[String]])
assert(wtf == Some("hi")) // how????
// val wtf2: Option[String] = wtf // does not compile even if above assert passes!!
}
Inspired by this question: Scala check type of generics
Due to type erasure, wtf.isInstanceOf[Option[String]] can only check that wtf is an instance of Option, but not the type parameter. Similarly, asInstanceOf[T] is actually a cast to Object at the runtime, and so it succeeds. You need to do
classTag[T].runtimeClass.cast(x)
instead.
The compiler can't use the information from asserts passing (you can imagine a compiler which could, but Scala simply isn't designed like that). It only knows that the type of wtf is Option[Int], so of course you can't initialize an Option[String] with it. If you want to get something like that, you need
wtf match {
case wtf2: Option[String] => ...
}
Of course, this doesn't work correctly due to point 1.
I'm beginner in scala and don't understand what happend here :
Given :
val reverse:Option[MyObject] = ...
And myObject.isNaire return Boolean.
If I do :
val v:Option[Boolean] = reverse.map(_.isNaire)
val b:Boolean = v.getOrElse(false)
It work.
Now, If I do :
val b:Boolean = reverse.map(_.isNaire).getOrElse(false)
It fail to compile with a type mismatch: found Any, required Boolean
Edit : Thanks Beryllium, by making SSCCE, I found a beginning of explication. In the first example, myObject is a java class, so isNaire is a java.lang.Boolean. I thought implicit conversion should make this transparent so the explanation is still welcome.
class Test(val naire:java.lang.Boolean)
class Other {
val testValue = Some(new Test(true))
def mysteriousCompilationError:Boolean = testValue.map(_.naire).getOrElse(false)
}
Note: ScalaCompiler is 2.10.2
In the scala.Predef there's an implicit conversion from java.lang.Boolean to scala.Boolean:
implicit def Boolean2boolean(x: java.lang.Boolean): Boolean = x.booleanValue
So in your first case val v:Option[Boolean] = reverse.map(_.isNaire) the compiler see's a java.lang.Boolean and looks for an implicit method in scope to convert it to a scala.Boolean, which it conveniently finds in scala.Predef.
In you're second case, testValue.map(_.naire).getOrElse(false), the compiler is doing things in this order:
Option[Test] => Option[java.lang.Boolean]
getOrElse[B >: A](default: => B): B where A is java.lang.Boolean and B is Any since scala.Boolean is not >: java.lang.Boolean
val b:Boolean, compiler can't find an implicit conversion from Any to scala.Boolean
The only way to get around this, is to tell the compiler during the map operation to use the implicit conversion from scala.Predef to go from java.lang.Boolean to scala.Boolean:
def works:Boolean = testValue.map[Boolean](_.naire).getOrElse(false)
This is a common problem and pops up often since map followed by getOrElse is very convienent. To properly fix this without the extra types, use a fold (catamorphism) over the option:
def worksToo:Boolean = testValue.fold(false)(_.naire)
By using fold you get some added type safety since there's no conversion down to common types. For instance, you can't do this:
def failsTypeCheck = testValue.fold("test")(_.naire)
While the compiler has no problem with this:
def passesTypeCheck = testValue.map(_.naire).getOrElse("test")
java.lang.Boolean and scala.Boolean is not the same. To bridge the gap you have to provide a location where the implicit conversion can do it's work.
There are some patterns to handle these types of Java/Scala interoperability problems:
If it's OK to have a different method to be used from the Scala side, you could use an implicit value class:
object Container {
implicit class Test2Scala(val test: Test) extends AnyVal {
def naireForScala: Boolean = test.naire
}
}
class Other {
val testValue = Some(new Test(true))
import Container._
def mysteriousCompilationError: Boolean =
testValue.map(_.naireForScala).getOrElse(false)
}
This does not require additional instances at run-time. It just provides another method to enrich the Java class.
If you can derive a sub class, you could preserve the method's name by using a DummyImplicit:
class Test2(_naire: Boolean) extends Test(_naire) {
def naire(implicit di: DummyImplicit): Boolean = _naire
}
class Other {
val testValue = Some(new Test2(true))
def mysteriousCompilationError: Boolean =
testValue.map(_.naire).getOrElse(false)
}
The DummyImplicit is required to get a different method signature. It's a bit tricky, requires an additional instance at run-time, but Test2 is a Test (in terms of OOP).
Wrap the Java instance in a Scala instance:
class TestWrapper(test: Test) {
def naire: Boolean = test.naire
}
class Other {
val testValue = Some(new TestWrapper(new Test(true)))
def mysteriousCompilationError: Boolean =
testValue.map(_.naire).getOrElse(false)
}
Requires an additional instance, you have to add delegates, TestWrapper is not a Test, but it's simple.