I'm interested how to improve the piece of code bellow. The idea is to build an immutable composable builder. In the end the builder just builds a Map[String, Object]. I want to be able to define core builder components that can be reused and to also let people define their own additional builders to extend the main builder. I'm able to do so but not without the ugly use of reflection. The code looks as follows:
object TestPizzaBuilder {
def main(args: Array[String]): Unit = {
val build = new PizzaBuilder()
.withCheese("blue")
.withSauce("tomato")
.build()
println(build)
}
}
object PizzaBuilder {
type BuilderParams = Map[String, Object]
}
class PizzaBuilder(params: BuilderParams = Map.empty) extends BaseBuilder[PizzaBuilder](params: BuilderParams)
with CheeseBuilder[PizzaBuilder]
with SauceBuilder[PizzaBuilder] {
}
abstract class BaseBuilder[A <: BaseBuilder[A]](params: BuilderParams)(implicit tag: ClassTag[A]) {
protected def _copy(tuples: (String, Object)*): A = {
val constr = tag.runtimeClass.getConstructors()(0)
constr.newInstance(params ++ tuples).asInstanceOf[A]
}
def build(): Map[String, Object] = {
params
}
}
trait CheeseBuilder[A <: BaseBuilder[A]] {
this: BaseBuilder[A] =>
def withCheese(cheese: String): A = _copy("cheese" -> cheese)
}
trait SauceBuilder[A <: BaseBuilder[A]] {
this: BaseBuilder[A] =>
def withSauce(sauce: String): A = _copy("sauce" -> sauce)
}
Do you have suggestions how reflection can be avoided in this scenario yet keeping the builder immutable and also allowing to compose the builder of tiny other builders.
Smallest change would be to pass the constructor (as a function) instead of a ClassTag:
abstract class BaseBuilder[A <: BaseBuilder[A]](params: BuilderParams)(constructor: BuilderParams => A) {
protected def _copy(tuples: (String, Object)*): A = constructor(params ++ tuples)
def build(): Map[String, Object] = {
params
}
}
// or class PizzaBuilder(params: BuilderParams = Map.empty) extends BaseBuilder[PizzaBuilder](params)(new PizzaBuilder(_))
case class PizzaBuilder(params: BuilderParams = Map.empty) extends BaseBuilder[PizzaBuilder](params)(PizzaBuilder)
with CheeseBuilder[PizzaBuilder]
with SauceBuilder[PizzaBuilder] {
}
Related
I want to bind a check method to the Test in such a way that the implementation does not contain an argument (look at the last line). It is necessary to use type classes here, but I'm new in Scala, so I have problems.
Object Checker is my attempts to solve the problem. Perhaps it is enough to make changes to it...
trait Test[+T] extends Iterable[T]
class TestIterable[+T](iterable: Iterable[T]) extends Test[T] {
override def iterator: Iterator[T] = iterable.iterator
}
object Test {
def apply[T](iterable: Iterable[T]): Test[T] = new TestIterable[T](iterable)
}
trait Check[M] {
def check(m: M): M
}
object Checker {
def apply[M](implicit instance: Check[M]): Check[M] = instance
implicit def checkSyntax[M: Check](m: M): CheckOps[M] = new CheckOps[M](m)
private implicit def test[T](m: Test[T]) : Check[Test[T]] = {
new Check[Test[T]] {
override def check(m: Test[T]) = m
}
}
final class CheckOps[M: Check](m: M) {
def x2: M = Checker[M].check(m)
}
}
import Checker._
val test123 = Test(Seq(1, 2, 3))
Test(test123).check
Following this answer shows how to bind an Enumeration to a form using a case class.
However in Play 2.7.3 this code fails with:
No Json serializer found for type jura.SearchRequest. Try to implement an implicit Writes or Format for this type.
When I implement the formatter:
object SearchRequest {
implicit val searchRequestFormat: OFormat[SearchRequest] = Json.format[SearchRequest]
}
I get
No instance of play.api.libs.json.Format is available for
scala.Enumeration.Value in the implicit scope
Should I be trying to write a formatter for the system scala.Enumeration type?
Or is there another way to implement a formatter when Enumerations are involved?
Test case here.
I use for any Enumeration this Library: enumeratum
With Dotty there will be great Enumerations, but until then I think moving to enumeratum is the best way handling Enumerations in Scala. See also Dotty - Enumerations.
As a bonus there is a play-json Extension, see Play JSON Extension.
With this your code would look like this:
import enumeratum.{ PlayJsonEnum, Enum, EnumEntry }
sealed trait SearchRequest extends EnumEntry
object SearchRequest extends Enum[SearchRequest] with PlayJsonEnum[SearchRequest] {
val values = findValues
case object SuperRequest extends SearchRequest
case object SimpleRequest extends SearchRequest
..
}
In essence PlayJsonEnum[SearchRequest] does all the work.
To write the enum as a string as cchantep says you can use Writes.enumNameWrites, we specifically use to read and write the ID. Therefore we have an EnumFormat in the package global for enums:
package object enums {
implicit def reads[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
def reads(json: JsValue): JsResult[E#Value] = json match {
case JsNumber(s) =>
try {
JsSuccess(enum.apply(s.toInt))
} catch {
case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'")
}
case _ => JsError("Number value expected")
}
}
implicit def writes[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] {
def writes(v: E#Value): JsValue = JsNumber(v.id)
}
implicit def formatID[E <: Enumeration](enum: E): Format[E#Value] =
Format(reads(enum), writes)
def readsString[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
def reads(json: JsValue): JsResult[E#Value] = json match {
case JsString(s) => {
try {
JsSuccess(enum.withName(s))
} catch {
case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'")
}
}
case _ => JsError("String value expected")
}
}
implicit def writesString[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] {
def writes(v: E#Value): JsValue = JsString(v.toString)
}
implicit def formatString[E <: Enumeration](enum: E): Format[E#Value] =
Format(readsString(enum), writesString)
}
And used:
object SearchRequest extends Enumeration(1) {
type SearchRequest = Value
val ONE /*1*/ , TWO /*2*/ , ETC /*n*/ = Value
implicit val searchRequestFormat: Format[SearchRequest] = formatID(SearchRequest)
}
Add the following to your Enumeration Object
implicit object MatchFilterTypeFormatter extends Formatter[MatchFilterType.Value] {
override val format = Some(("format.enum", Nil))
override def bind(key: String, data: Map[String, String]) = {
try {
Right(MatchFilterType.withName(data.get(key).head))
} catch {
case e:NoSuchElementException => Left(Seq(play.api.data.FormError(key, "Invalid MatchFilterType Enumeration")))
}
}
override def unbind(key: String, value: MatchFilterType.Value) = {
Map(key -> value.toString)
}
}
implicit val matchFilterTypeFormat = new Format[MatchFilterType.MatchFilterType] {
def reads(json: JsValue) = JsSuccess(MatchFilterType.withName(json.as[String]))
def writes(myEnum: MatchFilterType.MatchFilterType) = JsString(myEnum.toString)
}
And then the Formatter/Controller given in the question will work.
A working test case is here.
I have
trait OptionTransaction {
def data: Data
}
BuyOptionTransaction extends OptionTransaction
SellOptionTransaction extends OptionTransaction
I use these with a Formatter type class to create string representations of various transactions
trait Formatter[T] {
def format(ot:T):String
}
object Formatter {
def apply[T](implicit screen: Formatter[T]) = screen
implicit val buyOT = new Formatter[BuyOptionTransaction] {
def format(ot: BuyOptionTransaction):String = ot.x.toString
}
implicit val sellOT = new Formatter[SellOptionTransaction] {
def format(ot: SellOptionTransaction):String = ot.y.toString
}
}
This is the entry point:
import Formatter._
val closeTransactions: List[OptionTransaction] = ...
closeTransactions.map(startFormat)
The problem is that closeTransactions has type List[OptionTransaction] and the type class needs OptionTransaction downcast to BuyOptionTransaction or SellOptionTransaction otherwise it won't find the implicit formatter.
How can I achieve this downcast automatically?
If you want to deal with the polymorphism runtime, you need to implement some kind of dynamic (runtime) dispatch instead of Type classes, which are static one (compile time). It could look like this:
type Data = String
trait OptionTransaction {
def data: Data = ""
}
class BuyOptionTransaction extends OptionTransaction {
def x: String = "X"
}
class SellOptionTransaction extends OptionTransaction {
def y: String = "Y"
}
trait Formatter[T] {
def format(ot:T):String
}
object Formatter {
def apply[T](implicit screen: Formatter[T]) = screen
def selectFormatter[T](obj: T)(implicit formatter: Formatter[T]) = formatter
implicit val buyOT = new Formatter[BuyOptionTransaction] {
def format(ot: BuyOptionTransaction):String = ot.x.toString
}
implicit val sellOT = new Formatter[SellOptionTransaction] {
def format(ot: SellOptionTransaction):String = ot.y.toString
}
implicit val ot = new Formatter[OptionTransaction] {
def format(ot: OptionTransaction):String = ot match {
case ot: BuyOptionTransaction =>
selectFormatter(ot).format(ot)
case ot: SellOptionTransaction =>
selectFormatter(ot).format(ot)
}
}
}
def startFormat[T](ot: T)(implicit ev: Formatter[T]) = {
ev.format(ot)
}
import Formatter._
val closeTransactions: List[OptionTransaction] = List(new BuyOptionTransaction, new SellOptionTransaction)
closeTransactions.map(startFormat(_))
You can collect the appropriate types:
val closeTransactions: List[OptionTransaction] = ???
val buys = closeTransactions.collect { case b: BuyOptionTransaction => b}
val sells = closeTransactions.collect { case s: SellOptionTransaction => s}
Now you can apply the proper typeclasses.
It would be probably better to add the action/transformation you want to the OptionTransaction trait and use that for dynamic binding. If you want to keep working only for one of them, please take a look at this answer.
Any idea how to get the following to work:
trait HttpTransport {
def doGet(str: String): String
}
trait CoreGet {
def GET(str: String)(implicit imp:String): List[String]
}
trait VerbGet extends CoreGet with HttpTransport {
override def GET(str: String)(implicit imp:String): List[String]= {
println("->VerbGet.GET")
val str1 = doGet(str)
// and more biz logic calls here
List(s"\nverb (biz logic) called url $str and got '${str1}'>")
}
}
// PlayGet {
implicit class ExtendCoreGet(coreGet: CoreGet) {
def GET[A](url: String)(implicit imp:String, imp2: List[A]): List[A]= {
println(s"->ExtendCoreGet.GET($url)")
val coreGetResult = coreGet.GET(url)
coreGetResult.flatMap(_ => imp2)
}
}
trait Play extends HttpTransport {
override def doGet(str: String): String = {
println("->Play.doGet")
s"\nPlay.doGet($str)>"
}
}
val client = new VerbGet with Play
client.GET("www.go.com")("hello", List("1")) //<-- does not compile
Compiler error:
too many arguments (2) for method GET: (implicit imp:
String)List[String]
You can play with the code here:
https://scastie.scala-lang.org/arminio/tN9NfdxGQUmusrNL0lJ78w
It looks like you are trying extend functionality of VerbGet. You need to fix two things:
1. ExtendCoreGet must extend AnyVal to add more methods.
2. You can add new functionality by adding new method say GET2 but you can't overload existing method. Renmaed your GET to GET2 or something meaningful.
ExtendCoreGet definition should be
implicit class ExtendCoreGet(val coreGet: CoreGet) extends AnyVal {
def GET2[A](url: String)(implicit imp:String, imp2: List[A]): List[A]= {
coreGet.GET(url).flatMap(_ => imp2)
}
}
In short, I want to declare a trait like this:
trait Test {
def test(amount: Int): A[Int] // where A must be a Monad
}
so that I can use it without knowing what monad that A is, like:
class Usecase {
def someFun(t: Test) = for { i <- t.test(3) } yield i+1
}
more details...
essentially, I want to do something like this:
class MonadResultA extends SomeUnknownType {
// the base function
def test(s: String): Option[Int] = Some(3)
}
class MonadResultB(a: MonadResultA) extends SomeUnknownType {
// added a layer of Writer on top of base function
def test(s: String): WriterT[Option, String, Int] = WriterT.put(a.test(s))("the log")
}
class Process {
def work(x: SomeUnknownType) {
for {
i <- x.test("key")
} yield i+1
}
}
I wanted to be able to pass any instances of MonadResultA or MonadResultB without making any changes to the function work.
The missing piece is that SomeUnknowType, which I guess should have a test like below to make the work function compiles.
trait SomeUnknowType {
def test(s: String): T[Int] // where T must be some Monad
}
As I've said, I'm still learning this monad thing... if you find my code is not the right way to do it, you're more than welcomed to point it out~
thanks a lot~~
Assuming you have a type class called Monad you can just write
def test[A:Monad](amount: Int): A[Int]
The compiler will require that there is an implicit of type Monad[A] in scope when test is called.
EDIT:
I'm still not sure what you're looking for, but you could package up a monad value with its corresponding type class in a trait like this:
//trait that holds value and monad
trait ValueWithMonad[E] {
type A[+E]
type M <: Monad[A]
val v:A[E]
val m:M
}
object M {
//example implementation of test method
def test(amount:Int):ValueWithMonad[Int] = new ValueWithMonad[Int] {
type A[+E] = Option[E]
type M = Monad[Option]
override val v = Option(amount)
override val m = OptionMonad
}
//test can now be used like this
def t {
val vwm = test(1)
vwm.m.bind(vwm.v, (x:Int) => {
println(x)
vwm.m.ret(x)
})
}
}
trait Monad[A[_]] {
def bind[E,E2](m:A[E], f:E=>A[E2]):A[E2]
def ret[E](e:E):A[E]
}
object OptionMonad extends Monad[Option] {
override def bind[E,E2](m:Option[E], f:E=>Option[E2]) = m.flatMap(f)
override def ret[E](e:E) = Some(e)
}