Skip to content

introduce ImplicitOptArg #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading