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,33 @@ trait PartialTransformer[From, To] extends PartialTransformer.AutoDerived[From,
*/
final def transformFailFast(src: From): partial.Result[To] =
transform(src, failFast = true)

/** 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] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is map in partial, there could also be mapPartial[A](f: To => partial.Result[A]): PartialTransformer[From, A].

Since Transformer has contramap the PartialTransformer should also get one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this once again and don't see any issue with adding contramap to PartialTransformer and map to Transformer, respectively.

What about mapPartial, it might be useful indeed, but don't you mind if we would add it separately?

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]]
}
}
}

/** Companion of [[io.scalaland.chimney.PartialTransformer]].
Expand Down
24 changes: 23 additions & 1 deletion 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,28 @@ trait Transformer[From, To] extends Transformer.AutoDerived[From, To] {
* @since 0.1.0
*/
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))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since PartialTransformer has map the Transformer should also get one.

}

/** Companion of [[io.scalaland.chimney.Transformer]].
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,43 @@ 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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.scalaland.chimney

import io.scalaland.chimney.dsl.*

class TotalTransformerSpec extends ChimneySpec {

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