Skip to content

Commit

Permalink
Merge pull request #78 from LMnet/mapat
Browse files Browse the repository at this point in the history
Relax type constraint for QuicklensMapAt
  • Loading branch information
adamw authored Dec 22, 2021
2 parents 84d4d4e + 63a1fc4 commit 772266c
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.softwaremill

import com.softwaremill.quicklens.{QuicklensMapAtFunctor, canOnlyBeUsedInsideModify}

import scala.annotation.compileTimeOnly
import scala.collection.Factory
import scala.collection.SeqLike
import scala.language.experimental.macros
import scala.language.higherKinds

package object quicklens {
package object quicklens extends LowPriorityImplicits {

private def canOnlyBeUsedInsideModify(method: String) =
private[softwaremill] def canOnlyBeUsedInsideModify(method: String) =
s"$method can only be used inside modify"

/**
Expand Down Expand Up @@ -177,14 +179,6 @@ package object quicklens {
PathLazyModify[T, V]((t, vv) => self.doModify(t, u => f2.doModify(u, vv)))
}

implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) {
@compileTimeOnly(canOnlyBeUsedInsideModify("each"))
def each: T = sys.error("")

@compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere"))
def eachWhere(p: T => Boolean): T = sys.error("")
}

trait QuicklensFunctor[F[_], A] {
def map(fa: F[A])(f: A => A): F[A]
def each(fa: F[A])(f: A => A): F[A] = map(fa)(f)
Expand Down Expand Up @@ -239,8 +233,8 @@ package object quicklens {
def index(fa: F[T], idx: Int)(f: T => T): F[T]
}

implicit class QuicklensMapAt[M[KT, TT] <: Map[KT, TT], K, T](t: M[K, T])(
implicit f: QuicklensMapAtFunctor[M, K, T]
implicit class QuicklensMapAt[M[KT, TT], K, T](t: M[K, T])(
implicit f: QuicklensMapAtFunctor[M, K, T]
) {
@compileTimeOnly(canOnlyBeUsedInsideModify("at"))
def at(idx: K): T = sys.error("")
Expand Down Expand Up @@ -311,3 +305,19 @@ package object quicklens {
override def eachRight(e: Either[L, R])(f: (R) => R) = e.map(f)
}
}

sealed trait LowPriorityImplicits {

import quicklens._

/**
* `QuicklensEach` is in `LowPriorityImplicits` to not conflict with the `QuicklensMapAtFunctor` on `each` calls.
*/
implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) {
@compileTimeOnly(canOnlyBeUsedInsideModify("each"))
def each: T = sys.error("")

@compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere"))
def eachWhere(p: T => Boolean): T = sys.error("")
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.softwaremill

import com.softwaremill.quicklens.{QuicklensMapAtFunctor, canOnlyBeUsedInsideModify}

import scala.annotation.compileTimeOnly
import scala.collection.TraversableLike
import scala.collection.SeqLike
import scala.collection.generic.CanBuildFrom
import scala.language.experimental.macros
import scala.language.higherKinds

package object quicklens {
package object quicklens extends LowPriorityImplicits {

private def canOnlyBeUsedInsideModify(method: String) =
private[softwaremill] def canOnlyBeUsedInsideModify(method: String) =
s"$method can only be used inside modify"

/**
Expand Down Expand Up @@ -178,14 +180,6 @@ package object quicklens {
PathLazyModify[T, V]((t, vv) => self.doModify(t, u => f2.doModify(u, vv)))
}

implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) {
@compileTimeOnly(canOnlyBeUsedInsideModify("each"))
def each: T = sys.error("")

@compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere"))
def eachWhere(p: T => Boolean): T = sys.error("")
}

trait QuicklensFunctor[F[_], A] {
def map(fa: F[A])(f: A => A): F[A]
def each(fa: F[A])(f: A => A): F[A] = map(fa)(f)
Expand Down Expand Up @@ -241,8 +235,8 @@ package object quicklens {
def index(fa: F[T], idx: Int)(f: T => T): F[T]
}

implicit class QuicklensMapAt[M[KT, TT] <: Map[KT, TT], K, T](t: M[K, T])(
implicit f: QuicklensMapAtFunctor[M, K, T]
implicit class QuicklensMapAt[M[KT, TT], K, T](t: M[K, T])(
implicit f: QuicklensMapAtFunctor[M, K, T]
) {
@compileTimeOnly(canOnlyBeUsedInsideModify("at"))
def at(idx: K): T = sys.error("")
Expand Down Expand Up @@ -316,3 +310,19 @@ package object quicklens {
override def eachRight(e: Either[L, R])(f: (R) => R) = e.right.map(f)
}
}

sealed trait LowPriorityImplicits {

import quicklens._

/**
* `QuicklensEach` is in `LowPriorityImplicits` to not conflict with the `QuicklensMapAtFunctor` on `each` calls.
*/
implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) {
@compileTimeOnly(canOnlyBeUsedInsideModify("each"))
def each: T = sys.error("")

@compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere"))
def eachWhere(p: T => Boolean): T = sys.error("")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.softwaremill.quicklens

import com.softwaremill.quicklens.TestData._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class QuicklensMapAtFunctorTest extends AnyFlatSpec with Matchers {

"QuicklensMapAtFunctor" should "work for types which is not a subtype of Map" in {
case class MapLike[K, V](underlying: Map[K, V])

implicit def instance[K, T]: QuicklensMapAtFunctor[MapLike, K, T] = new QuicklensMapAtFunctor[MapLike, K, T] {
private val mapInstance: QuicklensMapAtFunctor[Map, K, T] =
implicitly[QuicklensMapAtFunctor[Map, K, T]]

def at(fa: MapLike[K, T], idx: K)(f: T => T): MapLike[K, T] =
MapLike(mapInstance.at(fa.underlying, idx)(f))
def atOrElse(fa: MapLike[K, T], idx: K, default: => T)(f: T => T): MapLike[K, T] =
MapLike(mapInstance.atOrElse(fa.underlying, idx, default)(f))
def index(fa: MapLike[K, T], idx: K)(f: T => T): MapLike[K, T] =
MapLike(mapInstance.index(fa.underlying, idx)(f))
def each(fa: MapLike[K, T])(f: T => T): MapLike[K, T] =
MapLike(mapInstance.each(fa.underlying)(f))
}

modify(MapLike(m1))(_.at("K1").a5.name).using(duplicate) should be(MapLike(m1dup))
}
}

0 comments on commit 772266c

Please sign in to comment.