I'm writing a simple Scala application using the Cake Pattern, but I'm running into trouble with a particular use case. Usually, I define a component with some existential type (MyType) that is retricted by a trait (MyTypeLike). This allows to "inject" a particular implementation that explicits that type (class MyType extends MyTypeLike). However, in some cases, I would need to define an existential type with several subtypes like in the example below. This is when I run into some trouble.
trait ApiComponent {
type Api <: ApiLike
trait ApiLike
type RestApi <: RestApiLike
trait RestApiLike extends ApiLike {
/* omitted for brevity */
}
type SoapApi <: SoapApiLike
trait SoapApiLike extends ApiLike {
/* omitted for brevity */
}
type WebsocketApi <: WebsocketApiLike
trait WebsocketApiLike extends ApiLike {
/* omitted for brevity */
}
def apiForName: PartialFunction[String, Api]
}
trait ApiComponentImpl extends ApiComponent {
class TwitterApi extends RestApiLike {
/* omitted for brevity */
}
class SalesforceApi extends SoapApi {
/* omitted for brevity */
}
override def apiForName: PartialFunction[String, Api] = {
case "twitter" => new TwitterApi // Compiler error: TwitterApi is not a subtype of Api
case "salesforce" => new SalesforceApi // Compiler error: SalesforceApi is not a subtype of Api
}
}
trait SomeOtherComponent {
self: ApiComponent =>
def doSomethingWithTwitter = apiForName lift "twitter" map { twitterApi =>
/* Do something with Twitter API */
}
}
It's easy to see why that doesn't work. RestApi is not a subtype of Api, but RestApiLike is a subtype of ApiLike, so TwitterApi is only a subtype of RestApiLike and ApiLike. Oops! Of course, a simple solution would be to change everything to *Like. However, that kind of shoots the whole idea of existential types in the head. Also, by varying where there is a *Like where there's not, I can arbitrarily make the compiler complain at different places, but I can't make the compiler happy :-(
How can fix this? I'm happy to take a quick fix, but I would also like to get some more detailed design feedback and other suggestions.
type Api <: ApiLike
etc. introduces an abstract type. Existential types are something different, which your code isn't using (which is a good thing). As for a quick fix, puttingtype Api = ApiLike
intoApiComponentImpl
would make it compile. You could also makeapiForName
return anApiLike
. I don't really understand why you have separate * and *Like traits, so I can't recommend something better. – wingedsubmariner