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

econstruct, like scalaz.MonadError.emap #120

Merged
merged 3 commits into from
Aug 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions core/shared/src/main/scala/interface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,29 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] (
final def construct[Return](makeParam: Param[Typeclass, Type] => Return): Type =
rawConstruct(parameters map makeParam)

/**
* Like construct but allows each parameter to fail with an error.
*
* @see construct
*/
final def constructEither[E <: AnyRef, Return](makeParam: Param[Typeclass, Type] => Either[E, Return]): Either[E, Type] = {
// poor man's scalaz.Traverse
try {
Right(
rawConstruct(
parameters.map { p =>
makeParam(p) match {
case Left(e) => throw EarlyExit(e)
case Right(a) => a
}
}
)
)
} catch {
case EarlyExit(err) => Left(err.asInstanceOf[E])
}
}

/** constructs a new instance of the case class type
*
* Like [[construct]] this method is implemented by Magnolia and lets you construct case class
Expand Down Expand Up @@ -248,3 +271,5 @@ final case class TypeName(owner: String, short: String) {
* whose full name contains the given [[String]]
*/
final class debug(typeNamePart: String = "") extends scala.annotation.StaticAnnotation

private[magnolia] final case class EarlyExit[E](e: E) extends Exception with util.control.NoStackTrace
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that using return will avoid the stacktrace being generated too.

23 changes: 16 additions & 7 deletions examples/shared/src/main/scala/default.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import magnolia._
import scala.language.experimental.macros

/** typeclass for providing a default value for a particular type */
trait Default[T] { def default: T }
trait Default[T] { def default: Either[String, T] }

/** companion object and derivation object for [[Default]] */
object Default {
Expand All @@ -28,24 +28,33 @@ object Default {
/** constructs a default for each parameter, using the constructor default (if provided),
* otherwise using a typeclass-provided default */
def combine[T](ctx: CaseClass[Default, T]): Default[T] = new Default[T] {
def default = ctx.construct { param =>
param.default.getOrElse(param.typeclass.default)
def default = ctx.constructEither { param =>
param.default match {
case Some(arg) => Right(arg)
case None => param.typeclass.default
}
}
}

/** chooses which subtype to delegate to */
def dispatch[T](ctx: SealedTrait[Default, T])(): Default[T] = new Default[T] {
def default: T = ctx.subtypes.head.typeclass.default
def default = ctx.subtypes.headOption match {
case Some(sub) => sub.typeclass.default
case None => Left("no subtypes")
}
}

/** default value for a string; the empty string */
implicit val string: Default[String] = new Default[String] { def default = "" }
implicit val string: Default[String] = new Default[String] { def default = Right("") }

/** default value for ints; 0 */
implicit val int: Default[Int] = new Default[Int] { def default = 0 }
implicit val int: Default[Int] = new Default[Int] { def default = Right(0) }

/** oh, no, there is no default Boolean... whatever will we do? */
implicit val boolean: Default[Boolean] = new Default[Boolean] { def default = Left("truth is a lie") }

/** default value for sequences; the empty sequence */
implicit def seq[A]: Default[Seq[A]] = new Typeclass[Seq[A]] { def default = Seq.empty }
implicit def seq[A]: Default[Seq[A]] = new Typeclass[Seq[A]] { def default = Right(Seq.empty) }

/** generates default instances of [[Default]] for case classes and sealed traits */
implicit def gen[T]: Default[T] = macro Magnolia.gen[T]
Expand Down
23 changes: 23 additions & 0 deletions examples/shared/src/main/scala/semiauto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package magnolia.examples

import scala.language.experimental.macros
import magnolia._

trait SemiDefault[A] {
def default: A
}
object SemiDefault {
type Typeclass[T] = SemiDefault[T]

def combine[T](ctx: CaseClass[SemiDefault, T]): SemiDefault[T] = new SemiDefault[T] {
def default = ctx.construct(p => p.default.getOrElse(p.typeclass.default))
}
def dispatch[T](ctx: SealedTrait[SemiDefault, T])(): SemiDefault[T] = new SemiDefault[T] {
def default = ctx.subtypes.head.typeclass.default
}
implicit val string: SemiDefault[String] = new SemiDefault[String] { def default = "" }
implicit val int: SemiDefault[Int] = new SemiDefault[Int] { def default = 0 }

// NOT IMPLICIT
def gen[T]: SemiDefault[T] = macro Magnolia.gen[T]
}
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.1.1
sbt.version=1.1.6
30 changes: 24 additions & 6 deletions tests/src/main/scala/tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,22 @@ case class Recursive(children: Seq[Recursive])
class GenericCsv[A: Csv]
object ParamCsv extends GenericCsv[Param]


class NotDerivable

case class NoDefault(value: Boolean)

// this should *not* be derived: both ServiceName and Option[String] should fail
final case class LoggingConfig(
name: ServiceName,
optName: Option[String]
)
object LoggingConfig {
implicit val semi: SemiDefault[LoggingConfig] = SemiDefault.gen
}

final case class ServiceName(value: String) extends AnyVal

object Tests extends TestApp {

def tests(): Unit = for (_ <- 1 to 1) {
Expand Down Expand Up @@ -151,7 +165,7 @@ object Tests extends TestApp {

test("construct a default value") {
Default.gen[Entity].default
}.assert(_ == Company(""))
}.assert(_ == Right(Company("")))

test("construction of Show instance for Leaf") {
scalac"""
Expand Down Expand Up @@ -179,7 +193,7 @@ object Tests extends TestApp {

test("access default constructor values") {
implicitly[Default[Item]].default
}.assert(_ == Item("", 1, 0))
}.assert(_ == Right(Item("", 1, 0)))

test("serialize case object as a sealed trait") {
implicitly[Show[String, Color]].show(Blue)
Expand Down Expand Up @@ -299,7 +313,7 @@ object Tests extends TestApp {

test("construct a default case class inside another class") {
Default.gen[InnerClassWithDefault].default
}.assert(_ == InnerClassWithDefault())
}.assert(_ == Right(InnerClassWithDefault()))

()
}
Expand All @@ -314,7 +328,7 @@ object Tests extends TestApp {

test("construct a default case class inside a method") {
Default.gen[LocalClassWithDefault].default
}.assert(_ == LocalClassWithDefault())
}.assert(_ == Right(LocalClassWithDefault()))

()
}
Expand All @@ -330,7 +344,11 @@ object Tests extends TestApp {

test("construct a default Account") {
Default.gen[Account].default
}.assert(_ == Account(""))
}.assert(_ == Right(Account("")))

test("construct a failed NoDefault") {
Default.gen[NoDefault].default
}.assert(_ == Left("truth is a lie"))

test("show a Portfolio of Companies") {
Show.gen[Portfolio].show(Portfolio(Company("Alice Inc"), Company("Bob & Co")))
Expand Down Expand Up @@ -379,7 +397,7 @@ object Tests extends TestApp {
implicit def showDefaultOption[A](
implicit showA: Show[String, A],
defaultA: Default[A]
): Show[String, Option[A]] = (optA: Option[A]) => showA.show(optA.getOrElse(defaultA.default))
): Show[String, Option[A]] = (optA: Option[A]) => showA.show(optA.getOrElse(defaultA.default.right.get))

Show.gen[Path[String]].show(OffRoad(Some(Crossroad(Destination("A"), Destination("B")))))
}.assert(_ == "OffRoad(path=Crossroad(left=Destination(value=A),right=Destination(value=B)))")
Expand Down