I am writing a piece of code which I feel has become very convoluted.
I have a API which accepts an argument which is a trait. This trait can be implemented by many types. Furthermore, each of these classes need to be handled by a specialized processor.
For example, I have created a Trait below called Context which has two actual types of MobileContext and WebContext.
let's say that the MobileContext and WebContext are logged differently and we have specialized implementations in the form of ContextWriter[MobileContext] and ContextWriter[WebContext].
The requirement is that the method should be generic, but it should be able to dispatch the call to the right ContextWriter depending on the actual type of the trait.
Here is my code.
trait Context
case class WebContext(name: String) extends Context
case class MobileContext(name: String) extends Context
trait ContextWriter[T] {
def log(message: String, context: T) : Unit
}
object ContextWriterUtil {
def log[T](message: String, context: T)(implicit writer: ContextWriter[T]) = {
writer.log(message, context)
}
}
object ContextWriterImplicits {
implicit val webImpl = new ContextWriter[WebContext] {
override def log(message: String, context: WebContext) = println(s"I am in web context ${context} and the message is ${message}")
}
implicit val mobileImpl = new ContextWriter[MobileContext] {
override def log(message: String, context: MobileContext) = println(s"I am in mobile context ${context} and the message is ${message}")
}
implicit val baseImpl = new ContextWriter[Context] {
override def log(message: String, context: Context) = context match {
case s: WebContext => {
val writer = implicitly[ContextWriter[WebContext]]
writer.log(message, s)
}
case s: MobileContext => {
val writer = implicitly[ContextWriter[MobileContext]]
writer.log(message, s)
}
case _ => throw new Exception("don't understand this type")
}
}
}
import ContextWriterImplicits._
object MyApplication extends App {
// this is the generic method.
def call[T <: Context](message: String)(implicit context: T) = {
val actualContext = implicitly[Context]
ContextWriterUtil.log(message, actualContext)
}
def web() = {
implicit val webContext = WebContext("web")
call("I am calling the method")
}
def mobile() = {
implicit val mobileContext = MobileContext("mobile")
call("I am calling the method")
}
web()
mobile()
}
This works. But I feel its too verbose and unwieldy. I want to write this in a cleaner way.
TLDR: Remove inheritance from your code.
I don't see why you would need baseImpl: ContextWriter[Context], just delete this implicit and always ask for the more precise context. call becomes:
def call[T: ContextWriter](message: String)(implicit context: T) = {
ContextWriterUtil.log(message, context)
}
For that to work you need to update web and mobile to explicitly specify the type parameter. Even if there is a single instantiation of this type parameter that make the code compile, scalac is not able to figure that out:
def web() = {
implicit val webContext = WebContext("web")
call[WebContext]("I am calling the method")
}
def mobile() = {
implicit val mobileContext = MobileContext("mobile")
call[MobileContext]("I am calling the method")
}
You might be able to get ride of the explicit typing by combining Context and ContextWriter into a single implicit. For example, why not take an ìmplicit ContextWriter argument when you instanciate your Context, and be done with it?
Related
I want to have various "flavors" of a component, each that handles a different "wire" format (e.g. String, Byte array, etc.). Example below. The innards of the read() function aren't important.
Note that on use I need to cast parameter "Heavy" to thing.WIRE to work. Since this is my top-level API I don't want the users to have to cast. They've chosen the flavor when they call FantasticThing.apply (or accept the default). After that I'd rather a cast not be needed.
How can I avoid the cast and have Scala realize that read() argument is a String based on StringFlavor being chosen?
trait Flavor {
type WIRE
def read[T](wire: WIRE)(implicit tt: TypeTag[T]): T
}
trait Maker {
def make(): Flavor
}
object StringFlavor extends Maker {
def make(): Flavor { type WIRE = String } = StringFlavor()
}
case class StringFlavor() extends Flavor {
type WIRE = String
def read[T](wire: String)(implicit tt: TypeTag[T]): T = {
println(tt.tpe)
if(tt.tpe =:= typeOf[Int]) {
5.asInstanceOf[T]
} else
throw new Exception("Boom")
}
}
object FantasticThing {
def apply[WIRE](maker: Maker = StringFlavor): Flavor = maker.make()
}
object RunMe extends App {
val thing: Flavor = FantasticThing(StringMaker)
println(thing.read[Int]("Heavy".asInstanceOf[thing.WIRE])) // <-- How can I avoid this cast?
}
Edit based on Luis Miguel's note: I can't really add that type to FantasticThing.apply() or I'd lose pluggability. I want users to easily select the Flavor they want. I've refactored a bit to show this with a factory pattern, which does add the type info you suggested, but still unfortunately leaves me with a need to cast top-level.
If I provide a bunch of Flavors then users should be able to do something like:
val foo = FantasticThing(ByteArrayFlavor)
You can make WIRE a type parameter and propagate it through a type member or your Maker type. I.e:
import scala.reflect.runtime.universe._
trait Flavor[WIRE] {
def read[T](wire: WIRE)(implicit tt: TypeTag[T]): T
}
trait Maker {
type O
def make(): Flavor[O]
}
object StringMaker extends Maker {
type O = String
def make(): Flavor[O] = StringFlavor()
}
case class StringFlavor() extends Flavor[String] {
def read[T](wire: String)(implicit tt: TypeTag[T]): T = {
if(tt.tpe =:= typeOf[Int]) {
5.asInstanceOf[T]
} else
throw new Exception("Boom")
}
}
object FantasticThing {
def apply(): Flavor[String] = StringMaker.make()
def apply(maker: Maker): Flavor[maker.O] = maker.make() // Path dependent type.
}
object RunMe extends App {
val thing: Flavor[String] = FantasticThing(StringMaker)
thing.read[Int]("Heavy") // res0: Int = 5
}
Edit: Added no-arg apply() to this anwser. If a default value for maker is used (e.g. StringMaker) you get a compile error because argument "Heavy" is now supposed to be type Maker.O. Adding the no-arg apply solves this problem while providing the same experience to the caller.
I took the liberty to modify your code with the intention to show how (what I understand of) your problem can be solved using typeclasses and type parameters, instead of type members.
import scala.reflect.runtime.universe.{TypeTag, typeOf}
implicit class Json(val underlying: String) extends AnyVal
implicit class Csv(val underlying: String) extends AnyVal
trait Flavor[W] {
def read[T](wire: W)(implicit tt: TypeTag[T]): T
}
trait Maker[W] {
def make(): Flavor[W]
}
object Maker {
implicit val StringFlavorMaker: Maker[String] = new Maker[String] {
override def make(): Flavor[String] = StringFlavor
}
implicit val JsonFlavorMaker: Maker[Json] = new Maker[Json] {
override def make(): Flavor[Json] = JsonFlavor
}
implicit val CsvFlavorMaker: Maker[Csv] = new Maker[Csv] {
override def make(): Flavor[Csv] = CsvFlavor
}
}
case object StringFlavor extends Flavor[String] {
override final def read[T](wire: String)(implicit tt: TypeTag[T]): T = {
if(tt.tpe =:= typeOf[Int])
0.asInstanceOf[T]
else
throw new Exception("Boom 1")
}
}
case object JsonFlavor extends Flavor[Json] {
override final def read[T](wire: Json)(implicit tt: TypeTag[T]): T = {
if(tt.tpe =:= typeOf[Int])
3.asInstanceOf[T]
else
throw new Exception("Boom 2")
}
}
case object CsvFlavor extends Flavor[Csv] {
override final def read[T](wire: Csv)(implicit tt: TypeTag[T]): T = {
if(tt.tpe =:= typeOf[Int])
5.asInstanceOf[T]
else
throw new Exception("Boom 3")
}
}
object FantasticThing {
def apply[W](implicit maker: Maker[W]): Flavor[W] = maker.make()
}
Then you can create and user any flavour (given there is an implicit maker in scope) this way.
val stringFlavor = FantasticThing[String]
// stringFlavor: Flavor[String] = StringFlavor
stringFlavor.read[Int]("Heavy")
// res0: Int = 0
val jsonFlavor = FantasticThing[Json]
// jsonFlavor: Flavor[Json] = JsonFlavor
jsonFlavor.read[Int]("{'heavy':'true'}")
// res1: Int = 3
val csvFlavor = FantasticThing[Csv]
// csvFlavor: Flavor[Csv] = CsvFlavor
csvFlavor.read[Int]("Hea,vy")
// res2: Int = 0
In general, is better to stay off of type members, since they are more complex and used for more advanced stuff like path dependent types.
Let me know in the comments if you have any doubt.
DISCLAIMER: I am bad with type members (still learning about them), that may motivate me to use different alternatives. - In any case, I hope you can apply something similar to your real problem..
I have a sum type, Mapping:
sealed trait Mapping
final case class XMapping(a:String)
final case class FallbackMapping(mappings: List[Mapping])
I have a typeclass defined as follows:
final case class Param(x:String)
trait ParameterLoader[T] {
def load(mapping:T) : List[Param]
}
With some instances:
object DefaultParameterLoaders {
implicit val xParameterLoader= new QueryParameterLoader[XMapping] {
override def load(mapping: XMapping): List[Param] = List(Param(mapping.a))
}
implicit val fallbackParameterLoader = new ParameterLoader[FallbackMapping] {
override def load(mapping: FallbackMapping): List[Param] =
mapping.mappings.flatMap(x => ???)
}
}
I can't find a way to have the implicit instances passed to the flatMap above. The error I get is that I'm missing an instance of ParameterLoader[Mapping]. Is there some way of telling the compiler that it should use whatever typeclass instances are in scope?
The type system is looking for a ParameterLoader[Mapping] specifically, meaning that a ParameterLoader[XMapping]/ParameterLoader[FallbackMapping] is not specific enough. You need to provide a ParameterLoader[Mapping]. You can do this using your existing definitions.
implicit def mappingLoader(implicit xpLoader: ParameterLoader[XMapping], fmLoader: ParameterLoader[FallbackMapping]) = new ParameterLoader[Mapping] {
def load(mapping: Mapping): List[QueryParam] =
mapping match {
case xm: XMapping = xpLoader.load(xm)
case fm: FallbackMapping => fmLoader.load(fm)
}
}
Alternatively, have your flatmap perform the matching logic:
implicit def fallbackParameterLoader(implicit xpLoader: ParameterLoader[XMapping]) = new ParameterLoader[FallbackMapping] {
override def load(mapping: FallbackMapping): List[Param] =
mapping.mappings.flatMap {
case xm: XMapping = xpLoader.load(xm)
case fm: FallbackMapping => this.load(fm)
}
}
For the following javascript API wrapper:
#JSName("React")
object React extends js.Object {
def createClass(init: ReactClassInit): ReactClassBuilder = ???
}
What is the suggested what to instantiate the following trait
trait ReactClassInit extends js.Object {
val render: js.ThisFunction0[js.Dynamic, js.Any]
}
Currently I am doing the following:
val * = js.Dynamic.literal
val init = *(render = new js.ThisFunction0[js.Dynamic, js.Any] {
override def apply(thisArg: js.Dynamic): js.Any = {
React.DOM.div(null, "Hello ", thisArg.props.name)
}
}).asInstanceOf[ReactClassInit]
val HelloMessage = React.createClass(init)
What I don't like about this approach is that there is no type-safety
ensuring that ReactClassInit is instantiated properly.
(Here is all of the code to put things into a better context)
//Work in progress React wrapers
#JSName("React")
object React extends js.Object {
def createClass(init: ReactClassInit): ReactClassBuilder = ???
def renderComponent(cls: ReactClassInstance, mountNode: HTMLElement) = ???
val DOM: ReactDOM = ???
}
trait ReactDOM extends js.Object {
def div(huh: js.Any, something: js.String, propsOrWhat: js.Any) = ???
}
trait ReactClassInstance extends js.Object
trait ReactClassBuilder extends js.Object {
def apply(args: js.Object): ReactClassInstance
}
trait ReactClassInit extends js.Object {
val render: js.ThisFunction0[js.Dynamic, js.Any]
}
#JSExport
object ReactTodo {
//Some helpers I use.
val * = js.Dynamic.literal
#JSExport
def main() {
helloJonT()
}
//Ideal Typed Example
def helloJonT() {
val init = *(render = new js.ThisFunction0[js.Dynamic, js.Any] {
override def apply(thisArg: js.Dynamic): js.Any = {
React.DOM.div(null, "Hello ", thisArg.props.name)
}
}).asInstanceOf[ReactClassInit]
val HelloMessage = React.createClass(init)
React.renderComponent(HelloMessage(*(name = "Jon").asInstanceOf[js.Object]), document.getElementById("content"))
}
}
Currently, the recommended approach is very close to what you are doing, except that the use of js.Dynamic.literal should be encapsulated in the companion object of your trait (ReactClassInit in your case). You can provide a type-safe apply method in that companion object like this:
trait ReactClassInit extends js.Object {
val render: js.ThisFunction0[js.Dynamic, js.Any]
}
object ReactClassInit {
def apply(render: js.ThisFunction0[js.Dynamic, js.Any]): ReactClassInit = {
js.Dynamic.literal(
render = render
).asInstanceOf[ReactClassInit]
}
}
which you can then use with:
val init = ReactClassInit(render = { (thisArg: js.Dynamic) =>
React.DOM.div(null, "Hello ", thisArg.props.name)
})
Of course this is still globally unsafe. But there is only one point in your code where you use a cast, and more importantly it is close to the definition of the type. So it is more likely that if you update one, you will update the other.
I know this is not a completely satisfactory solution. But so far in our design of Scala.js we have not yet found a really good solution to this problem.
Two side notes:
1) I strongly advise against using new js.ThisFunctionN { def apply }! It is an accident that this notation works at all. Simply use a lambda like I showed in my example. If the target type is typed as a js.ThisFunctionN already (like in my code), it'll work just like that. If, as was the case in your code, the target type is js.Any (or Any), you'll need to ascribe your lambda with : js.ThisFunction (without digit) to make sure that the compiler treats it as a this-function and not a (non-this-)function, but that's all. To make it clearer, here is how it would have looked with your code:
val init = *(render = { (thisArg: js.Dynamic) =>
React.DOM.div(null, "Hello ", thisArg.props.name)
}: js.ThisFunction).asInstanceOf[ReactClassInit]
2) You probably want your function to be typed as returning Any (or _) instead of js.Any:
trait ReactClassInit extends js.Object {
val render: js.ThisFunction0[js.Dynamic, Any]
}
Typically when you use js.Any in the result type of js.(This)Function, you mean any value, not any JS value. And Scala's type inference works best with Any in that location.
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)
}
I am trying to write a generic method f[T](id:String) that is something like this:
case class A(x:String)
case class B(y:String)
case class C(z:String)
def f[T](id:String): T = { /* equivalent to T(id) */ }
val result1:A = f[A]("123") // returns A("123")
val result2:B = f{B]("345") // returns B("345")
val result3:C = f[C]("567") // returns C("567")
Unfortunately I cannot figure out how to work with the type T inside the method, besides using reflection. By "working with the type T" i mean for example being able to do something like the following, which I know doesn't work (for illustration purposes only):
T match {
case A => A(id)
case B => B(id)
}
or simply invoke T(ID) to create a new object of whatever type T is.
I can of course break up this into three methods:
def f1(id:String): A = { A(id) }
def f2(id:String): B = { B(id) }
def f3(id:String): C = { C(id) }
val result1:A = f1("123") // returns A("123")
val result2:B = f2("345") // returns B("345")
val result3:C = f3("567") // returns C("567")
but I'm hoping there is a way to keep it as one generic method to avoid some ugly boilerplate code duplication, and still be nearl as fast as the tree method version.
If you do not want to use reflection (ClassTag or TypeTag), you could use a Factory type class to achieve the desired functionality (unless it defeats the purpose of your generic function by generating a lot of duplicated simple code ;)).
case class A(s: String)
case class B(s: String)
case class C(s: String)
trait Factory[T] extends ((String) => T) {
def apply(arg: String): T
}
object Factory {
implicit object AFactory extends Factory[A] {
override def apply(arg: String): A = A(arg)
}
implicit object BFactory extends Factory[B] {
override def apply(arg: String): B = B(arg)
}
implicit object CFactory extends Factory[C] {
override def apply(arg: String): C = C(arg)
}
}
def create[T : Factory](arg: String): T = implicitly[Factory[T]].apply(arg)
create[A]("foo") | -> res0: A = A(foo)