Skip to content

Commit

Permalink
Merge pull request #397 from TinkoffCreditSystems/optics
Browse files Browse the repository at this point in the history
Named composition
  • Loading branch information
Odomontois authored Sep 30, 2020
2 parents 724d356 + 0c3ecf3 commit 4337b63
Show file tree
Hide file tree
Showing 22 changed files with 162 additions and 54 deletions.
9 changes: 8 additions & 1 deletion optics/core/src/main/scala/tofu/optics/Contains.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,17 @@ object PContains extends OpticCompanion[PContains] with OpticProduct[PContains]
def set(s: S, b: B): T = fset(s, b)
def extract(s: S): A = fget(s)
}

def apply[A, T](name: String)(fget: S => A)(fset: (S, B) => T): PContains[S, T, A, B] = new PContains[S, T, A, B] {
def set(s: S, b: B): T = fset(s, b)
def extract(s: S): A = fget(s)

override def toString(): String = name
}
}

def compose[S, T, A, B, U, V](f: PContains[A, B, U, V], g: PContains[S, T, A, B]): PContains[S, T, U, V] =
new PContains[S, T, U, V] {
new PComposed[PContains, S, T, A, B, U, V](g, f) with PContains[S, T, U, V] {
def set(s: S, b: V): T = g.set(s, f.set(g.extract(s), b))
def extract(a: S): U = f.extract(g.extract(a))
}
Expand Down
17 changes: 13 additions & 4 deletions optics/core/src/main/scala/tofu/optics/Downcast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cats.instances.option._
import cats.syntax.foldable._
import tofu.optics.data.Constant

import scala.reflect.ClassTag
import scala.reflect.{ClassTag, classTag}

/** S could be T or not
* partial function from S to T
Expand All @@ -24,7 +24,9 @@ object Downcast extends MonoOpticCompanion(PDowncast)
object PDowncast extends OpticCompanion[PDowncast] {

def compose[S, T, A, B, U, V](f: PDowncast[A, B, U, V], g: PDowncast[S, T, A, B]): PDowncast[S, T, U, V] =
s => g.downcast(s).flatMap(f.downcast)
new PComposed[PDowncast, S, T, A, B, U, V](g, f) with PDowncast[S, T, U, V] {
def downcast(s: S): Option[U] = g.downcast(s).flatMap(f.downcast)
}

trait Context extends PExtract.Context {
def default: X
Expand All @@ -43,8 +45,15 @@ object PDowncast extends OpticCompanion[PDowncast] {
def default: Option[A] = None
})(a => Some(a))(s)

implicit def subType[A, B <: A: ClassTag]: Downcast[A, B] =
(s: A) => Some(s).collect { case b: B => b }
implicit def subType[A, B <: A: ClassTag]: Downcast[A, B] =
new Downcast[A, B] {
def downcast(s: A): Option[B] = Some(s).collect { case b: B => b }

override def toString: String = {
val name = classTag[B].runtimeClass.getCanonicalName.split("\\.").last
s":$name"
}
}

override def delayed[S, T, A, B](o: () => PDowncast[S, T, A, B]): PDowncast[S, T, A, B] = new PDowncast[S, T, A, B] {
lazy val opt = o()
Expand Down
16 changes: 15 additions & 1 deletion optics/core/src/main/scala/tofu/optics/Equivalent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ object Equivalent extends MonoOpticCompanion(PEquivalent) {
def extract(s: A): B = fget(s)
def back(b: B): A = finv(b)
}

def apply[B](name: String)(fget: A => B)(finv: B => A): Equivalent[A, B] = new Equivalent[A, B] {
def extract(s: A): B = fget(s)
def back(b: B): A = finv(b)

override def toString(): String = name
}
}
}

Expand All @@ -56,10 +63,17 @@ object PEquivalent extends OpticCompanion[PEquivalent] with OpticProduct[PEquiva
def extract(s: S): A = fget(s)
def back(b: B): T = finv(b)
}

def apply[T, A](name: String)(fget: S => A)(finv: B => T): PEquivalent[S, T, A, B] = new PEquivalent[S, T, A, B] {
def extract(s: S): A = fget(s)
def back(b: B): T = finv(b)

override def toString(): String = name
}
}

def compose[S, T, A, B, U, V](f: PEquivalent[A, B, U, V], g: PEquivalent[S, T, A, B]): PEquivalent[S, T, U, V] =
new PEquivalent[S, T, U, V] {
new PComposed[PEquivalent, S, T, A, B, U, V](g, f) with PEquivalent[S, T, U, V] {
def extract(s: S): U = f.extract(g.extract(s))
def back(v: V): T = g.upcast(f.upcast(v))
}
Expand Down
4 changes: 3 additions & 1 deletion optics/core/src/main/scala/tofu/optics/Extract.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ object Extract extends MonoOpticCompanion(PExtract)

object PExtract extends OpticCompanion[PExtract] {
def compose[S, T, A, B, U, V](f: PExtract[A, B, U, V], g: PExtract[S, T, A, B]): PExtract[S, T, U, V] =
s => f.extract(g.extract(s))
new PComposed[PExtract, S, T, A, B, U, V](g, f) with PExtract[S, T, U, V] {
def extract(s: S): U = f.extract(g.extract(s))
}

trait Context extends PContains.Context {
type X
Expand Down
6 changes: 5 additions & 1 deletion optics/core/src/main/scala/tofu/optics/Folded.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ trait PFolded[-S, +T, +A, -B] extends PBase[PFolded, S, T, A, B] { self =>
def ++[S1 <: S, A1 >: A](other: PFolded[S1, Any, A1, Nothing]): PFolded[S1, Nothing, A1, Any] =
new PFolded[S1, Nothing, A1, Any] {
override def foldMap[X: Monoid](s: S1)(f: A1 => X): X = self.foldMap(s)(f) |+| other.foldMap(s)(f)

override def toString(): String = s"($self) ++ ($other)"
}
}

Expand All @@ -41,11 +43,13 @@ object PFolded extends OpticCompanion[PFolded] {
def ++[S1 <: S, T1, A1 >: A, V1](that: PFolded[S1, T1, A1, V1]): PFolded[S1, T1, A1, V1] =
new PFolded[S1, T1, A1, V1] {
def foldMap[X: Monoid](s: S1)(f: A1 => X): X = self.foldMap(s)(f) |+| that.foldMap(s)(f)

override def toString(): String = s"($self) ++ ($that)"
}
}

def compose[S, T, A, B, U, V](f: PFolded[A, B, U, V], g: PFolded[S, T, A, B]): PFolded[S, T, U, V] =
new PFolded[S, T, U, V] {
new PComposed[PFolded, S, T, A, B, U, V](g, f) with PFolded[S, T, U, V] {
def foldMap[X: Monoid](s: S)(fux: U => X): X = g.foldMap(s)(f.foldMap(_)(fux))
}
final implicit def byFoldable[F[_], A, T, B](implicit F: Foldable[F]): PFolded[F[A], T, A, B] =
Expand Down
2 changes: 1 addition & 1 deletion optics/core/src/main/scala/tofu/optics/Items.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object PItems extends OpticCompanion[PItems] {
}

def compose[S, T, A, B, U, V](f: PItems[A, B, U, V], g: PItems[S, T, A, B]): PItems[S, T, U, V] =
new PItems[S, T, U, V] {
new PComposed[PItems, S, T, A, B, U, V](g, f) with PItems[S, T, U, V] {
def traverse[F[+_]: Applicative](a: S)(fc: U => F[V]): F[T] = g.traverse(a)(f.traverse(_)(fc))
}

Expand Down
7 changes: 7 additions & 0 deletions optics/core/src/main/scala/tofu/optics/PBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ trait PBase[+O[-s, +t, +a, -b] <: PBase[O, s, t, a, b], -S, +T, +A, -B] { self:

def >>[O1[-s, +t, +a, -b] >: O[s, t, a, b] @uv212: Category2, U, V](o1: O1[A, B, U, V]): O1[S, T, U, V] = andThen(o1)
}

class PComposed[+O[-s, +t, +a, -b] <: PBase[O, s, t, a, b], -S, +T, A, B, +U, -V](
x: PBase[O, S, T, A, B],
y: PBase[O, A, B, U, V]
) extends PBase[O, S, T, U, V] { self: O[S, T, U, V] =>
override def toString(): String = s"($x) >> ($y)"
}
27 changes: 25 additions & 2 deletions optics/core/src/main/scala/tofu/optics/Property.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import tofu.optics.data.Identity
* which may be set to B and change whole type to T
*/
trait PProperty[-S, +T, +A, -B]
extends PItems[S, T, A, B] with PDowncast[S, T, A, B] with PBase[PProperty, S, T, A, B] {
extends PItems[S, T, A, B] with PDowncast[S, T, A, B] with PBase[PProperty, S, T, A, B] { self =>
def set(s: S, b: B): T
def narrow(s: S): Either[T, A]

Expand All @@ -27,6 +27,29 @@ trait PProperty[-S, +T, +A, -B]
override def downcast(s: S): Option[A] = narrow(s).toOption

override def foldMap[X: Monoid](a: S)(f: A => X): X = downcast(a).foldMap(f)

/** Fallback to another property of compatible type */
def orElse[S1 >: T <: S, T1, A1 >: A, B1 <: B](other: PProperty[S1, T1, A1, B1]): PProperty[S1, T1, A1, B1] =
new PProperty[S1, T1, A1, B1] {
def set(s: S1, b: B1): T1 = other.set(self.set(s, b), b)

def narrow(s: S1): Either[T1, A1] = self.narrow(s) match {
case Left(t) => other.narrow(t)
case Right(a) => Right(a)
}

override def toString: String = s"$self orElse $other"
}

/** unsafe transform this property to contains */
def unsafeTotal: PContains[S, T, A, B] = new PContains[S, T, A, B] {
def set(s: S, b: B): T = self.set(s, b)

def extract(s: S): A =
self.narrow(s).getOrElse(throw new NoSuchElementException(s"$self was empty for $s"))

override def toString(): String = s"($self).unsafeTotal"
}
}

object Property extends MonoOpticCompanion(PProperty) {
Expand Down Expand Up @@ -59,7 +82,7 @@ object PProperty extends OpticCompanion[PProperty] {
trait Context extends PSubset.Context with PContains.Context

def compose[S, T, A, B, U, V](f: PProperty[A, B, U, V], g: PProperty[S, T, A, B]): PProperty[S, T, U, V] =
new PProperty[S, T, U, V] {
new PComposed[PProperty, S, T, A, B, U, V](g, f) with PProperty[S, T, U, V] {
def set(s: S, b: V): T = g.narrow(s).fold(identity[T], a => g.set(s, f.set(a, b)))
def narrow(s: S): Either[T, U] = g.narrow(s).flatMap(a => f.narrow(a).leftMap(g.set(s, _)))
}
Expand Down
2 changes: 1 addition & 1 deletion optics/core/src/main/scala/tofu/optics/Reduced.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object Reduced extends MonoOpticCompanion(PReduced) {

object PReduced extends OpticCompanion[PReduced] {
def compose[S, T, A, B, U, V](f: PReduced[A, B, U, V], g: PReduced[S, T, A, B]): PReduced[S, T, U, V] =
new PReduced[S, T, U, V] {
new PComposed[PReduced, S, T, A, B, U, V](g, f) with PReduced[S, T, U, V] {
def reduceMap[X: Semigroup](s: S)(fux: U => X): X = g.reduceMap(s)(f.reduceMap(_)(fux))
}

Expand Down
2 changes: 1 addition & 1 deletion optics/core/src/main/scala/tofu/optics/Repeated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object Repeated extends MonoOpticCompanion(PRepeated) {

object PRepeated extends OpticCompanion[PRepeated] {
def compose[S, T, A, B, U, V](f: PRepeated[A, B, U, V], g: PRepeated[S, T, A, B]): PRepeated[S, T, U, V] =
new PRepeated[S, T, U, V] {
new PComposed[PRepeated, S, T, A, B, U, V](g, f) with PRepeated[S, T, U, V] {
def traverse1[F[+_]: Apply](s: S)(fuv: U => F[V]): F[T] = g.traverse1(s)(f.traverse1(_)(fuv))
}
final implicit def fromNETraverse[F[_], A, B](implicit F: NonEmptyTraverse[F]): PRepeated[F[A], F[B], A, B] =
Expand Down
1 change: 1 addition & 0 deletions optics/core/src/main/scala/tofu/optics/Same.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ object PSame extends OpticCompanion[PSame] with OpticProduct[PSame] {

private def refl[A, B]: PSame[A, B, A, B] = new PSame[A, B, A, B] {
def rsubst[K[_, _]](k: K[A, B]): K[A, B] = k
override def toString = "id"
}

private val anyId = refl[Any, Any]
Expand Down
10 changes: 8 additions & 2 deletions optics/core/src/main/scala/tofu/optics/Subset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,20 @@ object PSubset extends OpticCompanion[PSubset] {
def apply[S, B] = new SubsetApplied[S, B](true)

class SubsetApplied[S, B](private val dummy: Boolean) extends AnyVal {
def apply[T, A](fdown: S => Either[T, A])(fup: B => T): PSubset[S, T, A, B] = new PSubset[S, T, A, B] {
def apply[T, A](fdown: S => Either[T, A])(fup: B => T): PSubset[S, T, A, B] = new PSubset[S, T, A, B] {
def narrow(s: S): Either[T, A] = fdown(s)
def upcast(b: B): T = fup(b)
}
def apply[T, A](name: String)(fdown: S => Either[T, A])(fup: B => T): PSubset[S, T, A, B] =
new PSubset[S, T, A, B] {
def narrow(s: S): Either[T, A] = fdown(s)
def upcast(b: B): T = fup(b)
override def toString(): String = name
}
}

def compose[S, T, A, B, U, V](f: PSubset[A, B, U, V], g: PSubset[S, T, A, B]): PSubset[S, T, U, V] =
new PSubset[S, T, U, V] {
new PComposed[PSubset, S, T, A, B, U, V](g, f) with PSubset[S, T, U, V] {
def narrow(s: S): Either[T, U] = g.narrow(s).flatMap(f.narrow(_).leftMap(g.upcast))
def upcast(v: V): T = g.upcast(f.upcast(v))
}
Expand Down
4 changes: 3 additions & 1 deletion optics/core/src/main/scala/tofu/optics/Upcast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ object Upcast extends MonoOpticCompanion(PUpcast)
object PUpcast extends OpticCompanion[PUpcast] with OpticProduct[PUpcast] {

def compose[S, T, A, B, U, V](f: PUpcast[A, B, U, V], g: PUpcast[S, T, A, B]): PUpcast[S, T, U, V] =
v => g.upcast(f.upcast(v))
new PComposed[PUpcast, S, T, A, B, U, V](g, f) with PUpcast[S, T, U, V] {
def upcast(v: V): T = g.upcast(f.upcast(v))
}

override def product[S1, S2, T1, T2, A1, A2, B1, B2](
f: PUpcast[S1, T1, A1, B1],
Expand Down
4 changes: 3 additions & 1 deletion optics/core/src/main/scala/tofu/optics/Update.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ object Update extends MonoOpticCompanion(PUpdate)

object PUpdate extends OpticCompanion[PUpdate] {
def compose[S, T, A, B, U, V](f: PUpdate[A, B, U, V], g: PUpdate[S, T, A, B]): PUpdate[S, T, U, V] =
(s, fuv) => g.update(s, f.update(_, fuv))
new PComposed[PUpdate, S, T, A, B, U, V](g, f) with PUpdate[S, T, U, V] {
def update(s: S, uv: U => V): T = g.update(s, f.update(_, uv))
}

override def delayed[S, T, A, B](o: () => PUpdate[S, T, A, B]): PUpdate[S, T, A, B] = new PUpdate[S, T, A, B] {

Expand Down
5 changes: 2 additions & 3 deletions optics/core/src/main/scala/tofu/optics/Zipping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ trait PZipping[-S, +T, +A, -B] extends PBase[PZipping, S, T, A, B] with PUpdate[

object PZipping extends OpticCompanion[PZipping] {
override def compose[S, T, A, B, U, V](f: PZipping[A, B, U, V], g: PZipping[S, T, A, B]): PZipping[S, T, U, V] =
new PZipping[S, T, U, V] {
override def grate(suv: (S => U) => V): T =
g.grate(sa => f.grate(au => suv(au compose sa)))
new PComposed[PZipping, S, T, A, B, U, V](g, f) with PZipping[S, T, U, V] {
def grate(suv: (S => U) => V): T = g.grate(sa => f.grate(au => suv(au compose sa)))
}

override def delayed[S, T, A, B](o: () => PZipping[S, T, A, B]): PZipping[S, T, A, B] = new PZipping[S, T, A, B] {
Expand Down
38 changes: 19 additions & 19 deletions optics/macro/src/main/scala/tofu/optics/macros/GenEquivalent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@ sealed abstract class GenEquivalentImplBase {
val table = c.universe.asInstanceOf[SymbolTable]
val tree = table.gen
val obj = tree.mkAttributedQualifier(sTpe.asInstanceOf[tree.global.Type]).asInstanceOf[Tree]

val name = s"${sTpe.typeSymbol.name.decodedName}.type<->"
q"""
_root_.tofu.optics.Equivalent[${sTpe}, Unit](Function.const(()))(Function.const(${obj}))
_root_.tofu.optics.Equivalent[${sTpe}]($name)(Function.const(()))(Function.const(${obj}))
"""
} else {
val name = s"${sTpe.typeSymbol.name.decodedName}<->"
caseAccessorsOf[S] match {
case Nil =>
val sTpeSym = sTpe.typeSymbol.companion
q"""
_root_.tofu.optics.Equivalent[${sTpe}, Unit](Function.const(()))(Function.const(${sTpeSym}()))
_root_.tofu.optics.Equivalent[${sTpe}]($name)(Function.const(()))(Function.const(${sTpeSym}()))
"""
case _ => fail(s"$sTpe needs to be a case class with no accessor or an object.")
}
Expand All @@ -72,16 +75,11 @@ class GenEquivalentImpl(override val c: blackbox.Context) extends GenEquivalentI
}

val sTpeSym = sTpe.typeSymbol.companion
val name = s"${sTpeSym.name.decodedName}<->"

c.Expr[Equivalent[S, A]](q"""
import _root_.tofu.optics.Equivalent
new Equivalent[$sTpe, $aTpe]{ self =>
override def extract(s: $sTpe): $aTpe =
s.$fieldMethod

override def back(a: $aTpe): $sTpe =
$sTpeSym(a)
}
Equivalent[$sTpe]($name)((s: $sTpe) => s.$fieldMethod)((a: $aTpe) => $sTpeSym(a))
""")
}

Expand Down Expand Up @@ -118,18 +116,18 @@ class GenEquivalentImplW(override val c: whitebox.Context) extends GenEquivalent
.getOrElse(fail(s"Unable to discern primary constructor for $sTpe."))
.paramLists

val name = s"${sTpeSym.name.decodedName}<->"

paramLists match {
case Nil | Nil :: Nil =>
genEquiv_unit_tree[S]

case (param :: Nil) :: Nil =>
val (pName, pType) = nameAndType(sTpe, param)
q"""
new _root_.tofu.optics.Equivalent[$sTpe, $pType] {
override def extract(s: $sTpe): $pType = s.$pName

override def back(a: $pType): $sTpe = ${sTpeSym.companion}(_)
}
q"""
import _root_.tofu.optics.Equivalent
Equivalent[$sTpe]($name)((s: $sTpe) => s.$pName)((a: $pType) => ${sTpeSym.companion}(a))
"""

case params :: Nil =>
Expand All @@ -142,12 +140,14 @@ class GenEquivalentImplW(override val c: whitebox.Context) extends GenEquivalent
readTuple ::= q"a.${TermName("_" + (i + 1))}"
types ::= pType
}
q"""
new _root_.tofu.optics.Equivalent[$sTpe, (..$types)] {
override def extract(s: $sTpe): (..$types) = (..$readField)

override def back(a: (..$types)): $sTpe = ${sTpeSym.companion}(..$readTuple)
}
q"""
import _root_.tofu.optics.Equivalent
Equivalent[$sTpe]($name)(
(s: $sTpe) => (..$readField)
)(
(a: (..$types)) => ${sTpeSym.companion}(..$readTuple)
)
"""

case _ :: _ :: _ =>
Expand Down
12 changes: 4 additions & 8 deletions optics/macro/src/main/scala/tofu/optics/macros/GenSubset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ private class GenSubsetImpl(val c: blackbox.Context) {
import c.universe._

val (sTpe, aTpe) = (weakTypeOf[S], weakTypeOf[A])
val aName = ":" + (aTpe: Type).typeSymbol.name.decodedName.toString()

c.Expr[Subset[S, A]](q"""
import _root_.tofu.optics.Subset

new Subset[$sTpe, $aTpe]{
override def narrow(s: $sTpe): $sTpe Either $aTpe =
_root_.tofu.optics.PSubset[$sTpe, $aTpe]($aName)(
(s: $sTpe) =>
if(s.isInstanceOf[$aTpe]) Right(s.asInstanceOf[$aTpe])
else Left(s)

override def upcast(a: $aTpe): $sTpe =
a.asInstanceOf[$sTpe]
}
)((a: $aTpe) => a.asInstanceOf[$sTpe])
""")
}
}
Loading

0 comments on commit 4337b63

Please sign in to comment.