Sk1tter
Sk1tter

Reputation: 108

Why does scala allow concating Strings with Option[Strings](or any other type)?

I mistakenly concatted a string with an Option[String] while coding in scala. I expected as a strongly typed language, scala would not allow me to do such operation.
This is what I tried.
This works

scala> val a:String = "aaa"
val a: String = aaa

scala> val b:Option[String] = Some("bbbb")
val b: Option[String] = Some(bbbb)

scala> a + b
val res0: String = aaaSome(bbbb)

scala> val c:Option[String] = None
val c: Option[String] = None

scala> val d = a + c
val d: String = aaaNone

scala> val e = 1
val e: Int = 1

scala> a + e
val res2: String = aaa1

while this does not work

scala> val f:Option[String] = Some("ffff")
val f: Option[String] = Some(ffff)

scala> val g:Option[String] = None
val g: Option[String] = None

scala> f + g
           ^
       error: type mismatch;
        found   : Option[String]
        required: String

Why does scala allow such behavior? Dynamically typed languages like python will stop me from adding strings to int types, None types or any type other than strings. Curious if this design is intentional? If so why?

Upvotes: 3

Views: 289

Answers (1)

Andrey Patseev
Andrey Patseev

Reputation: 524

Scala contains an implicit class any2stringadd in it's Predef package. This class is responsible for these concatenation operations.

implicit final class any2stringadd[A](private val self: A) extends AnyVal {
  def +(other: String): String = String.valueOf(self) + other
}

What it means is that, by default, scope contains a method + which can concatenate value of any type A with string by converting value of this type to string via String.valueOf(...).

I can't speak of design choices, I agree that this might be an unexpected behavior. The same applies to Scala's native == method. For example, this code compiles just ok: Some("a") == "b". This can lead to nasty bugs in filtering and other methods.

If you want to eliminate this behavior I suggest you take a look at https://typelevel.org/cats/ library, which introduces different typeclasses that can solve this problem.

For example, for string concatenation you can use Semigroup typeclass (which has tons of other useful use-cases as well):

import cats.Semigroup

Semigroup[String].combine("a", "b") // works, returns "ab"
Semigroup[String].combine("a", Some("b")) // won't work, compilation error

This looks tedious, but there is a syntactic sugar:

import cats.implicits._
"a" |+| "b" // works, returns "ab"
"a" |+| Some("b") // won't work, compilation error
// |+| here is the same as Semigroup[String].combine

The same thing applies to == method. Instead you can use Eq typeclass:

import cats.implicits._
"a" == Some("b") // works, no error, but could be unexpected
"a" === Some("b") // compilation error (Cats Eq)
"a" === "b" // works, as expected
"a" =!= "b" // same as != but type safe

Upvotes: 8

Related Questions