Skip to content

Commit

Permalink
Merge pull request #101 from sagifogel/addition_of_polymorphic_partsOf
Browse files Browse the repository at this point in the history
addition of unsafePartsOf
  • Loading branch information
sagifogel authored May 26, 2021
2 parents 5792f5c + d874f3c commit fbf8953
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 32 deletions.
14 changes: 11 additions & 3 deletions core/shared/src/main/scala/proptics/ATraversal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import spire.std.boolean._
import proptics.data._
import proptics.internal._
import proptics.internal.partsOf.{ins, outs}
import proptics.profunctor.Corepresentable.Aux
import proptics.profunctor.{Traversing, Wander}
import proptics.rank2types.{LensLikeWithIndex, Rank2TypeLensLike}
import proptics.syntax.aTraversal._
Expand Down Expand Up @@ -175,6 +176,10 @@ abstract class ATraversal_[S, T, A, B] {
/** compose a [[ATraversal_]] with a function lifted to a [[Getter_]] */
final def to[C, D](f: A => C): Fold_[S, T, C, D] = compose(Getter_[A, B, C, D](f))

/** convert a [[ATraversal]] into a [[Lens]] over a list of the [[ATraversal]]'s foci */
final def unsafePartsOf(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]], ev1: Aux[* => *, State[List[B], *]]): Lens_[S, T, List[A], List[B]] =
Bazaar.unsafePartsOf(self.toBazaar)

/** compose an [[ATraversal_]] with an [[Iso_]] */
final def compose[C, D](other: Iso_[A, B, C, D]): ATraversal_[S, T, C, D] =
ATraversal_(new RunBazaar[* => *, C, D, S, T] {
Expand Down Expand Up @@ -373,6 +378,11 @@ object ATraversal_ {

/** polymorphic identity of an [[ATraversal_]] */
final def id[S, T]: ATraversal_[S, T, S, T] = ATraversal_(identity[S] _)(const(identity[T]))

/** convert a [[Traversal]] into a [[Lens]] over a list of the [[Traversal]]'s foci */
final def unsafePartsOf[S, T, A, B](
aTraversal: ATraversal_[S, T, A, B])(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]], ev2: Aux[* => *, State[List[B], *]]): Lens_[S, T, List[A], List[B]] =
aTraversal.unsafePartsOf
}

