4

I've been running into issues refactoring common code out of the 3 methods and into makeRequest() but I get ambiguous implicit matching from the compiler. I am not sure if this is from having defaults for the implicit methods or some other issue but my goal is for getRequest/deleteRequest/postRequest can simply call makeRequest("GET")/makeRequest("DELETE")/makeRequest("POST"). Previously none of the parameters were implicit, I'm just attempting to reach the goal by using implicits

def makeRequest(method: String)(implicit path: String, base: String, params: Seq[(String, String)], body: Option[String], retriesLeft: Int): Future[WSResponse] = ???

def getRequest()(implicit path: String, base: String = baseUrl, params: Seq[(String, String)] = Seq(), body: Option[String] = None, retriesLeft: Int = retries): Future[WSResponse] = makeRequest("GET")

def deleteRequest()(implicit path: String, base: String = baseUrl, params: Seq[(String, String)] = Seq(), body: Option[String] = None, retriesLeft: Int = retries): Future[WSResponse] = makeRequest("GET")

def postRequest[T]()(path: String, body: T, base: String = baseUrl, params: Seq[(String, String)] = Seq(), retriesLeft: Int = retries)
  (implicit wrt: play.api.http.Writeable[T], ct : play.api.http.ContentTypeOf[T]): Future[WSResponse] = makeRequest("POST")

I get this and same with deleteRequest

ambiguous implicit values:
[error]  both value base of type String
[error]  and value path of type String
[error]  match expected type String
[error]     def getRequest()(implicit path: String, base: String = baseUrl, params: Seq[(String, String)] = Seq(), body: Option[String] = None, retriesLeft: Int = retries): Future[WSResponse] = makeRequest("GET")
5
  • 1
    One glaring red flag is that you require an implicit for String - you should as much as possible avoid defining implicits for common types... That said, to debug ambiguous implicits, you need to look at the call sites of the above methods and find all the implicits in scope, so the information you've provided us isn't enough. Commented Aug 10, 2016 at 2:46
  • @Alec, I will clarify this in the post after this comment but previously getRequest/deleteRequest/postRequest had only explicit parameters, I am attempting to refactor the common code from the 3 methods into makeRequest so that I can make each method simply call i.e. makeRequest("GET"). To do this I am attempting to use implicits Commented Aug 10, 2016 at 2:54
  • 3
    Implicits search by type only, not name, so using String will not work as expected. Commented Aug 10, 2016 at 3:13
  • @Reactormonk ah. In this case then, would trying to abstract until getRequest can just call makeRequest("GET") be not possible? Commented Aug 10, 2016 at 3:39
  • Sure, but you'll have to wrap everything into a case class. I still wouldn't do it, as it makes it harder to read the code. Commented Aug 10, 2016 at 3:48

2 Answers 2

11

I think you should revisit using all these implicit unless you are doing some really funky DSL.

Here is one way to solve the problem you are having. As you may have guessed, implicit work on the Type not the name, so having two implicit with the same type is a No-No.

Since Scala 2.10, Scala allows you to "inline" classes using AnyVal (SIP-15). Here is an example of a so called value class:

case class Path(p: String) extends AnyVal

Now instead of using Strings to represent your entities you enclose them in this nice "wrapper" class. The cool thing about this is that the compiler will take care of replacing all Path objects in your code with a String during compilation time. So in terms of compiled code, you get the same performance as if you used a String. You gain a lot of type safety without paying a runtime penalty.

Now coming back to your case, here is how to solve the problem you have:

case class Path(s: String) extends AnyVal
case class BaseUrl(s: String) extends AnyVal

def foo(implicit a: Path, b: BaseUrl) = a.s ++ b.s

implicit val a: Path = Path("a")
implicit val b: BaseUrl = BaseUrl("b")

Here is how to use it:

scala> foo
res0: String = ab
Sign up to request clarification or add additional context in comments.

1 Comment

The tip on wrapping the types was very informative! For now I'll stick with non-implicit values because adding case classes for each param seems like boiler plate code to me. This definitely gave me more insight on how implicits work though thank you
2

As Marios says, the problem is that you're using implicits for types like String and Int. Since implicits work on type and not on name the compiler would not know where to put an 'implicit String'. That's why custom types should be used. One thing I took away from last ScalaDays conference is that you should create types for everything because the compiler can than help you to verify you're program is correct.

However, looking at your solution, I would not use implicits at all. It's better to use a single argument list and provide defaults for all or most values. Then 'makeRequest' could default do a 'Get /' and this behavior can be changed by providing 'path = "/search"' or 'method = "POST"' for example.

Also use some kind of scoped type for http method since you already know what valid values are and which ones you'd like to support.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.