I’m new to Scala.

In Scala 3, shouldn’t the signature be

```
def orElse[B](alternative: => Option[B]): Option[A] | Option[B]
```

instead of

```
def orElse[B >: A](alternative: => Option[B]): Option[B]
```

?

I’m new to Scala.

In Scala 3, shouldn’t the signature be

```
def orElse[B](alternative: => Option[B]): Option[A] | Option[B]
```

instead of

```
def orElse[B >: A](alternative: => Option[B]): Option[B]
```

?

`Option`

is from Scala 2, where there weren’t type unions.

But even if there were, you’d probably want `Option[A | B]`

, because you’re *sure* you have an `Option`

. You don’t want to wonder, “Hm, do I have an option? Or an option?”.

But…if your other option is actually of type `C`

, then if `B`

is `A | C`

it *is* `>: A`

!

So Scala 3 can already infer `Option[A | B]`

in those cases where it decides a type union is a better idea than a supertype.

And it does:

```
scala> Option(3).orElse(Option("eel"))
val res0: Option[Int | String] = Some(3)
```

4 Likes

That’s not the reason, though:

- Since
`Option`

is covariant, then`A | B >: A`

implies`Option[A | B] >: Option[A]`

. - Since
`Option`

only wraps a single value,`Option[A | B]`

and`Option[A] | Option[B]`

are equivalent.

Neither hold for mutable lists, for example.

The second property is not expressible at the type system level, AFAICT, so the compiler can’t simplify type expressions such as `Option[A | B] & !Option[B]`

, which come up during narrowing. The “split form” is superior, in this regard, but I guess it doesn’t matter too much in practice.

Yeah, that was an oversight of mine. I’m glad to see Scala 3 does the right thing!

I guess it’s because Scala 3 still uses Scala 2.13 standard library, and the standard library has been frozen for some years now (before Scala 3 even came out). There are talks of un-freezing it this year or next year: Scala Center Roadmap for 2024 | The Scala Programming Language

Yes, that is correct. But, not sure what is the point you are trying to make.

Conceptually, probably yes *(I am not an expert in type theory and sometimes what seems obvious has deep small details)*.

However, in practice, they are different because of the way the language specs define the use of union types.

If you have the type `Option[A] | Option[B]`

the usage would need to be:

```
data match
case optA: Option[A] =>
optA match
case Some(a) => ???
case None => ???
case optB: Option[B] =>
optB match
case Some(b) => ???
case None => ???
```

Which is uglier and more verbose than if the type is `Option[A | B]`

:

```
data match
case Some(a: A) => ???
case Some(b: B) => ???
case None => ???
```

But, more importantly, the first version is broken due to type erasure.

I was elaborating on why the “divided form” is better than the “fused one”, and why we can still use the fused one with little loss in this particular case.

You’re forgetting that tagged unions are still unions:

```
enum MyOpt1[+A]:
case Some(a: A)
case None
enum MyOpt2[+A]:
case Some(a: A)
case None
def f[A, B](x: MyOpt1[A] | MyOpt2[B]) = x match
case MyOpt1.Some(a) => 1
case MyOpt2.Some(b) => 2
case _ => 0
assert(f(MyOpt1.Some("whatever")) == 1)
assert(f(MyOpt2.Some("whatever")) == 2)
assert(f(MyOpt1.None) == 0)
def g(x: MyOpt1[Int] | MyOpt1[String]): String = x match
case MyOpt1.Some(_: Int) => "int"
case MyOpt1.Some(_: String) => "str"
case MyOpt1.None => "none"
assert(g(MyOpt1.Some(1)) == "int")
assert(g(MyOpt1.Some("s")) == "str")
assert(g(MyOpt1.None) == "none")
```

I guess the answer is because they are equivalent:

(Rewritten to use `Option[A | B2]`

as it makes the algebra simpler, and you claim they are equivalent)

```
def orElse1[B1](alternative: => Option[B1]): Option[A | B1]
def orElse2[B2 >: A](alternative: => Option[B2]): Option[B2]
// (A | B1) >: A, Option[B1] <: Option[A | B1]
orElse2[A | B1](alternative: Option[B1])
// has type Option[A | B1]
// B2 >: A ==> (A | B2) =:= B2
orElse1[B2](alternative: Option[B2])
// has type Option[A | B2] which is equivalent to Option[B2]
```

Furthermore only the second version was expressible in Scala 2, it makes therefore sense that it is in the standard library.

I also find it clearer conceptually, but that might be a habit thing.

1 Like

Yes, you formalized what @Ichoran said.

One difference between the two forms is that the type of `alternative`

is narrower in the union version so, in general, we have better types inside the method implementation (not that it matters in this simple case).

BTW, it’s enough to add an argument to break the equivalence:

```
def f1[B1](a: => B1, f: B1 => B1): Int | B1 = ???
def f2[B2 >: Int](a: => B2, f: B2 => B2): B2 = ???
f1("a", (x: String) => x)
f2("a", (x: String) => x) // type error
```

It’s not really about the number of arguments, rather about their variance, in the original example, all occurences of Bs are in covariant spots, this is no longer the case in your examples.

This can be fixed by adding a contra-variant type parameter:

```
def f3[B31 >: A, B32 <: A](a: => B31, f: B31 => B32): B32 = ???
```

Or of course by making B invariant, but it is then just an alias of A:

```
def f4[B4 >: A <: A](a: => B4, f: B4 => B4): B4 = ???
// equivalent to
def f4(a: => A, f: A => A): A = ???
```

Aren’t you forgetting something?

```
def f1[B1](a: => B1, f: B1 => B1): Int | B1 =
7
```

I should have been more clear, I was showing how to make f2 not throw a type error

I thought this would bring back equivalence but I was incorrect, this is how to make it work:

Instead do

```
f2("a", (x: String | Int) => x)
// expands to
f2[String | Int]("a" /* widened to String | Int */, (x: String | Int) => x)
```

You’ll note this was present in my original reply as well:

I should also clarify that when I said equivalent, I mean that neither can do something the other can’t, this is not to say they are both equally practical ^^’

Sorry for nitpicking, but you’re supposed to make the type error go away by changing the definition of `f2`

, *not* by modifying my counterexample. The fact that `f1`

and `f2`

behave differently while being passed the same exact args was meant to prove that they’re not equivalent.

My claim was/is that there’s no way, at least in Scala, to express

```
def f1[B1](a: => B1, f: B1 => B1): Int | B1 = ???
```

without using unions.

kiuhnm:

The fact that

`f1`

and`f2`

behave differently while being passed the same exact args was meant to prove that they’re not equivalent.

That I don’t dispute !

I think we have different concepts of equivalence, hence the confusion

I meant by equivalence that two things are capable of the same thing, but might require some additional work

For example the following are equivalent in this regard:

```
def foo1[A, B](x: A): B => A = (y: B) => x
def foo2[A, B](x: B): A => B = (y: A) => x
// Any call
foo1[X, Y](bar)
// can be replaced by
foo2[Y, X](bar)
```

But they are of course not equivalent in your definition

(And both definitions are useful)

Therefore, I agree with the following:

kiuhnm:

My claim was/is that there’s no way, at least in Scala, to express

`def f1[B1](a: => B1, f: B1 => B1): Int | B1 = ???`

without using unions.

I hope this clarification was useful ^^

Sporarum:

For example the following are equivalent in this regard:

`def foo1[A, B](x: A): B => A = (y: B) => x def foo2[A, B](x: B): A => B = (y: A) => x // Any call foo1[X, Y](bar) // can be replaced by foo2[Y, X](bar)`

But they are of course not equivalent in your definition

Why not? Whether the type vars are inferred or explicitly given, what matters is whether there is or there isn’t an assignment that makes things equal.

For all `bar, A, B`

, there exist `X, Y`

such that `foo2[X, Y](bar)`

is equivalent to `foo1[A, B](bar)`

, and vice versa, so `foo1`

and `foo2`

are equivalent.

Now, back to our case:

```
def f1[B1](a: => B1, f: B1 => B1): Int | B1 = ???
def f2[B2 >: Int](a: => B2, f: B2 => B2): B2 = ???
f1("a", (x: String) => x)
f2("a", (x: String) => x) // type error
```

Is there any valid assignment to `B2`

that makes `f2[B2]("a", (x: String) => x)`

behave like `f1("a", (x: String) => x)`

? No, there isn’t, so `f1`

and `f2`

are not equivalent, and replacing `(x: String) => x`

with `(x: String | Int) => x`

won’t change that.