chore: Make use of pattern matching on singleton type instead. (#1026)

More info: @som-snytt point out in https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-patterns see singleton.
This commit is contained in:
He-Pin(kerr) 2024-01-27 23:33:06 +08:00 committed by GitHub
parent a2b5b5df22
commit db4f57396d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 60 additions and 29 deletions

View file

@ -15,14 +15,15 @@ package org.apache.pekko.stream
import com.typesafe.config.ConfigFactory
import org.apache.pekko
import org.apache.pekko.stream.ActorAttributes.SupervisionStrategy
import org.apache.pekko.stream.Attributes.SourceLocation
import org.apache.pekko.stream.impl.Stages.DefaultAttributes
import org.apache.pekko.stream.impl.fusing.Collect
import org.apache.pekko.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }
import org.openjdk.jmh.annotations._
import pekko.actor.ActorSystem
import pekko.stream.ActorAttributes.SupervisionStrategy
import pekko.stream.Attributes.SourceLocation
import pekko.stream.impl.Stages.DefaultAttributes
import pekko.stream.impl.fusing.Collect
import pekko.stream.impl.fusing.Collect.NotApplied
import pekko.stream.scaladsl._
import pekko.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }
import java.util.concurrent.TimeUnit
import scala.annotation.nowarn
@ -82,6 +83,7 @@ class CollectBenchmark {
private lazy val decider = inheritedAttributes.mandatoryAttribute[SupervisionStrategy].decider
import Collect.NotApplied
@nowarn("msg=Any")
override def onPush(): Unit =
try {
pf.applyOrElse(grab(in), NotApplied) match {
@ -116,4 +118,28 @@ class CollectBenchmark {
def benchNewCollect(): Unit =
Await.result(newCollect.run(), Duration.Inf)
@nowarn("msg=Any")
def collectOnSingleton[A](a: A, pf: PartialFunction[A, A]): A = pf.applyOrElse(a, NotApplied) match {
case NotApplied => a
case _ => a
}
@nowarn("msg=Any")
def collectOnSingletonType[A](a: A, pf: PartialFunction[A, A]): A = pf.applyOrElse(a, NotApplied) match {
case _: NotApplied.type => a
case _ => a
}
private val string2String: PartialFunction[String, String] = { case a => a }
@Benchmark
@OperationsPerInvocation(OperationsPerInvocation)
def benchCollectOnSingleton(): Unit =
collectOnSingleton("", string2String)
@Benchmark
@OperationsPerInvocation(OperationsPerInvocation)
def benchCollectOnSingletonType(): Unit =
collectOnSingletonType("", string2String)
}

View file

@ -25,6 +25,7 @@ import pekko.stream.impl.Stages.DefaultAttributes
import pekko.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }
import pekko.stream._
import scala.annotation.nowarn
import scala.util.control.NonFatal
/**
@ -43,6 +44,7 @@ private[pekko] final class CollectFirst[In, Out](pf: PartialFunction[In, Out]) e
private lazy val decider = inheritedAttributes.mandatoryAttribute[SupervisionStrategy].decider
import Collect.NotApplied
@nowarn("msg=Any")
override final def onPush(): Unit =
try {
// 1. `applyOrElse` is faster than (`pf.isDefinedAt` and then `pf.apply`)
@ -50,12 +52,11 @@ private[pekko] final class CollectFirst[In, Out](pf: PartialFunction[In, Out]) e
// eg: just a simple `IF_ACMPNE`, and you can find the same trick in `CollectWhile` operator.
// If you interest, you can check the associated PR for this change and the
// current implementation of `scala.collection.IterableOnceOps.collectFirst`.
val result = pf.applyOrElse(grab(in), NotApplied)
if (result.asInstanceOf[AnyRef] eq NotApplied) {
pull(in)
} else {
push(out, result.asInstanceOf[Out])
completeStage()
pf.applyOrElse(grab(in), NotApplied) match {
case _: NotApplied.type => pull(in)
case elem: Out @unchecked =>
push(out, elem)
completeStage()
}
} catch {
case NonFatal(ex) =>

View file

@ -17,6 +17,7 @@
package org.apache.pekko.stream.impl.fusing
import scala.annotation.nowarn
import scala.util.control.NonFatal
import org.apache.pekko
@ -43,6 +44,7 @@ private[pekko] final class CollectWhile[In, Out](pf: PartialFunction[In, Out]) e
private lazy val decider = inheritedAttributes.mandatoryAttribute[SupervisionStrategy].decider
import Collect.NotApplied
@nowarn("msg=Any")
override final def onPush(): Unit =
try {
// 1. `applyOrElse` is faster than (`pf.isDefinedAt` and then `pf.apply`)
@ -50,11 +52,9 @@ private[pekko] final class CollectWhile[In, Out](pf: PartialFunction[In, Out]) e
// eg: just a simple `IF_ACMPNE`, and you can find the same trick in `Collect` operator.
// If you interest, you can check the associated PR for this change and the
// current implementation of `scala.collection.IterableOnceOps.collectFirst`.
val result = pf.applyOrElse(grab(in), NotApplied)
if (result.asInstanceOf[AnyRef] eq NotApplied) {
completeStage()
} else {
push(out, result.asInstanceOf[Out])
pf.applyOrElse(grab(in), NotApplied) match {
case _: NotApplied.type => completeStage()
case elem: Out @unchecked => push(out, elem)
}
} catch {
case NonFatal(ex) =>

View file

@ -14,8 +14,8 @@
package org.apache.pekko.stream.impl.fusing
import java.util.concurrent.TimeUnit.NANOSECONDS
import scala.annotation.nowarn
import scala.annotation.tailrec
import scala.annotation.{ nowarn, tailrec }
import scala.collection.immutable
import scala.collection.immutable.VectorBuilder
import scala.concurrent.Future
@ -236,7 +236,9 @@ private[stream] object Collect {
// Cached function that can be used with PartialFunction.applyOrElse to ensure that A) the guard is only applied once,
// and the caller can check the returned value with Collect.notApplied to query whether the PF was applied or not.
// Prior art: https://github.com/scala/scala/blob/v2.11.4/src/library/scala/collection/immutable/List.scala#L458
final val NotApplied: Any => Any = _ => Collect.NotApplied
object NotApplied extends (Any => Any) {
final override def apply(v1: Any): Any = this
}
}
/**
@ -255,18 +257,17 @@ private[stream] object Collect {
private lazy val decider = inheritedAttributes.mandatoryAttribute[SupervisionStrategy].decider
import Collect.NotApplied
@nowarn("msg=Any")
override def onPush(): Unit =
try {
val result = pf.applyOrElse(grab(in), NotApplied)
// 1. `applyOrElse` is faster than (`pf.isDefinedAt` and then `pf.apply`)
// 2. using reference comparing here instead of pattern matching can generate less and quicker bytecode,
// eg: just a simple `IF_ACMPNE`, and you can find the same trick in `CollectWhile` operator.
// If you interest, you can check the associated PR for this change and the
// current implementation of `scala.collection.IterableOnceOps.collectFirst`.
if (result.asInstanceOf[AnyRef] eq Collect.NotApplied) {
pull(in)
} else {
push(out, result.asInstanceOf[Out])
pf.applyOrElse(grab(in), NotApplied) match {
case _: NotApplied.type => pull(in)
case elem: Out @unchecked => push(out, elem)
}
} catch {
case NonFatal(ex) =>
@ -310,9 +311,10 @@ private[stream] object Collect {
case _ => pull(in)
}
@nowarn("msg=Any")
override def onUpstreamFailure(ex: Throwable): Unit =
try pf.applyOrElse(ex, NotApplied) match {
case NotApplied => failStage(ex)
case _: NotApplied.type => failStage(ex)
case result: T @unchecked => {
if (isAvailable(out)) {
push(out, result)
@ -348,10 +350,11 @@ private[stream] object Collect {
override def onPush(): Unit = push(out, grab(in))
import Collect.NotApplied
@nowarn("msg=Any")
override def onUpstreamFailure(ex: Throwable): Unit = f.applyOrElse(ex, NotApplied) match {
case NotApplied => super.onUpstreamFailure(ex)
case t: Throwable => super.onUpstreamFailure(t)
case _ => throw new IllegalStateException() // won't happen, compiler exhaustiveness check pleaser
case _: NotApplied.type => super.onUpstreamFailure(ex)
case t: Throwable => super.onUpstreamFailure(t)
case _ => throw new IllegalStateException() // won't happen, compiler exhaustiveness check pleaser
}
override def onPull(): Unit = pull(in)
@ -2186,11 +2189,12 @@ private[pekko] object TakeWithin {
override def onPull(): Unit = pull(in)
@nowarn("msg=Any")
def onFailure(ex: Throwable): Unit = {
import Collect.NotApplied
if (maximumRetries < 0 || attempt < maximumRetries) {
pf.applyOrElse(ex, NotApplied) match {
case NotApplied => failStage(ex)
case _: NotApplied.type => failStage(ex)
case source: Graph[SourceShape[T] @unchecked, M @unchecked] if TraversalBuilder.isEmptySource(source) =>
completeStage()
case other: Graph[SourceShape[T] @unchecked, M @unchecked] =>