-
-
Notifications
You must be signed in to change notification settings - Fork 203
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add as extension method to optics in Scala 2 and 3 (#1110)
* add as extension method to optics in Scala 2 and 3 * use extension syntax in Scala 3 * use as syntax within GenPrism
- Loading branch information
1 parent
e2fb3ca
commit 58bd6f6
Showing
8 changed files
with
166 additions
and
5 deletions.
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
core/shared/src/main/scala-2.x/monocle/syntax/MacroSyntax.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package monocle.syntax | ||
|
||
trait MacroSyntax |
47 changes: 47 additions & 0 deletions
47
core/shared/src/main/scala-3.x/monocle/syntax/MacroSyntax.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package monocle.syntax | ||
|
||
import monocle.{Focus, Fold, Iso, Optional, Prism, Setter, Traversal} | ||
|
||
import scala.quoted.{Expr, Quotes, Type} | ||
|
||
trait MacroSyntax { | ||
|
||
extension [From, To] (optic: Prism[From, To]) { | ||
inline def as[CastTo <: To]: Prism[From, CastTo] = | ||
optic.andThen(GenPrism[To, CastTo]) | ||
} | ||
|
||
extension [From, To] (optic: Optional[From, To]) { | ||
inline def as[CastTo <: To]: Optional[From, CastTo] = | ||
optic.andThen(GenPrism[To, CastTo]) | ||
} | ||
|
||
extension [From, To] (optic: Traversal[From, To]) { | ||
inline def as[CastTo <: To]: Traversal[From, CastTo] = | ||
optic.andThen(GenPrism[To, CastTo]) | ||
} | ||
|
||
extension [From, To] (optic: Setter[From, To]) { | ||
inline def as[CastTo <: To]: Setter[From, CastTo] = | ||
optic.andThen(GenPrism[To, CastTo]) | ||
} | ||
|
||
extension [From, To] (optic: Fold[From, To]) { | ||
inline def as[CastTo <: To]: Fold[From, CastTo] = | ||
optic.andThen(GenPrism[To, CastTo]) | ||
} | ||
|
||
} | ||
|
||
private[monocle] object GenPrism { | ||
inline def apply[From, To <: From]: Prism[From, To] = | ||
${ GenPrismImpl.apply } | ||
} | ||
|
||
private[monocle] object GenPrismImpl { | ||
def apply[From: Type, To: Type](using Quotes): Expr[Prism[From, To]] = | ||
'{ | ||
Prism[From, To]((from: From) => if (from.isInstanceOf[To]) Some(from.asInstanceOf[To]) else None)( | ||
(to: To) => to.asInstanceOf[From]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
core/shared/src/test/scala-3.x/monocle/syntax/AsSyntaxSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package monocle.syntax | ||
|
||
import monocle._ | ||
import monocle.syntax.all._ | ||
import munit.DisciplineSuite | ||
|
||
class AsSyntaxSpec extends DisciplineSuite { | ||
|
||
sealed trait StringOrInt | ||
case class S(value: String) extends StringOrInt | ||
case class I(value: Int) extends StringOrInt | ||
|
||
val iso: Iso[StringOrInt, StringOrInt] = Iso.id | ||
val lens: Lens[StringOrInt, StringOrInt] = Iso.id | ||
val prism: Prism[StringOrInt, StringOrInt] = Iso.id | ||
val optional: Optional[StringOrInt, StringOrInt] = Iso.id | ||
val traversal: Traversal[StringOrInt, StringOrInt] = Iso.id | ||
val setter: Setter[StringOrInt, StringOrInt] = Iso.id | ||
val getter: Getter[StringOrInt, StringOrInt] = Iso.id | ||
val fold: Fold[StringOrInt, StringOrInt] = Iso.id | ||
|
||
test("iso.as"){ assertEquals(iso.as[I].getOption(I(1)), Some(I(1))) } | ||
test("lens.as"){ assertEquals(lens.as[I].getOption(I(1)), Some(I(1))) } | ||
test("prism.as"){ assertEquals(prism.as[I].getOption(I(1)), Some(I(1))) } | ||
test("optional.as"){ assertEquals(optional.as[I].getOption(I(1)), Some(I(1))) } | ||
test("traversal.as"){ assertEquals(traversal.as[I].getAll(I(1)), List(I(1))) } | ||
test("setter.as"){ assertEquals(setter.as[I].replace(I(5))(I(1)), I(5)) } | ||
test("getter.as"){ assert(getter.as[I].getAll(I(1)) == List(I(1))) } | ||
test("fold.as"){ assert(fold.as[I].getAll(I(1)) == List(I(1))) } | ||
|
||
} |
48 changes: 48 additions & 0 deletions
48
macro/src/main/scala-2.x/monocle/macros/syntax/MacroSyntax.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package monocle.macros.syntax | ||
|
||
import monocle.{Fold, Optional, Prism, Setter, Traversal} | ||
|
||
import scala.reflect.macros.blackbox | ||
|
||
trait MacroSyntax { | ||
implicit def toMacroPrismOps[S, A](optic: Prism[S, A]): MacroPrismOps[S, A] = new MacroPrismOps(optic) | ||
implicit def toMacroOptionalOps[S, A](optic: Optional[S, A]): MacroOptionalOps[S, A] = new MacroOptionalOps(optic) | ||
implicit def toMacroTraversalOps[S, A](optic: Traversal[S, A]): MacroTraversalOps[S, A] = new MacroTraversalOps(optic) | ||
implicit def toMacroSetterOps[S, A](optic: Setter[S, A]): MacroSetterOps[S, A] = new MacroSetterOps(optic) | ||
implicit def toMacroFoldOps[S, A](optic: Fold[S, A]): MacroFoldOps[S, A] = new MacroFoldOps(optic) | ||
} | ||
|
||
class MacroPrismOps[S, A](private val optic: Prism[S, A]) extends AnyVal { | ||
def as[CastTo <: A]: Prism[S, CastTo] = macro MacroAsOpsImpl.as_impl[Prism, S, A, CastTo] | ||
} | ||
|
||
class MacroOptionalOps[S, A](private val optic: Optional[S, A]) extends AnyVal { | ||
def as[CastTo <: A]: Optional[S, CastTo] = macro MacroAsOpsImpl.as_impl[Optional, S, A, CastTo] | ||
} | ||
|
||
class MacroTraversalOps[S, A](private val optic: Traversal[S, A]) extends AnyVal { | ||
def as[CastTo <: A]: Traversal[S, CastTo] = macro MacroAsOpsImpl.as_impl[Traversal, S, A, CastTo] | ||
} | ||
|
||
class MacroSetterOps[S, A](private val optic: Setter[S, A]) extends AnyVal { | ||
def as[CastTo <: A]: Setter[S, CastTo] = macro MacroAsOpsImpl.as_impl[Setter, S, A, CastTo] | ||
} | ||
|
||
class MacroFoldOps[S, A](private val optic: Fold[S, A]) extends AnyVal { | ||
def as[CastTo <: A]: Fold[S, CastTo] = macro MacroAsOpsImpl.as_impl[Fold, S, A, CastTo] | ||
} | ||
|
||
class MacroAsOpsImpl(val c: blackbox.Context) { | ||
def as_impl[Optic[_, _], From, To: c.WeakTypeTag, CastTo: c.WeakTypeTag]: c.Expr[Optic[From, CastTo]] = { | ||
import c.universe._ | ||
|
||
val subj = c.prefix.tree match { | ||
case Apply(TypeApply(_, _), List(x)) => x | ||
case t => c.abort(c.enclosingPosition, s"Invalid prefix tree ${show(t)}") | ||
} | ||
|
||
c.Expr[Optic[From, CastTo]]( | ||
q"""$subj.andThen(_root_.monocle.macros.GenPrism[${c.weakTypeOf[To]}, ${c.weakTypeOf[CastTo]}])""" | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
package monocle.macros.syntax | ||
|
||
object all extends ApplyFocusSyntax | ||
object all extends ApplyFocusSyntax with MacroSyntax |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
package monocle.macros | ||
|
||
import monocle.Focus | ||
import monocle.{Focus, Prism} | ||
import monocle.syntax.all._ | ||
|
||
object GenPrism { | ||
transparent inline def apply[Source, Target <: Source] = | ||
Focus[Source](_.as[Target]) | ||
inline def apply[Source, Target <: Source]: Prism[Source, Target] = | ||
Focus[Source]().as[Target] | ||
} |
31 changes: 31 additions & 0 deletions
31
macro/src/test/scala-2.x/monocle/macros/AsSyntaxSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package monocle.macros | ||
|
||
import monocle.macros.syntax.all._ | ||
import monocle._ | ||
import munit.DisciplineSuite | ||
|
||
class AsSyntaxSpec extends DisciplineSuite { | ||
|
||
sealed trait StringOrInt | ||
case class S(value: String) extends StringOrInt | ||
case class I(value: Int) extends StringOrInt | ||
|
||
val iso: Iso[StringOrInt, StringOrInt] = Iso.id | ||
val lens: Lens[StringOrInt, StringOrInt] = Iso.id | ||
val prism: Prism[StringOrInt, StringOrInt] = Iso.id | ||
val optional: Optional[StringOrInt, StringOrInt] = Iso.id | ||
val traversal: Traversal[StringOrInt, StringOrInt] = Iso.id | ||
val setter: Setter[StringOrInt, StringOrInt] = Iso.id | ||
val getter: Getter[StringOrInt, StringOrInt] = Iso.id | ||
val fold: Fold[StringOrInt, StringOrInt] = Iso.id | ||
|
||
test("iso.as")(assertEquals(iso.as[I].getOption(I(1)), Some(I(1)))) | ||
test("lens.as")(assertEquals(lens.as[I].getOption(I(1)), Some(I(1)))) | ||
test("prism.as")(assertEquals(prism.as[I].getOption(I(1)), Some(I(1)))) | ||
test("optional.as")(assertEquals(optional.as[I].getOption(I(1)), Some(I(1)))) | ||
test("traversal.as")(assertEquals(traversal.as[I].getAll(I(1)), List(I(1)))) | ||
test("setter.as")(assertEquals(setter.as[I].replace(I(5))(I(1)), I(5))) | ||
test("getter.as")(assert(getter.as[I].getAll(I(1)) == List(I(1)))) | ||
test("fold.as")(assert(fold.as[I].getAll(I(1)) == List(I(1)))) | ||
|
||
} |