adpi2
September 4, 2025, 3:13pm
1
I defined an Optional capability like this:
import scala.util.boundary
import scala.util.boundary.*
opaque type Optional = Label[None.type]
object Optional:
def apply[T](f: Optional ?=> T): Option[T] = boundary(Some(f))
def none(using Optional): Nothing = break(None)
extension [T] (x: Option[T])
def getOrBreak(using opt: Optional): T = x.getOrElse(none)
And I would like to call the getOrBreak extension in the Optional context, without an import:
Optional:
xs.map(x => x.getOrBreak)
That does not work because getOrBreak is not a member of the Optional type. Is there a trick to make it work somehow?
Or, would it require to change the rules of Translation of Calls to Extension Methods ? What would be such rule?
If the extension is in the same scope as the opaque type, then both are made available by
import lib.* which I think is not terrible.
The following wraps the extensions in a contextual arg:
import scala.util.boundary, boundary.*
object lib:
opaque type Optional = Label[None.type]
object Optional:
def apply[A](body: (Optional, Ext) ?=> A): Option[A] = boundary(Some(body))
def none(using Optional): Nothing = break(None)
class Ext:
extension [A](x: Option[A]) def getOrBreak(using Optional): A = x.getOrElse(Optional.none)
given Ext()
@main def test = println:
import lib.Optional
val xs = List(Option(42))
Optional:
xs.map(_.getOrBreak)
I also tried the trick of passing a Conversion, but it doesn’t save an import because of the feature import.
adpi2
September 5, 2025, 7:35am
3
This given Ext() does not work for me, I still need the import lib.given.
But you are right that if I put the extension at the same level as Optional, I get everything I need with a single import lib.*.
Still I think it would be nice to have it working with a single import lib.Optional, as it would be the case for a class:
class Optional(using Label[None.type]):
extension [A](x: Option[A]) def getOrBreak A = x.getOrElse(Optional.none)
opaque type are ideal to create capabilities on top of Label, except for this inconvenience that we cannot have extension methods without an import, or a wildcard.
arainko
September 5, 2025, 10:06am
4
I think I managed to do exactly what you’re after:
import scala.util.boundary
import scala.util.boundary.*
opaque type Optional = Label[None.type]
object Optional {
def apply[T](f: (Ext.type, Optional) ?=> T): Option[T] = boundary(Some(f(using Ext)))
def none(using Optional): Nothing = break(None)
}
object Ext {
extension [A](opt: Option[A]) {
def getOrBreak(using Optional): A = opt.getOrElse(Optional.none)
}
}
object test {
val value = Optional {
Option(1).getOrBreak
}
}
the only difference to the previous version (besides Ext being a singleton) is providing Ext directly in Optional.apply: boundary(Some(f(using Ext)))…
But I’m also pretty sure that the OG version with class Ext also works? At least after taking a very brief look
Showing that it works:
➜ snips scala-cli run --server=false -S 3.7.2 -Vprint:typer option-ext.scala
[[syntax trees at end of typer]] // /home/amarki/snips/option-ext.scala
package <empty> {
import scala.util.boundary
import scala.util.boundary.*
final lazy module val lib: lib = new lib()
final module opaque class lib() extends Object() { this: lib.type =>
opaque type Optional = scala.util.boundary.Label[None.type]
final lazy module val Optional: lib.Optional = new lib.Optional()
final module class Optional() extends Object() { this: lib.Optional.type =>
def apply[A >: Nothing <: Any](body: (lib.Optional, lib.Ext) ?=> A):
Option[A] =
scala.util.boundary.apply[Option[A]]((using
contextual$1: scala.util.boundary.Label[Option[A]]) =>
Some.apply[A](body.apply(contextual$1, lib.given_Ext)))
def none(using x$1: lib.Optional): Nothing =
scala.util.boundary.break[None.type](None)(x$1)
}
class Ext() extends Object() {
extension [A >: Nothing <: Any](x: Option[A])(using x$2: lib.Optional)
def getOrBreak: A = x.getOrElse[A](lib.Optional.none(x$2))
}
final lazy module given val given_Ext: lib.given_Ext = new lib.given_Ext()
final module class given_Ext() extends lib.Ext() {
this: lib.given_Ext.type =>}
}
final lazy module val option-ext$package: option-ext$package =
new option-ext$package()
final module class option-ext$package() extends Object() {
this: option-ext$package.type =>
@main def test: Unit =
println(
{
import lib.Optional
val xs: List[Option[Int]] =
List.apply[Option[Int]]([Option.apply[Int](42) : Option[Int]]*)
lib.Optional.apply[List[Int]]((using contextual$2: lib.Optional,
contextual$3: lib.Ext) =>
xs.map[Int]((_$1: Option[Int]) =>
contextual$3.getOrBreak[Int](_$1)(contextual$2))
)
}
)
}
final class test() extends Object() {
<static> def main(args: Array[String]): Unit =
try test catch
{
case error @ _:scala.util.CommandLineParser.ParseError =>
scala.util.CommandLineParser.showError(error)
}
}
}
Some(List(42))
adpi2
September 5, 2025, 11:19am
6
som-snytt:
Showing that it works:
Oh yes it works, it is only that I missed the Ext context param in def apply[A](body: (Optional, Ext) ?=> A): Option[A].
That’s a nice trick indeed and it does what I want.
But then it makes it harder to pass this capability around, as I have to declare two parameters. Still not really satisfying.
You could try wrapping a Label instead of aliasing it?
import scala.util.boundary
import scala.util.boundary.*
class Optional private (using private val label: Label[None.type]):
extension [T] (x: Option[T])
def getOrBreak(using opt: Optional): T = x.getOrElse(Optional.none)
object Optional:
def apply[T](f: Optional ?=> T): Option[T] = boundary(Some(f(using new Optional)))
def none(using optional: Optional): Nothing =
break(None)(using optional.label)
val xs = List(Option(1), Option(2), None)
Optional {
xs.map(x => x.getOrBreak)
}
1 Like