object ATraversal {
Expand Down Expand Up @@ -422,9 +432,7 @@ object ATraversal {
ATraversal.fromTraverse[G, A].dropWhile(predicate)

/** convert an [[ATraversal]] into a [[Lens]] over a list of the [[ATraversal]]'s foci */
final def partsOf[S, T, A](traversal: ATraversal_[S, T, A, A])(
implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]],
ev1: Applicative[Bazaar[* => *, A, A, Unit, *]]): Lens_[S, T, List[A], List[A]] =
final def partsOf[S, T, A](traversal: ATraversal_[S, T, A, A])(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]]): Lens_[S, T, List[A], List[A]] =
Lens_(new Rank2TypeLensLike[S, T, List[A], List[A]] {
override def apply[P[_, _]](pab: P[List[A], List[A]])(implicit ev: Strong[P]): P[S, T] = {
val s2b = traversal.toBazaar.runBazaar(ev0.sell[A, A])(_)
Expand Down
14 changes: 11 additions & 3 deletions core/shared/src/main/scala/proptics/Traversal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import proptics.Traversal_.wander
import proptics.data._
import proptics.internal.partsOf._
import proptics.internal.{Bazaar, _}
import proptics.profunctor.Corepresentable.Aux
import proptics.profunctor.Wander._
import proptics.profunctor.{Star, Traversing, Wander}
import proptics.rank2types.{LensLike, LensLikeWithIndex, Rank2TypeLensLike, Rank2TypeTraversalLike}
Expand Down Expand Up @@ -186,6 +187,10 @@ abstract class Traversal_[S, T, A, B] extends Serializable { self =>
/** compose a [[Traversal_]] with a function lifted to a [[Getter_]] */
final def to[C, D](f: A => C): Fold_[S, T, C, D] = compose(Getter_[A, B, C, D](f))

/** convert a [[Traversal]] into a [[Lens]] over a list of the [[Traversal]]'s foci */
final def unsafePartsOf(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]], ev1: Aux[* => *, State[List[B], *]]): Lens_[S, T, List[A], List[B]] =
Bazaar.unsafePartsOf(self.toBazaar)

/** compose a [[Traversal_]] with an [[Iso_]] */
final def compose[C, D](other: Iso_[A, B, C, D]): Traversal_[S, T, C, D] = new Traversal_[S, T, C, D] {
override def apply[P[_, _]](pab: P[C, D])(implicit ev: Wander[P]): P[S, T] = self(other(pab))
Expand Down Expand Up @@ -387,6 +392,11 @@ object Traversal_ {

/** polymorphic identity of a [[Traversal_]] */
final def id[S, T]: Traversal_[S, T, S, T] = Traversal_(identity[S] _)(const(identity[T]))

/** convert a [[Traversal]] into a [[Lens]] over a list of the [[Traversal]]'s foci */
final def unsafePartsOf[S, T, A, B](
traversal: Traversal_[S, T, A, B])(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]], ev2: Aux[* => *, State[List[B], *]]): Lens_[S, T, List[A], List[B]] =
traversal.unsafePartsOf
}

object Traversal {
Expand Down Expand Up @@ -450,9 +460,7 @@ object Traversal {
Traversal.fromTraverse[G, A].dropWhile(predicate)

/** convert a [[Traversal]] into a [[Lens]] over a list of the [[Traversal]]'s foci */
final def partsOf[S, T, A](traversal: Traversal_[S, T, A, A])(
implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]],
ev1: Applicative[Bazaar[* => *, A, A, Unit, *]]): Lens_[S, T, List[A], List[A]] =
final def partsOf[S, T, A](traversal: Traversal_[S, T, A, A])(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]]): Lens_[S, T, List[A], List[A]] =
Lens_(new Rank2TypeLensLike[S, T, List[A], List[A]] {
override def apply[P[_, _]](pab: P[List[A], List[A]])(implicit ev: Strong[P]): P[S, T] = {
val s2b = traversal.toBazaar.runBazaar(ev0.sell[A, A])(_)
Expand Down
8 changes: 6 additions & 2 deletions core/shared/src/main/scala/proptics/instances/PartsOf.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package proptics.instances

import proptics.internal.{CorepresentableInstances, SellableInstances}
import proptics.internal.{Bazaar, CorepresentableInstances, Sellable, SellableInstances}
import proptics.profunctor.Corepresentable.Aux

trait PartsOf extends SellableInstances with CorepresentableInstances
trait PartsOf extends SellableInstances with CorepresentableInstances {
implicit def partsOfSellable[A](implicit ev: Aux[* => *, Bazaar[* => *, List[A], List[A], Unit, *]]): Sellable[* => *, Bazaar[* => *, *, *, Unit, *]] =
sellableBazaar[* => *, Bazaar[* => *, List[A], List[A], Unit, *]]
}
18 changes: 17 additions & 1 deletion core/shared/src/main/scala/proptics/internal/Bazaar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package proptics.internal
import scala.annotation.implicitNotFound

import cats.arrow.{Profunctor, Strong}
import cats.data.State
import cats.syntax.either._
import cats.{Applicative, Bitraverse}

import proptics.Lens_
import proptics.internal.partsOf.{ins, unsafeOuts}
import proptics.profunctor.Corepresentable.Aux
import proptics.profunctor.{Choice, Traversing, Wander}
import proptics.rank2types.Rank2TypeLensLike

/** Bazaar is used to characterize a [[proptics.Traversal_]] */
@implicitNotFound("Could not find an instance of Bazaar[${P}, ${A}, ${B}, ${S}, ${T}]")
Expand Down Expand Up @@ -110,4 +115,15 @@ abstract class BazaarInstances {
}
}

object Bazaar extends BazaarInstances
object Bazaar extends BazaarInstances {
private[proptics] def unsafePartsOf[S, T, A, B](
bazaar: Bazaar[* => *, A, B, S, T])(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]], ev1: Aux[* => *, State[List[B], *]]): Lens_[S, T, List[A], List[B]] =
Lens_(new Rank2TypeLensLike[S, T, List[A], List[B]] {
override def apply[P[_, _]](pab: P[List[A], List[B]])(implicit ev: Strong[P]): P[S, T] = {
val s2b = bazaar.runBazaar(ev0.sell[A, B])(_)
val second = ev.second[List[A], List[B], S](pab)

ev.dimap[(S, List[A]), (S, List[B]), S, T](second)(s => (s, ins(s2b(s)))) { case (s, list) => unsafeOuts(s2b(s))(ev1)(list) }
}
})
}
24 changes: 18 additions & 6 deletions core/shared/src/main/scala/proptics/internal/Corepresentable.scala
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
package proptics.internal

