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 contramap and map ops to the Transformer and PartialTransformer #591

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,16 @@ trait CatsPartialTransformerImplicits {

override def tailRecM[A, B](a: A)(
f: A => PartialTransformer[Source, Either[A, B]]
): PartialTransformer[Source, B] =
(src, failFast) => {
def run(a1: A) = partial.Result.fromCatching(f(a1).transform(src, failFast)).flatMap(identity)
@scala.annotation.tailrec
def loop(a1: A): partial.Result[B] = run(a1) match {
case partial.Result.Value(Left(a2)) => loop(a2)
case partial.Result.Value(Right(b)) => partial.Result.Value(b)
case errors => errors.asInstanceOf[partial.Result[B]]
}
loop(a)
MateuszKubuszok marked this conversation as resolved.
Show resolved Hide resolved
): PartialTransformer[Source, B] = { (src, failFast) =>
def run(a1: A) = partial.Result.fromCatching(f(a1).transform(src, failFast)).flatMap(identity)
@scala.annotation.tailrec
def loop(a1: A): partial.Result[B] = run(a1) match {
case partial.Result.Value(Left(a2)) => loop(a2)
case partial.Result.Value(Right(b)) => partial.Result.Value(b)
case errors => errors.asInstanceOf[partial.Result[B]]
}
loop(a)
}

override def raiseError[A](e: partial.Result.Errors): PartialTransformer[Source, A] = (_, _) => e

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,14 @@ trait CatsTotalTransformerImplicits {

override def tailRecM[A, B](a: A)(
f: A => Transformer[Source, Either[A, B]]
): Transformer[Source, B] =
src => {
@scala.annotation.tailrec
def loop(a1: A): B = f(a1).transform(src) match {
case Left(a2) => loop(a2)
case Right(b) => b
}
loop(a)
MateuszKubuszok marked this conversation as resolved.
Show resolved Hide resolved
): Transformer[Source, B] = { src =>
@scala.annotation.tailrec
def loop(a1: A): B = f(a1).transform(src) match {
case Left(a2) => loop(a2)
case Right(b) => b
}
loop(a)
}

override def coflatMap[A, B](
fa: Transformer[Source, A]
Expand All @@ -60,6 +59,6 @@ trait CatsTotalTransformerImplicits {
implicit final def catsContravariantForTransformer[Target]: Contravariant[Transformer[*, Target]] =
new Contravariant[Transformer[*, Target]] {
def contramap[A, B](fa: Transformer[A, Target])(f: B => A): Transformer[B, Target] =
b => fa.transform(f(b))
fa.contramap(f)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import io.scalaland.chimney.internal.runtime.{TransformerFlags, TransformerOverr
* @since 0.7.0
*/
@FunctionalInterface
trait PartialTransformer[From, To] extends PartialTransformer.AutoDerived[From, To] {
trait PartialTransformer[From, To] extends PartialTransformer.AutoDerived[From, To] { self =>

/** Run transformation using provided value as a source.
*
Expand Down Expand Up @@ -58,6 +58,7 @@ trait PartialTransformer[From, To] extends PartialTransformer.AutoDerived[From,
*/
final def transformFailFast(src: From): partial.Result[To] =
transform(src, failFast = true)

}

/** Companion of [[io.scalaland.chimney.PartialTransformer]].
Expand Down Expand Up @@ -163,7 +164,7 @@ object PartialTransformer extends PartialTransformerCompanionPlatform {
* @since 0.8.0
*/
@FunctionalInterface
trait AutoDerived[From, To] {
trait AutoDerived[From, To] extends PartialTransformerOps[From, To] {
def transform(src: From, failFast: Boolean): partial.Result[To]
}

Expand All @@ -173,6 +174,62 @@ object PartialTransformer extends PartialTransformerCompanionPlatform {
implicit def liftTotal[From, To](implicit total: Transformer[From, To]): AutoDerived[From, To] =
(src: From, failFast: Boolean) => partial.Result.fromCatching(total.transform(src))
}

trait PartialTransformerOps[From, To] { self =>

def transform(src: From, failFast: Boolean): partial.Result[To]

/** Creates a new [[io.scalaland.chimney.PartialTransformer PartialTransformer]] by applying a pure function to a
* source of type `A` before transforming it to `To`. See an example:
* {{{
* val stringTransformer: PartialTransformer[String, Int] =
* PartialTransformer.fromFunction(_.length)
*
* case class Id(id: String)
*
* implicit val idTransformer: PartialTransformer[Id, Int] =
* stringTransformer.contramap(_.id)
* }}}
*
* @param f
* a pure function that maps a value of `A` to `From`
* @return
* new [[io.scalaland.chimney.PartialTransformer PartialTransformer]]
*
* @since 1.5.0
*/
final def contramap[A](f: A => From): PartialTransformer[A, To] = new PartialTransformer[A, To] {
override def transform(src: A, failFast: Boolean): partial.Result[To] =
self.transform(f(src), failFast)
}

/** Creates a new [[io.scalaland.chimney.PartialTransformer PartialTransformer]] by applying a pure function to a
* [[io.scalaland.chimney.partial.Result Result]] of transforming `From` to `To`. See an example:
* {{{
* val stringTransformer: PartialTransformer[String, Int] =
* PartialTransformer.fromFunction(_.length)
*
* case class Length(length: Int)
*
* implicit val toLengthTransformer: PartialTransformer[String, Length] =
* stringTransformer.map(id => Length(id))
* }}}
*
* @param f
* a pure function that maps a value of `To` to `A`
* @return
* new [[io.scalaland.chimney.PartialTransformer PartialTransformer]]
*
* @since 1.5.0
*/
final def map[A](f: To => A): PartialTransformer[From, A] = new PartialTransformer[From, A] {
override def transform(src: From, failFast: Boolean): partial.Result[A] =
self.transform(src, failFast) match {
case partial.Result.Value(to) => partial.Result.Value(f(to))
case errs: partial.Result.Errors => errs.asInstanceOf[partial.Result[A]]
}
}
}
}
// extended by PartialTransformerCompanionPlatform
private[chimney] trait PartialTransformerLowPriorityImplicits1 { this: PartialTransformer.type =>
Expand Down
54 changes: 52 additions & 2 deletions chimney/src/main/scala/io/scalaland/chimney/Transformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import io.scalaland.chimney.internal.runtime.{TransformerFlags, TransformerOverr
* @since 0.1.0
*/
@FunctionalInterface
trait Transformer[From, To] extends Transformer.AutoDerived[From, To] {
trait Transformer[From, To] extends Transformer.AutoDerived[From, To] { self =>

/** Run transformation using provided value as a source.
*
Expand All @@ -30,6 +30,7 @@ trait Transformer[From, To] extends Transformer.AutoDerived[From, To] {
* @since 0.1.0
*/
def transform(src: From): To

}

/** Companion of [[io.scalaland.chimney.Transformer]].
Expand Down Expand Up @@ -96,12 +97,61 @@ object Transformer extends TransformerCompanionPlatform {
* @since 0.8.0
*/
@FunctionalInterface
trait AutoDerived[From, To] {
trait AutoDerived[From, To] extends TransformerOps[From, To] {
def transform(src: From): To
}

/** @since 0.8.0 */
object AutoDerived extends TransformerAutoDerivedCompanionPlatform

trait TransformerOps[From, To] { self =>

def transform(src: From): To

/** Creates a new [[io.scalaland.chimney.Transformer Transformer]] by applying a pure function to a source of type
* `A` before transforming it to `To`. See an example:
* {{{
* val stringTransformer: Transformer[String, Int] = _.length
*
* case class Id(id: String)
*
* implicit val idTransformer: Transformer[Id, Int] =
* stringTransformer.contramap(_.id)
* }}}
*
* @param f
* a pure function that maps a value of `A` to `From`
* @return
* new [[io.scalaland.chimney.Transformer Transformer]]
*
* @since 1.5.0
*/
final def contramap[A](f: A => From): Transformer[A, To] = new Transformer[A, To] {
override def transform(src: A): To = self.transform(f(src))
}

/** Creates a new [[io.scalaland.chimney.Transformer Transformer]] by applying a pure function to a result of
* transforming `From` to `To`. See an example:
* {{{
* val stringTransformer: Transformer[String, Int] = _.length
*
* case class Length(length: Int)
*
* implicit val toLengthTransformer: Transformer[String, Length] =
* stringTransformer.map(id => Length(id))
* }}}
*
* @param f
* a pure function that maps a value of `To` to `A`
* @return
* new [[io.scalaland.chimney.Transformer Transformer]]
*
* @since 1.5.0
*/
final def map[A](f: To => A): Transformer[From, A] = new Transformer[From, A] {
override def transform(src: From): A = f(self.transform(src))
}
}
}
// extended by TransformerCompanionPlatform
private[chimney] trait TransformerLowPriorityImplicits1 extends TransformerLowPriorityImplicits2 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.scalaland.chimney

import io.scalaland.chimney.dsl.*

class PartialTransformerSpec extends ChimneySpec {

private val pt1 = PartialTransformer[String, Int](str => partial.Result.fromValue(str.toInt))
Expand Down Expand Up @@ -73,4 +75,73 @@ class PartialTransformerSpec extends ChimneySpec {
// no second error due to fail fast mode
)
}

test("map") {
case class Length(length: Int)

trait Prefix {
def code: Int
}

object Prefix {

def from(i: Int): Prefix = i match {
case 1 => FooPrefix
case 2 => BarPrefix
case _ => NanPrefix
}

case object NanPrefix extends Prefix {
override def code: Int = 0
}

case object FooPrefix extends Prefix {
override def code: Int = 1
}

case object BarPrefix extends Prefix {
override def code: Int = 2
}
}

implicit val toLengthTransformer: PartialTransformer[String, Length] =
pt1.map(Length.apply)

implicit val toPrefixTransformer: PartialTransformer[String, Prefix] =
pt1.map(Prefix.from)

val id = "2"
id.intoPartial[Length].transform ==> partial.Result.fromValue(Length(id.toInt))
id.intoPartial[Prefix].transform ==> partial.Result.fromValue(Prefix.BarPrefix)
}

test("contramap") {
case class Id(id: String)

trait Prefix {
def value: String
}

object Prefix {
case object FooPrefix extends Prefix {
override def value: String = "1"
}

case object BarPrefix extends Prefix {
override def value: String = "2"
}
}

implicit val idTransformer: PartialTransformer[Id, Int] =
pt1.contramap(_.id)

implicit val prefixTransformer: PartialTransformer[Prefix, Int] =
pt1.contramap(_.value)

val id = "1"
Id(id).intoPartial[Int].transform ==> partial.Result.fromValue(id.toInt)

val prefix: Prefix = Prefix.FooPrefix
prefix.intoPartial[Int].transform ==> partial.Result.fromValue(id.toInt)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.scalaland.chimney

import io.scalaland.chimney.dsl.*

class TotalTransformerSpec extends ChimneySpec {

test("map") {
case class Length(length: Int)

trait Prefix {
def code: Int
}

object Prefix {

def from(i: Int): Prefix = i match {
case 1 => FooPrefix
case 2 => BarPrefix
case _ => NanPrefix
}

case object NanPrefix extends Prefix {
override def code: Int = 0
}

case object FooPrefix extends Prefix {
override def code: Int = 1
}

case object BarPrefix extends Prefix {
override def code: Int = 2
}
}

val stringTransformer = new Transformer[String, Int] {
override def transform(src: String): Int = src.length
}

implicit val toLengthTransformer: Transformer[String, Length] =
stringTransformer.map(Length.apply)

implicit val toPrefixTransformer: Transformer[String, Prefix] =
stringTransformer.map(Prefix.from)

val id = "1"
id.into[Length].transform ==> Length(id.length)
id.into[Prefix].transform ==> Prefix.FooPrefix
}

test("contramap") {
case class Id(id: String)

case class Length(length: Int)

trait Prefix {
def value: String
}

object Prefix {
case object FooPrefix extends Prefix {
override def value: String = "Foo"
}

case object BarPrefix extends Prefix {
override def value: String = "Bar"
}
}

val stringTransformer = new Transformer[String, Length] {
override def transform(src: String): Length = Length(src.length)
}

implicit val idTransformer: Transformer[Id, Length] =
stringTransformer.contramap(_.id)

implicit val prefixTransformer: Transformer[Prefix, Length] =
stringTransformer.contramap(_.value)

val id = "id"
Id(id).into[Length].transform ==> Length(id.length)

val prefix: Prefix = Prefix.FooPrefix
prefix.into[Length].transform ==> Length(prefix.value.length)
}
}
Loading