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.
@wingedsubmariner is right that you can add type Api = ApiLike
in the implementation. But that's too restrictive. I'll explain what else works and how you can figure it out.
Api <: ApiLike
— that is, Api >: Nothing <: ApiLike
, Api
could well be Nothing
. So, the typechecker will accept expression e
when you need to return Api
only if e
has type Nothing
— that is, if e
fails. In other words, you cannot return a value of type Api
. Api
, you need to give a more specific lower bound. Saying that Api = ApiLike
is one way, but you can just fix Api
to be a more specific type, still inheriting from ApiLike
: This allows Api
to have more methods (hidden from clients) in implementations like ApiComponentImpl
. See Sol.1 / 2 / 3 below. ApiComponent
, all implementations of RestApi
and RestApiLike
need to descend from Api
. This does not show up in the code you have, but
RestApi <: Api
— you can give two upper bounds by using their intersection, created with with
. RestApiLike
(or *Like) to also inherit from Api
. Since Api
is an abstract type, you cannot say that a trait extends Api
. Instead, you can use a self-type annotation to specify that: this adds a constraint for implementors. For example:
trait ApiComponent {
type Api <: ApiLike // with Api //EDIT, Api <: Api makes no sense.
trait ApiLike
type RestApi <: RestApiLike with Api
trait RestApiLike extends ApiLike {
this: Api =>
/* omitted for brevity */
}
type SoapApi <: SoapApiLike
trait SoapApiLike extends ApiLike {
this: Api =>
/* omitted for brevity */
}
type WebsocketApi <: WebsocketApiLike
trait WebsocketApiLike extends ApiLike {
this: Api =>
/* omitted for brevity */
}
def apiForName: PartialFunction[String, Api]
}
trait ApiComponentImpl extends ApiComponent {
//Sol. 1:
//type Api = ApiLike
//Sol. 2:
//trait Api extends ApiLike {
//decls
//}
//Sol. 3, equivalent to 2:
trait Api2 extends ApiLike {
//decls
}
type Api = Api2
class TwitterApi extends RestApiLike with Api {
/* omitted for brevity */
}
class SalesforceApi extends SoapApiLike with Api {
/* 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 */
}
}
EDIT:
For the code you are showing, do you need the full power of the cake pattern? Not really. In this answer, I provide a simplification of this design, and I discuss for what you might need the code you show. I imagine your actual code might require some intermediate form.
How do we simplify this code:
Api
, and the other classes look like simple implementations of this API, with more methods which are not accessible to the client. In this case, you don't need all those interfaces in ApiComponent
. type Api <: ApiLike; trait ApiLike {...}
type Api <: ApiLike; trait ApiLike {...}
instead of having simply trait Api {...}
, you allow ApiComponentImpl
to refine Api
to a more specific interface, so that clients of Api
within ApiComponentImpl
can use the more specific interface. Do you actually need that? ApiComponent
. As long as this is not not going to change you could simplify this code further by conflating ApiComponent
and ApiComponentImpl
, but I guess you do want to keep this separation because you want to hide part of ApiComponentImpl
and allow other implementations. So, here's an (over)simplified version of your code:
trait ApiComponent { trait Api { ... } def apiForName: PartialFunction[String, Api] } trait ApiComponentImpl { //Many implementations of Api //... def apiForName: PartialFunction[String, Api] = //pick which. }
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.