The right way to work with two instances of Option

When I have one instance of Option[T] , it’s pretty easy to do any operation on T using monadic operations like map() and flatMap() . Thus, I do not need to do checks to determine if it is defined or empty, and chain operations together, in order to eventually get Option[R] for the result of R

My difficulty is whether there is such an elegant way to execute functions on two instances of Option[T] .

Let's look at a simple example where I have two vals, x and y type Option[Int] . And I want to get the maximum of them if they are both defined, or one that is defined if only one is defined, and None if neither is defined.

How could one write this gracefully without involving a large number of isDefined checks inside map() first Option ?

+5
source share
8 answers

You can use something like this:

 def optMax(op1:Option[Int], op2: Option[Int]) = op1 ++ op2 match { case Nil => None case list => list.max } 

Or much better:

 def f(vars: Option[Int]*) = (for( vs <- vars) yield vs).max 

@jwvh, thanks for the nice improvement:

 def f(vars: Option[Int]*) = vars.max 
+5
source

Usually you want to do something if both values ​​are defined. In this case, you can use to understand:

 val aOpt: Option[Int] = getIntOpt val bOpt: Option[Int] = getIntOpt val maxOpt: Option[Int] = for { a <- aOpt b <- bOpt } yield max(a, b) 

Now the problem you described is not so common. You want to do something if both values ​​are defined, but you also want to get the parameter value if only one of them is defined.

I would just use the above understanding and then hook two calls to orElse to provide alternative values ​​if maxOpt turns out to be None .

 maxOpt orElse aOpt orElse bOpt 

orElse signature:

 def orElse[B >: A](alternative: ⇒ Option[B]): Option[B] 
+3
source

Here's another fwiw:

 import scala.util.Try def maxOpt (a:Option[Int]*)= Try(a.flatten.max).toOption 

It works with n arguments (including null arguments).

+2
source

Matching the pattern will make it easy to understand, but it may not be the most elegant way:

 def maxOpt[T](optA: Option[T], optB: Option[T])(implicit f: (T, T) => T): Option[T] = (optA, optB) match { case (Some(a), Some(b)) => Some(f(a, b)) case (None, Some(b)) => Some(b) case (Some(a), None) => Some(a) case (None, None) => None } 

You end up with something like:

 scala> maxOpt(Some(1), None)(Math.max) res2: Option[Int] = Some(1) 

Once you have this building, block, you can use it inside for-comp or monadic.

+1
source

To get maxOpt, you can also use an applicative application that with Scalaz will look like (aOpt | @ | bOpt) {max (_, _)} and then the orElses chain as suggested by @dcastro.

+1
source

I assume that you expect Some[Int]|None as a result, not Int|None (otherwise the return type should be Any ):

  def maxOption(opts: Option[Int]*) = { val flattened = opts.flatten flattened.headOption.map { _ => flattened.max } } 
+1
source

Actually, Scala already gives you this ability more or less directly.

 scala> import Ordering.Implicits._ import Ordering.Implicits._ scala> val (a,b,n:Option[Int]) = (Option(4), Option(9), None) a: Option[Int] = Some(4) b: Option[Int] = Some(9) n: Option[Int] = None scala> a max b res60: Option[Int] = Some(9) scala> a max n res61: Option[Int] = Some(4) scala> n max b res62: Option[Int] = Some(9) scala> n max n res63: Option[Int] = None 
+1
source

A Haskell-ish takes over this question to notice that the following operations:

 max, min :: Ord a => a -> a -> a max ab = if a < b then b else a min ab = if a < b then a else b 

... associative :

 max a (max bc) == max (max ab) c min a (min bc) == min (min ab) c 

Thus, any type Ord a => a together with any of these operations is a semigroup , a concept for which reusable abstractions can be created.

And you are dealing with Maybe (Haskell for "option"), which adds a common "neutral" element to the base type a (you want max Nothing x == x executed as law). This will lead you to monoids , which are a subtype of semigroups.

The Haskell semigroups library provides a class of type Semigroup and two shell types, Max and Min , which generally implement the corresponding behavior.

Since we are dealing with Maybe , in terms of this library, the type that captures the semantics you want is Option (Max a) -a monoid, which has the same binary operation as the Max semigroup and uses Nothing as a single element. So the function just becomes:

 maxOpt :: Ord a => Option (Max a) -> Option (Max a) -> Option (Max a) maxOpt ab = a <> b 

... which, since it is just a <> operator for Option (Max a) , is not worth writing. You also get all the other utility functions and classes that work on Semigroup and Monoid , so for example, to find the maximum element [Option (Max a)] , you simply use the mconcat function .

The scalaz library comes with Semigroup and Monoid , as well as the Max , Min , MaxVal and MinVal tags that implement these features, so in fact the material that I demonstrated here in Haskell also exists in scalas.

+1
source

Source: https://habr.com/ru/post/1235627/


All Articles