import cats.arrow.Profunctor
import cats.data.State
import cats.instances.function._
import cats.{Applicative, Id}

import proptics.profunctor.{Corepresentable => Corep}

trait CorepresentableInstances {
implicit def corepresentableBazaar[C, D]: Corep.Aux[* => *, Bazaar[* => *, C, D, Unit, *]] = new Corep[* => *] {
implicit def corepresentableBazaar[C, D]: Corep.Aux[* => *, Bazaar[* => *, List[C], List[D], Unit, *]] = new Corep[* => *] {
override def P: Profunctor[* => *] = Profunctor[* => *]

override type Corepresentation[x] = Bazaar[* => *, C, D, Unit, x]
override type Corepresentation[x] = Bazaar[* => *, List[C], List[D], Unit, x]

override def cotabulate[A, B](f: Bazaar[* => *, C, D, Unit, A] => B): A => B = a => {
f(Applicative[Bazaar[* => *, C, D, Unit, *]].pure(a))
override def cotabulate[A, B](f: Bazaar[* => *, List[C], List[D], Unit, A] => B): A => B = a => {
f(Applicative[Bazaar[* => *, List[C], List[D], Unit, *]].pure(a))
}

override def cosieve[A, B](pab: A => B)(fa: Bazaar[* => *, C, D, Unit, A]): B = {
val res = fa.runBazaar.apply[Id](c => pab(c.asInstanceOf[A]).asInstanceOf[D])(())
override def cosieve[A, B](pab: A => B)(fa: Bazaar[* => *, List[C], List[D], Unit, A]): B = {
val res = fa.runBazaar.apply[Id](list => list.map(c => pab(c.asInstanceOf[A]).asInstanceOf[D]))(())
pab(res)
}
}

implicit def corepresentableState[C]: Corep.Aux[* => *, State[List[C], *]] = new Corep[* => *] {
override def P: Profunctor[* => *] = Profunctor[* => *]

override type Corepresentation[x] = State[List[C], x]

override def cotabulate[A, B](f: State[List[C], A] => B): A => B = a => f(State.pure[List[C], A](a))

override def cosieve[A, B](pab: A => B)(fa: State[List[C], A]): B =
fa.map(pab).runEmptyA.value
}
}

object Corepresentable extends CorepresentableInstances
8 changes: 4 additions & 4 deletions core/shared/src/main/scala/proptics/internal/Sellable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ trait Sellable[P[_, _], W[_, _, _]] extends Serializable {
}

abstract class SellableInstances {
implicit final def sellableBazaar[P[_, _], G[_]](implicit ev0: Profunctor[P], ev1: Aux[P, G]): Sellable[P, Bazaar[P, *, *, Unit, *]] = new Sellable[P, Bazaar[P, *, *, Unit, *]] {
implicit final def sellableBazaar[P[_, _]: Profunctor, G[_]](implicit ev1: Aux[P, G]): Sellable[P, Bazaar[P, *, *, Unit, *]] = new Sellable[P, Bazaar[P, *, *, Unit, *]] {
override def sell[A, B]: P[A, Bazaar[P, A, B, Unit, B]] =
ev1.cotabulate { (ga: G[A]) =>
new Bazaar[P, A, B, Unit, B] {
override def runBazaar: RunBazaar[P, A, B, Unit, B] = new RunBazaar[P, A, B, Unit, B] {
override def apply[F[_]](pafb: P[A, F[B]])(s: Unit)(implicit ev: Applicative[F]): F[B] =
override def apply[F[_]](pafb: P[A, F[B]])(s: Unit)(implicit ev2: Applicative[F]): F[B] =
ev1.cosieve(pafb)(ga)
}
}
Expand All @@ -28,6 +28,6 @@ abstract class SellableInstances {
}

object Sellable extends SellableInstances {
/** summon an instance of [[Sellable]] for `P` and `F` */
@inline def apply[P[_, _], F[_]](implicit ev: Sellable[P, Bazaar[P, *, *, Unit, *]]): Sellable[P, Bazaar[P, *, *, Unit, *]] = ev
/** summon an instance of [[Sellable]] for `P` */
@inline def apply[P[_, _]](implicit ev0: Sellable[P, Bazaar[P, *, *, Unit, *]]): Sellable[P, Bazaar[P, *, *, Unit, *]] = ev0
}
20 changes: 15 additions & 5 deletions core/shared/src/main/scala/proptics/internal/partsOf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@ package proptics.internal
import cats.data.State

import proptics.Traversal_
import proptics.profunctor.Corepresentable.Aux

private[proptics] object partsOf {
def ins[A, T](bazaar: Bazaar[* => *, A, A, Unit, T]): List[A] =
Traversal_.fromBazaar[Unit, T, A, A](bazaar).toList(())
def ins[A, B, T](bazaar: Bazaar[* => *, A, B, Unit, T]): List[A] =
Traversal_.fromBazaar[Unit, T, A, B](bazaar).toList(())

def outs[A, T](bazaar: Bazaar[* => *, A, A, Unit, T]): List[A] => T = list =>
bazaar.runBazaar
.apply(a => State(unconsWithDefault(a)))(())
bazaar
.runBazaar(a => State(unconsWithDefault(a)))(())
.runA(list)
.value

def unconsWithDefault[A](a: A): List[A] => (List[A], A) = {
def unsafeOuts[A, B, T](bazaar: Bazaar[* => *, A, B, Unit, T])(implicit ev: Aux[* => *, State[List[B], *]]): List[B] => T = list =>
bazaar
.runBazaar(ev.cotabulate[A, State[List[B], B]](_ => State(unconsWithDefault(fakeVal))))(())
.runA(list)
.value

def unconsWithDefault[A](a: => A): List[A] => (List[A], A) = {
case Nil => (Nil, a)
case x :: xs => (xs, x)
}

def fakeVal[B]: B =
throw new IllegalArgumentException("Not enough elements were supplied")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.Applicative
import cats.data.State
import cats.syntax.eq._

import proptics.instances.partsOf._
import proptics.internal.{Bazaar, Sellable}
import proptics.syntax.indexedTraversal._
import proptics.syntax.traversal._
import proptics.{ATraversal, ATraversal_, Lens_}
Expand All @@ -16,8 +16,9 @@ trait ATraversalSyntax {
}

final case class ATraversalElementOps[S, T, A](private val aTraversal: ATraversal_[S, T, A, A]) extends AnyVal {
/** convert an [[ATraversal]] into a [[Lens]] over a list of the [[ATraversal]]'s foci */
def partsOf: Lens_[S, T, List[A], List[A]] = ATraversal.partsOf(aTraversal)
/** convert an [[ATraversal]] into a [[proptics.Lens]] over a list of the [[ATraversal]]'s foci */
def partsOf(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]]): Lens_[S, T, List[A], List[A]] =
ATraversal.partsOf(aTraversal)

/** narrow the focus of an [[ATraversal_]] to a single element */
def elementAt(i: Int): ATraversal_[S, T, A, A] = filterByIndex(_ === i)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cats.data.{Nested, State}
import cats.syntax.apply._
import cats.syntax.eq._

import proptics.instances.partsOf._
import proptics.internal.{Bazaar, Sellable}
import proptics.profunctor.{Star, Traversing, Wander}
import proptics.rank2types.Rank2TypeTraversalLike
import proptics.syntax.indexedTraversal._
Expand All @@ -20,7 +20,8 @@ trait TraversalSyntax {

final case class TraversalElementOps[S, T, A](private val traversal: Traversal_[S, T, A, A]) extends AnyVal {
/** convert a [[Traversal]] into a [[proptics.Lens]] over a list of the [[Traversal]]'s foci */
def partsOf: Lens_[S, T, List[A], List[A]] = Traversal.partsOf(traversal)
def partsOf(implicit ev0: Sellable[* => *, Bazaar[* => *, *, *, Unit, *]]): Lens_[S, T, List[A], List[A]] =
Traversal.partsOf(traversal)

/** narrow the focus of a [[Traversal_]] to a single element */
def elementAt(i: Int): Traversal_[S, T, A, A] = filterByIndex(_ === i)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package optics

import proptics.instances.field1._
import proptics.instances.field2._
import proptics.instances.partsOf._
import proptics.specs.PropticsSuite
import proptics.std.tuple.{_1, _2}
import proptics.syntax.aTraversal._
import proptics.syntax.traversal._
import proptics.{ATraversal, Lens, Traversal}

class PartsOf extends PropticsSuite {
class PartsOfExamples extends PropticsSuite {
val traversedTuple1: Traversal[List[(String, Int)], String] = Traversal.fromTraverse[List, (String, Int)] compose _1[String, Int]
val aTraversedTuple1: ATraversal[List[(String, Int)], String] = ATraversal.fromTraverse[List, (String, Int)] compose _1[String, Int]
val traversedTuple2: Traversal[List[(String, Double)], Double] = Traversal.fromTraverse[List, (String, Double)] compose _2[String, Double]
Expand Down
85 changes: 85 additions & 0 deletions example/src/test/scala/optics/UnsafePartsOfExamples.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package optics

import proptics.instances.partsOf._
import proptics.specs.PropticsSuite
import proptics.std.tuple._1P
import proptics.{ATraversal_, Lens_, Traversal_}

class UnsafePartsOfExamples extends PropticsSuite {
val traversedTuple1: Traversal_[List[(String, Int)], List[(Boolean, Int)], String, Boolean] =
Traversal_.fromTraverse[List, (String, Int), (Boolean, Int)] compose
_1P[String, Boolean, Int]
val traversedTuple2: ATraversal_[List[(String, Int)], List[(Boolean, Int)], String, Boolean] =
ATraversal_.fromTraverse[List, (String, Int), (Boolean, Int)] compose
_1P[String, Boolean, Int]
val unsafePartsOfFromTraversal: Lens_[List[(String, Int)], List[(Boolean, Int)], List[String], List[Boolean]] =
traversedTuple1.unsafePartsOf
val unsafePartsOfFromATraversal: Lens_[List[(String, Int)], List[(Boolean, Int)], List[String], List[Boolean]] =
traversedTuple2.unsafePartsOf

test("unsafePartsOf from Traversal can view focuses as List") {
val target = List("A", "B", "C").zipWithIndex
unsafePartsOfFromTraversal.view(target) shouldEqual List("A", "B", "C")
}

test("unsafePartsOf from ATraversal can view focuses as List") {
val target = List("A", "B", "C").zipWithIndex
unsafePartsOfFromATraversal.view(target) shouldEqual List("A", "B", "C")
}

test("unsafePartsOf from Traversal can set the lens to a list to replace the corresponding elements") {
val target = List("A", "B", "C").zipWithIndex
unsafePartsOfFromTraversal.set(List(true, false, true))(target) shouldEqual
List((true, 0), (false, 1), (true, 2))
}

test("unsafePartsOf from ATraversal can set the lens to a list to replace the corresponding elements") {
val target = List("A", "B", "C").zipWithIndex
unsafePartsOfFromATraversal.set(List(true, false, true))(target) shouldEqual
List((true, 0), (false, 1), (true, 2))
}

test("unsafePartsOf from Traversal ignores extra elements") {
val target = List("A", "B", "C").zipWithIndex
val replaceList = List(true, false, true, false, true, false, true)

unsafePartsOfFromTraversal.set(replaceList)(target) shouldEqual
List((true, 0), (false, 1), (true, 2))
}

test("unsafePartsOf from ATraversal ignores extra elements") {
val target = List("A", "B", "C").zipWithIndex
val replaceList = List(true, false, true, false, true, false, true)

unsafePartsOfFromATraversal.set(replaceList)(target) shouldEqual
List((true, 0), (false, 1), (true, 2))
}

test("unsafePartsOf from Traversal crashes, when setting the lens with a wrong number of list elements") {
val target = List("A", "B", "C").zipWithIndex
val thrown = intercept[IllegalArgumentException] {
unsafePartsOfFromTraversal.set(List(true, false))(target)
}

thrown.getMessage shouldEqual "Not enough elements were supplied"
}

test("unsafePartsOf from ATraversal crashes, when setting the lens with a wrong number of list elements") {
val target = List("A", "B", "C").zipWithIndex
val thrown = intercept[IllegalArgumentException] {
unsafePartsOfFromATraversal.set(List(true, false))(target)
}

thrown.getMessage shouldEqual "Not enough elements were supplied"
}

test("unsafePartsOf from Traversal is safer when using over") {
val target = List("A", "B", "C").zipWithIndex
val replaceList = List(true, false)
val result = unsafePartsOfFromATraversal.over { list =>
list.zipWithIndex.map { case (_, i) => replaceList.lift(i).getOrElse(true) }
}

result(target) shouldEqual List((true, 0), (false, 1), (true, 2))
}
}
2 changes: 1 addition & 1 deletion project/MimaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import sbt.Keys.{moduleName, organization}
import sbt._

object MimaSettings {
lazy val previousArtifactsToCompare = "0.1.0"
lazy val previousArtifactsToCompare = "0.2.0"
def mimaSettings(failOnProblem: Boolean) = Seq(
mimaFailOnProblem := failOnProblem,
mimaPreviousArtifacts := Set(organization.value %% moduleName.value % previousArtifactsToCompare)
Expand Down
Loading

0 comments on commit fbf8953

Please sign in to comment.