WestCoastProjects
WestCoastProjects

Reputation: 63062

Why is is idiomatic scala to be explicit/verbose about Some[x[/Option[x]?

From the canonical "Programming in Scala" from Oderskey and Venners:

scala> val results = List(Some("apple"), None,
                   Some("orange"))
        results: List[Option[java.lang.String]] = List(Some(apple),
        None, Some(orange))
        scala> for (Some(fruit) <- results) println(fruit)
        apple
        orange

I do not understand the philosophy of scala to impose on the programmer the need to explicitly mention Some(apple) as opposed to inferring it. I would prefer to write/see the following:

scala> val results = List("apple", None, "orange")
        results: List[Option[java.lang.String]] = List(apple, None, orange)
        scala> for (fruit <- results) println(fruit)
        apple
        orange

Or maybe at the least the following (providing typing at the list level but not at the individual item level):

scala> val results :List[Option[String]] = ("apple", None, "orange")
        results: List[Option[java.lang.String]] = List(apple, None, orange)
        scala> for (fruit <- results) println(fruit)
        apple
        orange

At least in this last case: the type of the List is being provided (to help the compiler..) but we still avoid the verbosity of "boxing" every element in the List like Some('foo').

Anyone out there who is better in tune with scala's way of thinking can tell me why I should have to do that extra typing .. ?

Edit: so the following does what I want for Strings.

scala> implicit def toOpt(a : String) = Some(a)
toOpt: (a: String)Some[String]

scala> val  myList : List[Option[String]] = List("first", None, "third")
myList: List[Option[String]] = List(Some(first), None, Some(third))

If someone can show how to generalize the above using higher kinded types I will award the answer.

Upvotes: 1

Views: 495

Answers (5)

scand1sk
scand1sk

Reputation: 1124

For use in limited sections of code, you can use implicits for that:

implicit def string2SomeString(s: String): Option[String] = Some(s)

val l: List[Option[String]] = List("a", None, "b")

You may experience difficulties when ambiguities occur, however.

A more general implicit is doable but may lead to even more ambiguities:

implicit def a2SomeA[A](s: A): Option[A] = Some(s)

Upvotes: 0

Mark Lister
Mark Lister

Reputation: 1143

I don't endorse this but what you seem to be looking for is this:

scala> implicit def aToOptionA[T](a:T):Option[T]=Some(a)
warning: there were 1 feature warning(s); re-run with -feature for details
aToOptionA: [T](a: T)Option[T]

scala> val  myList : List[Option[String]] = List("first", None, "third")
myList: List[Option[String]] = List(Some(first), None, Some(third))

scala> val  myList2:List[Option[Int]]  = List(1, None, 2)
myList2: List[Option[Int]] = List(Some(1), None, Some(2))

Upvotes: 3

om-nom-nom
om-nom-nom

Reputation: 62835

Don't think I'm rude, but the answer is because compiler isn't some magician that is intended to guess your wishes.

It will help you in case there is boilerplate (type inferring, implicit conversions), but the whole point of compiler (at least for a language with typesystem) is to catch ambiguous situations like

val results :List[Option[String]] = ("apple", None, "orange")

and yell on you Hey dude, you must be doing something wrong!. You're basically asking why the compiler does not allow me to assign dog to the cat, orange to the apple or trick sorter game and when language allows such tricks some very bad and nasty things are happen.

for (fruit <- results) println(fruit)

How should language know that you want to print underlying unboxed value and not the option itself? What should it do if you use some other function? What if I have Option[Option[String]]? You see, there are many things to consider and if we really deem extra typing as a real problem in this particular case we have to sit down and enumerate all those cases in large book and everyone has to check it out to understand every possible outcome otherwise code is unpredictable. Note also, that there will be hidden performance sinks: e.g. compiler will implicitly convert one collection to another, they're different classes and different structures after all and surprise -- you don't know where. At least without some extra look on your code. After all, why don't you expect

val i: List[Int] = 3

to be valid?

Upvotes: 4

Ankur
Ankur

Reputation: 33637

Because it will lead to some serious ambiguity.

Check the below snippet (based on your suggest sample).

List("apple", "hello", "orange")

Is the above code List[Option[String]] or List[String], well both are valid types, which one to choose?

UPDATE:

Lets take another case class example (Option is a case class with 2 cases Some and None):

abstract class Superhero
case class Superman(health : Int) extends Superhero
case class Spiderman(health: Int) extends Superhero

Now we are faced with another ambiguity, if we say: List[Superhero](100,100), Is the first 100 for Superman or is it for Spiderman, same to next 100.

Upvotes: 3

flavian
flavian

Reputation: 28511

In addition to Ankur's answer, Option is a Monad in Scala, means it fully implements map, flatMap.

This way you can use it in for comprehensions:

   def sum(a: Some[Int], b: Some[Int]): Int = {
      for {
        a <- maybeA
        b <- maybeB
       ...
      } yield {
        a + b
      }
    } getOrElse 0 

Or map over it's contents: val a = Some(5); a map (3 + ) // gives you Some(8)

The type signatures of monadic methods are complicated and difficult for the compiler to deal with.

More ambiguity over identity between the Optional[A] and A is useless and simply not possible.

Verbosity

Your claim is not well grounded. In practice you will find scala.Option is extremely powerful.

Say you have:

def methodA: Option[ComplexType]
def methodB: Option[MoreComplexType]
def methodC: Option[InsanelyComplexStructure]

Yet to chain and check for existence, all you need is a comprehension:

for {
   a <- methodA
   b <- methodB
   c <- methodC
} yield {
   // now this gets executed only if all the 3 methods return Some()
}

This becomes extremely powerful. Deeply nested if statements are a bad old memory.

Upvotes: 4

Related Questions