Anton Kuzmin
Anton Kuzmin

Reputation: 831

How to assign to Option only if it is None

In ruby it is possible to do it like so:

first ||= number

and first will get the value of number only if it is nil

How to do similar thing in scala?

So far I've come up with this and it doesn't look as nice as in ruby.

var first: Option[Int] = None
...
first = if (first == None) Option(number) else first
// or
first = Some(first.getOrElse(number))

Upvotes: 4

Views: 622

Answers (3)

0__
0__

Reputation: 67280

I don't know why other poster deleted his answer, but I would go for the approach also suggested in the comments:

first = first orElse Some(number)

Upvotes: 3

yǝsʞǝla
yǝsʞǝla

Reputation: 16412

This is not the best practice or anything, but here is something similar you can do:

scala> implicit class RubyFriend[T](val value: Option[T]) extends AnyVal {
     |   def ||=(alt: T): Option[T] = value orElse Some(alt)
     | }
defined class RubyFriend

scala> var something: Option[Int] = None
something: Option[Int] = None

scala> something = something ||= 5
something: Option[Int] = Some(5)

scala> something ||= 4
res11: Option[Int] = Some(5)

scala> (None: Option[Int]) ||= 3
res12: Option[Int] = Some(3)

We create an implicit conversion from Option to RubyFriend which has ||= method. It's a pimp my library pattern. The only difference that it can't really assign to the original variable because in Scala assignment can't be overloaded as far as I know.

Overall try to avoid vars in Scala.

Here is another attempt to make it even more Ruby like:

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> implicit class RubyFriend[T](var value: Option[T]) {
     |   def ||=(alt: T): Unit = value = value orElse Some(alt)
     | }
defined class RubyFriend

scala> implicit def ruby2Option[T](smth: RubyFriend[T]): Option[T] = smth.value
ruby2Option: [T](smth: RubyFriend[T])Option[T]

scala> var ropt: RubyFriend[Int] = None
ropt: RubyFriend[Int] = RubyFriend@2d483fef

scala> ropt.value
res0: Option[Int] = None

scala> val opt: Option[Int] = ropt
opt: Option[Int] = None

scala> ropt ||= 6

scala> ropt.value
res2: Option[Int] = Some(6)

scala> val opt: Option[Int] = ropt
opt: Option[Int] = Some(6)

scala> ropt ||= 7

scala> ropt.value
res4: Option[Int] = Some(6)

This makes a wrapper class around Option and implicitly converts back and forth. At the same time an optional value is presented as mutable field (yikes).

These both examples are just to show what's possible in Scala not really a good way of doing it :).

A "refinement" of the first example as suggested by @senia:

scala> implicit class RubyFriend[T](val value: Option[T]) extends AnyVal {
     |   def ||(alt: T): Option[T] = value orElse Some(alt)
     | }
defined class RubyFriend

scala> var something: Option[Int] = None
something: Option[Int] = None

scala> something ||= 5

scala> something
res1: Option[Int] = Some(5)

scala> something ||= 6

scala> something
res3: Option[Int] = Some(5)

Upvotes: 3

senia
senia

Reputation: 38045

There is a scalaz operator for this:

import scalaz._, Scalaz._

none[Int] <+> 1.some
// Some(1)

2.some <+> 1.some
// Some(2)

You could use a <+>= b instead of a = a <+> b:

var i = none[Int] // i == None
i <+>= 1.some // i == Some(1)
i <+>= 2.some // i == Some(1)

See also Plus[F[_]] in scalaz cheatsheet.

For Option plus is implemented as orElse.

For collections (List, Vector, Stream, etc) Plus is implemented as append:

Vector(1, 2) <+> Vector(3, 4)
// Vector(1, 2, 3, 4)

I can't find a method to combine Option[T] and T in the way you want, but you could write such method like this:

implicit class PlusT[T, M[_]: Plus : Applicative](m: M[T]) {
  def ?+?(t: T) = m <+> t.point[M]
}

none[Int] ?+? 1
// Some(1)

2.some ?+? 1
// Some(2)

var i = none[Int] // i == None
i ?+?= 1 // i == Some(1)
i ?+?= 2 // i == Some(1)

You could rename this method to || and use it like ||= just like in Ruby, but this name can confuse other developers - there is already method || for Boolean.

Upvotes: 5

Related Questions