Skip to content

Commit

Permalink
introduce ImplicitOptArg
Browse files Browse the repository at this point in the history
  • Loading branch information
halotukozak committed Dec 6, 2024
1 parent c01929f commit 74db107
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 80 deletions.
18 changes: 0 additions & 18 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* Lightweight alternatives for Scala `Option` -
[`Opt`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/misc/Opt.html) - guarantees no `null`s,
[`OptArg`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/misc/OptArg.html),
[`ImplicitOptArg`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/misc/ImplicitOptArg.html),
[`NOpt`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/misc/NOpt.html),
[`OptRef`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/misc/OptRef.html) (implemented as value
classes)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.avsystem.commons
package jiop

import java.{util => ju}
import java.util as ju

trait JOptionalUtils {

import JOptionalUtils._
import JOptionalUtils.*

type JOptional[T] = ju.Optional[T]
type JOptionalDouble = ju.OptionalDouble
Expand Down Expand Up @@ -37,6 +37,9 @@ trait JOptionalUtils {
implicit def optArg2AsJava[T](opt: OptArg[T]): optArg2AsJava[T] =
new optArg2AsJava((opt: OptArg[Any]).orNull)

implicit def implicitOptArg2AsJava[T](opt: ImplicitOptArg[T]): implicitOptArg2AsJava[T] =
new implicitOptArg2AsJava((opt: ImplicitOptArg[Any]).orNull)

implicit def option2AsJavaDouble(option: Option[Double]): option2AsJavaDouble =
new option2AsJavaDouble(option)

Expand Down Expand Up @@ -166,6 +169,18 @@ object JOptionalUtils {

def asJava: JOptional[T] = toJOptional
}
final class implicitOptArg2AsJava[T](private val rawOrNull: Any) extends AnyVal {
private def opt: ImplicitOptArg[T] = ImplicitOptArg(rawOrNull.asInstanceOf[T])

/**
* Note that in scala Some(null) is valid value. It will throw an exception in such case, because java Optional
* is not able to hold null
*/
def toJOptional: JOptional[T] =
if (opt.isDefined) ju.Optional.of(opt.get) else ju.Optional.empty()

def asJava: JOptional[T] = toJOptional
}

final class option2AsJavaDouble(private val option: Option[Double]) extends AnyVal {
def toJOptionalDouble: JOptionalDouble =
Expand Down
13 changes: 9 additions & 4 deletions core/src/main/scala/com/avsystem/commons/SharedExtensions.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.avsystem.commons

import com.avsystem.commons.concurrent.RunNowEC
import com.avsystem.commons.misc._
import com.avsystem.commons.misc.*

import scala.annotation.{nowarn, tailrec}
import scala.collection.{AbstractIterator, BuildFrom, Factory, mutable}

trait SharedExtensions {

import com.avsystem.commons.SharedExtensionsUtils._
import com.avsystem.commons.SharedExtensionsUtils.*

implicit def universalOps[A](a: A): UniversalOps[A] = new UniversalOps(a)

Expand Down Expand Up @@ -416,6 +416,11 @@ object SharedExtensionsUtils extends SharedExtensions {
*/
def toOptArg: OptArg[A] =
if (option.isEmpty) OptArg.Empty else OptArg(option.get)
/**
* Converts this `Option` into `ImplicitOptArg`. Because `ImplicitOptArg` cannot hold `null`, `Some(null)` is translated to `OptArg.Empty`.
*/
def toImplicitOptArg: ImplicitOptArg[A] =
if (option.isEmpty) ImplicitOptArg.Empty else ImplicitOptArg(option.get)

/**
* Apply side effect only if Option is empty. It's a bit like foreach for None
Expand Down Expand Up @@ -502,7 +507,7 @@ object SharedExtensionsUtils extends SharedExtensions {

class PartialFunctionOps[A, B](private val pf: PartialFunction[A, B]) extends AnyVal {

import PartialFunctionOps._
import PartialFunctionOps.*

/**
* The same thing as `orElse` but with arguments flipped.
Expand Down Expand Up @@ -638,7 +643,7 @@ object SharedExtensionsUtils extends SharedExtensions {

class MapOps[M[X, Y] <: BMap[X, Y], K, V](private val map: M[K, V]) extends AnyVal {

import MapOps._
import MapOps.*

def getOpt(key: K): Opt[V] = map.get(key).toOpt

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ object OptionLike {
implicit def optArgOptionLike[A]: BaseOptionLike[OptArg[A], A] =
new OptionLikeImpl(OptArg.Empty, OptArg.some, _.isDefined, _.get, ignoreNulls = true)

implicit def implicitOptArgOptionLike[A]: BaseOptionLike[ImplicitOptArg[A], A] =
new OptionLikeImpl(ImplicitOptArg.Empty, ImplicitOptArg.some, _.isDefined, _.get, ignoreNulls = true)

implicit def nOptOptionLike[A]: BaseOptionLike[NOpt[A], A] =
new OptionLikeImpl(NOpt.Empty, NOpt.some, _.isDefined, _.get, ignoreNulls = false)
}
Expand Down
132 changes: 132 additions & 0 deletions core/src/main/scala/com/avsystem/commons/misc/ImplicitOptArg.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.avsystem.commons.misc

object ImplicitOptArg {
/**
* This implicit conversion allows you to pass unwrapped values where `OptArg` is required.
*/
implicit def argToImplicitOptArg[A](value: A): ImplicitOptArg[A] = ImplicitOptArg(value)
implicit def implicitArgToImplicitOptArg[A](implicit value: A): ImplicitOptArg[A] = ImplicitOptArg(value)

// additional implicits to cover most common, safe numeric promotions
implicit def intToOptArgLong(int: Int): ImplicitOptArg[Long] = ImplicitOptArg(int)
implicit def intToOptArgDouble(int: Int): ImplicitOptArg[Double] = ImplicitOptArg(int)

private object EmptyMarker extends Serializable

def apply[A](value: A): ImplicitOptArg[A] = new ImplicitOptArg[A](if (value != null) value else EmptyMarker)
def unapply[A](opt: ImplicitOptArg[A]): ImplicitOptArg[A] = opt //name-based extractor

def some[A](value: A): ImplicitOptArg[A] =
if (value != null) new ImplicitOptArg[A](value)
else throw new NullPointerException

val Empty: ImplicitOptArg[Nothing] = new ImplicitOptArg(EmptyMarker)
def empty[A]: ImplicitOptArg[A] = Empty
}

/**
* [[ImplicitOptArg]] is like [[OptArg]] except it's intended to be used to type-safely express optional implicit method/constructor
* parameters while at the same time avoiding having to explicitly wrap arguments when passing them
* (thanks to the implicit conversion from `implicit A` to `ImplicitOptArg[A]`). For example:
*
* {{{
* def takesMaybeString(implicit str: ImplicitOptArg[String]) = ???
*
* implicit val str: String = "string"
* takesMaybeString() // str is used
* takesMaybeString(using str) // str is used explicitly
* takesMaybeString(using ImplicitOptArg.Empty) // Empty is used explicitly
* }}}
*
* Note that like [[Opt]], [[ImplicitOptArg]] assumes its underlying value to be non-null and `null` is translated into `ImplicitOptArg.Empty`.
* <br/>
* It is strongly recommended that [[ImplicitOptArg]] type is used without default argument.
* You should not use [[ImplicitOptArg]] as a general-purpose "optional value" type - other types like
* [[Opt]], [[NOpt]] and `Option` serve that purpose. For this reason [[ImplicitOptArg]] deliberately does not have any "transforming"
* methods like `map`, `flatMap`, `orElse`, etc. Instead it's recommended that [[ImplicitOptArg]] is converted to [[Opt]],
* [[NOpt]] or `Option` as soon as possible (using `toOpt`, `toNOpt` and `toOption` methods).
*/
final class ImplicitOptArg[+A] private(private val rawValue: Any) extends AnyVal with OptBase[A] with Serializable {

import ImplicitOptArg.*

private def value: A = rawValue.asInstanceOf[A]

@inline def get: A = if (isEmpty) throw new NoSuchElementException("empty ImplicitOptArg") else value

@inline def isEmpty: Boolean = rawValue.asInstanceOf[AnyRef] eq EmptyMarker
@inline def isDefined: Boolean = !isEmpty
@inline def nonEmpty: Boolean = isDefined

@inline def boxedOrNull[B >: Null](implicit boxing: Boxing[A, B]): B =
if (isEmpty) null else boxing.fun(value)

@inline def toOpt: Opt[A] =
if (isEmpty) Opt.Empty else Opt(value)

@inline def toOption: Option[A] =
if (isEmpty) None else Some(value)

@inline def toNOpt: NOpt[A] =
if (isEmpty) NOpt.Empty else NOpt.some(value)

@inline def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] =
if (isEmpty) OptRef.Empty else OptRef(boxing.fun(value))

@inline def getOrElse[B >: A](default: => B): B =
if (isEmpty) default else value

@inline def orNull[B >: A](implicit ev: Null <:< B): B =
if (isEmpty) ev(null) else value

@inline def fold[B](ifEmpty: => B)(f: A => B): B =
if (isEmpty) ifEmpty else f(value)

/**
* The same as [[fold]] but takes arguments in a single parameter list for better type inference.
*/
@inline def mapOr[B](ifEmpty: => B, f: A => B): B =
if (isEmpty) ifEmpty else f(value)

@inline def contains[A1 >: A](elem: A1): Boolean =
!isEmpty && value == elem

@inline def exists(p: A => Boolean): Boolean =
!isEmpty && p(value)

@inline def forall(p: A => Boolean): Boolean =
isEmpty || p(value)

@inline def foreach[U](f: A => U): Unit = {
if (!isEmpty) f(value)
}

@inline def iterator: Iterator[A] =
if (isEmpty) Iterator.empty else Iterator.single(value)

@inline def toList: List[A] =
if (isEmpty) List() else value :: Nil

@inline def toRight[X](left: => X): Either[X, A] =
if (isEmpty) Left(left) else Right(value)

@inline def toLeft[X](right: => X): Either[A, X] =
if (isEmpty) Right(right) else Left(value)

/**
* Apply side effect only if ImplicitOptArg is empty. It's a bit like foreach for ImplicitOptArg.Empty
*
* @param sideEffect - code to be executed if optArg is empty
* @return the same ImplicitOptArg
* @example {{{captionOptArg.forEmpty(logger.warn("caption is empty")).foreach(setCaption)}}}
*/
@inline def forEmpty(sideEffect: => Unit): ImplicitOptArg[A] = {
if (isEmpty) {
sideEffect
}
this
}

override def toString: String =
if (isEmpty) "ImplicitOptArg.Empty" else s"ImplicitOptArg($value)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ trait MiscAliases {
final val Opt = com.avsystem.commons.misc.Opt
type OptArg[+A] = com.avsystem.commons.misc.OptArg[A]
final val OptArg = com.avsystem.commons.misc.OptArg
type ImplicitOptArg[+A] = com.avsystem.commons.misc.ImplicitOptArg[A]
final val ImplicitOptArg = com.avsystem.commons.misc.ImplicitOptArg
type NOpt[+A] = com.avsystem.commons.misc.NOpt[A]
final val NOpt = com.avsystem.commons.misc.NOpt
type OptRef[+A >: Null] = com.avsystem.commons.misc.OptRef[A]
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/scala/com/avsystem/commons/misc/NOpt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ object NOpt {
*/
final class NOpt[+A] private(private val rawValue: Any) extends AnyVal with OptBase[A] with Serializable {

import NOpt._
import NOpt.*

private def value: A = (if (rawValue.asInstanceOf[AnyRef] eq NullMarker) null else rawValue).asInstanceOf[A]

Expand Down Expand Up @@ -83,6 +83,9 @@ final class NOpt[+A] private(private val rawValue: Any) extends AnyVal with OptB
@inline def toOptArg: OptArg[A] =
if (isEmpty) OptArg.Empty else OptArg(value)

@inline def toImplicitOptArg: ImplicitOptArg[A] =
if (isEmpty) ImplicitOptArg.Empty else ImplicitOptArg(value)

@inline def getOrElse[B >: A](default: => B): B =
if (isEmpty) default else value

Expand Down Expand Up @@ -142,7 +145,7 @@ final class NOpt[+A] private(private val rawValue: Any) extends AnyVal with OptB
if (isEmpty) Iterator.empty else Iterator.single(value)

@inline def toList: List[A] =
if (isEmpty) List() else new ::(value, Nil)
if (isEmpty) List() else new::(value, Nil)

@inline def toRight[X](left: => X): Either[X, A] =
if (isEmpty) Left(left) else Right(value)
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/com/avsystem/commons/misc/Opt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ final class Opt[+A] private(private val rawValue: Any) extends AnyVal with OptBa
@inline def toOptArg: OptArg[A] =
if (isEmpty) OptArg.Empty else OptArg(value)

@inline def toImplicitOptArg: ImplicitOptArg[A] =
if (isEmpty) ImplicitOptArg.Empty else ImplicitOptArg(value)

@inline def getOrElse[B >: A](default: => B): B =
if (isEmpty) default else value

Expand Down
6 changes: 5 additions & 1 deletion core/src/main/scala/com/avsystem/commons/misc/OptArg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ object OptArg {
* [[NOpt]] or `Option` as soon as possible (using `toOpt`, `toNOpt` and `toOption` methods).
*/
final class OptArg[+A] private(private val rawValue: Any) extends AnyVal with OptBase[A] with Serializable {

import OptArg._

private def value: A = rawValue.asInstanceOf[A]
Expand All @@ -69,6 +70,9 @@ final class OptArg[+A] private(private val rawValue: Any) extends AnyVal with Op
@inline def toOptRef[B >: Null](implicit boxing: Boxing[A, B]): OptRef[B] =
if (isEmpty) OptRef.Empty else OptRef(boxing.fun(value))

@inline def toImplicitOptArg: ImplicitOptArg[A] =
if (isEmpty) ImplicitOptArg.Empty else ImplicitOptArg(value)

@inline def getOrElse[B >: A](default: => B): B =
if (isEmpty) default else value

Expand Down Expand Up @@ -101,7 +105,7 @@ final class OptArg[+A] private(private val rawValue: Any) extends AnyVal with Op
if (isEmpty) Iterator.empty else Iterator.single(value)

@inline def toList: List[A] =
if (isEmpty) List() else new ::(value, Nil)
if (isEmpty) List() else new::(value, Nil)

@inline def toRight[X](left: => X): Either[X, A] =
if (isEmpty) Left(left) else Right(value)
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/com/avsystem/commons/misc/OptRef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ final class OptRef[+A >: Null] private(private val value: A) extends AnyVal with
@inline def toOptArg: OptArg[A] =
if (isEmpty) OptArg.Empty else OptArg(value)

@inline def toImplicitOptArg: ImplicitOptArg[A] =
if (isEmpty) ImplicitOptArg.Empty else ImplicitOptArg(value)

@inline def getOrElse[B >: A](default: => B): B =
if (isEmpty) default else value

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
private implicit class IterableOps[A](private val coll: BIterable[A]) extends AnyVal {
def writeToList(lo: ListOutput)(implicit writer: GenCodec[A]): Unit = {
lo.declareSizeOf(coll)
coll.foreach(new (A => Unit) {
coll.foreach(new(A => Unit) {
private var idx = 0
def apply(a: A): Unit = {
try writer.write(lo.writeElement(), a) catch {
Expand Down Expand Up @@ -599,6 +599,9 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
implicit def optArgCodec[T: GenCodec]: GenCodec[OptArg[T]] =
new Transformed[OptArg[T], Opt[T]](optCodec[T], _.toOpt, _.toOptArg)

implicit def implicitOptArgCodec[T: GenCodec]: GenCodec[ImplicitOptArg[T]] =
new Transformed[ImplicitOptArg[T], Opt[T]](optCodec[T], _.toOpt, _.toImplicitOptArg)

implicit def optRefCodec[T >: Null : GenCodec]: GenCodec[OptRef[T]] =
new Transformed[OptRef[T], Opt[T]](optCodec[T], _.toOpt, _.toOptRef)

Expand Down
Loading

0 comments on commit 74db107

Please sign in to comment.