Skip to content
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

add as extension method to optics in Scala 2 and 3 #1110

Merged
merged 3 commits into from
Mar 5, 2021
Merged
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
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 core/shared/src/main/scala-3.x/monocle/syntax/MacroSyntax.scala
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 {
julien-truffaut marked this conversation as resolved.
Show resolved Hide resolved

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])
}
}
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/monocle/syntax/All.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package monocle.syntax

object all extends Syntaxes

trait Syntaxes extends ApplySyntax with AppliedFocusSyntax with FieldsSyntax
trait Syntaxes extends ApplySyntax with AppliedFocusSyntax with MacroSyntax with FieldsSyntax
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))) }

}
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]}])"""
)
}
}
2 changes: 1 addition & 1 deletion macro/src/main/scala-2.x/monocle/macros/syntax/all.scala
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
7 changes: 4 additions & 3 deletions macro/src/main/scala-3.x/monocle/macros/GenPrism.scala
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 macro/src/test/scala-2.x/monocle/macros/AsSyntaxSpec.scala
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))))

}