Treat None like false

I have two function calls which I may preform. One of them is sure to return None and the other will return Some(...). Is there an or-like operator for options which I can do the analogous to f1() || f2()? This means that if and only if f1() returns None then f2() is called and the non-None value is returned.

Of course I can write logic for this, but it seems awkward, like that must be an easier way.

val opt = recur(positive, Assignment(assignTrue.trueVariables + label),assignFalse)
if (opt.nonEmpty)
  opt
else
  recur(negative, assignTrue, Assignment(assignFalse.trueVariables + label))

It seems that `||`` would express the intent better.

recur(positive, Assignment(assignTrue.trueVariables + label),assignFalse)
|| recur(negative, assignTrue, Assignment(assignFalse.trueVariables + label))

but alas, || is not defined for Option. :frowning:

I think the method you are looking for is Option.orElse.

Its parameter is by-name, so in f1() orElse f2() it only calls f2() if f1() returns None.

2 Likes

Why didn’t they just name it || ? Is there a corresponding andAlso which evals and returns the second only if the first is non-None similar to && ? Admittedly, I can’t think of a use case.

Do you need the value?

Otherwise:

implicit def convert(input:Option[A]) = input.isDefined

Yes, I need to return the actual Option, not a Boolean.

The general form is opt1 ||| opt2 ||| opt3 ... ||| optN returns the first option which is not None.

Perhaps I’m thinking lisp/scheme where we do a lot of nil-punning. since nil is simultaneously the false value and also the empty value. an expression such as

(or (f1) (f2) (f3) ... (fN))

calls the functions from left to right, but terminating on the first one which does not return nil and returns its value. Turns out this happens to have the correct semantics if all the functions return boolean. (thus the pun)

Sounds like ||| (and the analogous &&&) would be pretty easy to implement using extension methods in Scala 3?

The standard library prefers names consisting of letters over symbolic operators in most cases, hence no ||| or somesuch.

There is no andAlso with the semantics you describe, though there is zip(that: Option[B]): Option((A, B)) which is captures both. zip also exists on collections.

You can chain orElse in the way you describe, opt1 orElse opt2 orElse opt3 orElse opt4 will work.

There is no standard library method to get the first definded element of a sequence of options, though .flatten.headOption is pretty close, as is .collectFirst { case Some(v) => v }

If you have a dependency on cats, .foldK does the same. I’m not sure if I would recommend that though.

1 Like

I’m challenged now to think of a use case for andAlso as it is the logical dual of orElse.

I agree with @martijnhoekstra that && is probably more like zip, but the thing you describe sounds like a.flatMap(_ => b). I think Cats has a special operator for that: a *> b.

I’m not familiar with Cats so I don’t know whether *> is close. But I don’t quite see the connection of what I’m referring to and flatMap. The operation is basically to evaluate a sequence of expressions until one fails, and return the final (possibly failed) result. Moreover, measure failure in terms of whether the computation returns Some or None. Of course I see how to write that using pass-by-name arguments, but these would be called using prefix syntax. I don’t see how to do it using infix syntax.

def andAlso[T](a:Option[T],b: => Option[T]):Option[T] = {
  a match {
    case None => None
    case _ => b
  }
}

def test(n:Int):Option[Int] = {
  println(s"testing $n")
  if (n==0)
    None
  else
    Some(n)
}

andAlso(test(1),
  andAlso(test(2),
    andAlso(test(3),
      andAlso(test(0),
        andAlso(test(4),test(5))))))

returns None and prints the following

testing 1
testing 2
testing 3
testing 0

while

andAlso(test(1),
  andAlso(test(2),
    andAlso(test(3),
      andAlso(test(4),test(5)))))

returns Some(5) prints the following

testing 1
testing 2
testing 3
testing 4
testing 5

That being said, I admit that I don’t have a convincing use case. It’s just the logical dual of a real use case.

The connection with flatMap is that the implementation is identical to flatMap(_ => b): https://scastie.scala-lang.org/bUTbOHoXRECLW0ZHGFVJYg

3 Likes

I can’t think of a convincing example for Option, either. It would mean your computation requires the first value(s) to be present, without having any need for the actual value(s) itself. That’s a strange spot to be in.

The use case for *> usually enters with stateful APIs, where you first want to execute one action (likely returning a Unit-based result, anyway) for its side effects only, then, upon success, another one that yields a proper result. Simple Haskell IO example:

Prelude> putStrLn "please enter a color" *> getLine
please enter a color
blue
"blue"

Similar for cats Writer:

type Logging[T] = Writer[Vector[String], T]

def log(msg: String): Logging[Unit] = Vector(msg).tell
def calc[M[_] : Applicative](a: Int, b: Int): M[Int] =
  implicitly[Applicative[M]].pure(a + b)

val prog = log("calculating") *> calc[Logging](3, 4)
println(prog.run) // (Vector(calculating),7)

This makes my brain hurt. It is very subtle. Why isn’t b evaluated in the flatMap based solution? This was really puzzling. The reason is that the argument of flatMap is something like lambda(x){b}. So b is not passed to flatMap but rather a closure containing a reference to b.

The semantics of closing over a call-by-name parameter is something I had never considered before now.

def andAlso[T](a:Option[T],b: => Option[T]):Option[T] = a.flatMap(_ => b)

It’s not really related to call by name parameters except in the sense that they are syntactic sugar for passing a nullary function. It’s because flatMap for Option is short-circuiting:

def flatMap[B](f: A => Option[B]): Option[B] =
    if (isEmpty) None else f(this.get)

In other words, if it is called on None then the result is simply None and the function which was provided is not applied. This allows you to chain calls to flatMap which will only be applied if the previous operations returned Some.

So, when flatMap is used to return a constant value it can be used to implement something similar to Boolean || for Option.

1 Like

By name arguments behave as def's.

given

def sideeffect = {
  println("I'm a sideeffect")
  42
}

None.map(_ => sideeffect) isn’t going to print anything, but List(1, 2, 3)map(_ => sideeffect) is going to print it 3 times.

1 Like

Hello,

in scalaz you can choose which option monoid to use to reduce your options. You can use the default monoid, which combines the contents of your options using the default monoid for the contents. Or you can use the optionFirst monoid, which lets the first not-undefined option win, or you can use the optionLast monoid, which lets the last not-undefined option win. In your case you would use the optionFirst monoid.

import scalaz._
import scalaz.Scalaz._

// use default Monoid for Option[Int] (combines contents using [Int,+,0] Monoid)
assert( (2.some |+| 3.some |+| 4.some) == 9.some )
assert( List(2.some, 3.some, 4.some).sumr  == 9.some )

// use optionFirst Monoid - first not undefined value wins
assert( (2.some.first |+| 3.some.first |+| 4.some.first) == 2.some.first )
assert( (List(2.some, 3.some, 4.some).foldMap(_.first))  == 2.some.first )

// use optionLast Monoid - last not undefined value wins
assert( (2.some.last |+| 3.some.last |+| 4.some.last) == 4.some.last )
assert( (List(2.some, 3.some, 4.some).foldMap(_.last))  == 4.some.last )

See slides 30-34 in https://www.slideshare.net/pjschwarz/monoids-with-examples-using-scalaz-and-cats-part-1#30

*> is the right shark or right-facing bird. It is provided by the Applicative Functor. It eats/discards its left argument.

Applicative Functor is explained in https://www.slideshare.net/pjschwarz/applicative-functor-116035644

*> is explained on slide 30 of https://www.slideshare.net/pjschwarz/applicative-functor-part-2

Applicative Functor in scalaz with an example of using *> is shown in https://www.slideshare.net/pjschwarz/applicative-functor-part-3

I don’t think I agree with this claim. It is absolutely related to the call-by-name semantics. The argument to the function doesn’t get evaluated in some cases. Without call-by-name all arguments would be evaluated.

If instead, andAlso were implemented as follows, then all the options would be computed and all the side effects would happen before the logic of which value to return.

def andAlso[T](a:Option[T],b: Option[T]):Option[T] = a.flatMap(_ => b)

andAlso(test(1),
  andAlso(test(2),
    andAlso(test(3),
      andAlso(test(0),
        andAlso(test(4),test(5))))))

in this call, all the side effects happen. It is completely independent of the implementation of flatMap. All the side affects happen before flatMap is called for the first time. The question of whether flatMap is short circuiting plays no role.

testing 1
testing 2
testing 3
testing 0
testing 4
testing 5

The argument does get evaluated - it evaluates to a function of type A => Option[B], which is represented as an object on the JVM. This function is simply never called by the implementation of flatMap when the Option is None. :slight_smile:

I think I see what you mean. Except that I don’t know what you mean by A in your claim that the function type is A => Option[B]. Perhaps that’s a typo? But from the Scala point of view, there is no function. The fact that it happens to be implemented at the java level as a function is not relavent to the Scala semantics. Any evidence that there is an underlying function is an ugly leaky abstraction.

OK, that’s my opinion, I accept that others might view things differently.