问题
There are businesses and people. Users can either like or post a comment about a business but the same can not happen with a person. When a user posts something about a business or likes it, that business is called the target
of that like or post:
trait TargetingRelation[TargetingType[_],TargetedType]
class Business
class Person
class Post[Target | TargetingRelation[Business,Post] ] {
def target:Target
}
class Like[Target | TargetingRelation[Business,Like] ] {
def target:Target
}
Here I'm inventing a T | P[T]
notation meaning type parameter T
such that it satisfies some property P[T]
(or T :|: P[T]
if it carries more type-appeal). Somewhere else in the code I want to have declarations like:
object canPostAboutBusiness extends TargetingRelation[Post,Business]
object canLikeBusiness extends TargetingRelation[Like,Business]
these objects are evidences actually, something like Haskell type-classes. So this would type check:
val p = new Post[Business]
val l = new Like[Business]
but not this one:
val p = new Post[Person]
val l = new Like[Person]
As far as my knowledge of Scala permits, I can't model this particular state of affairs in a satisfactory way. Now I insist that this is not sub-typing because Business is not a:
class Business extends
TargetingRelation[Post,Business] with
TargetingRelation[Like,Business]
As a matter of fact it is very desirable that Business
remains totally ignorant of the Post
. The relationship is actually outside of both Post
and Business
. Besides, I suppose the above code would not even compile to begin with since Business
is inheriting from TargetingRelation
twice. Insights are most welcome.
related : Using a context bound in a class type parameter
回答1:
You can do this in scala with something that acts like a typeclass using implicits. For example:
import scala.language.higherKinds
trait TargetingRelation[A[_], B]
class Business
class Person
// Using explicitly declared implicit parameter:
class Post[T](implicit ev: TargetingRelation[Post, T])
// Using a "context bound". The syntax is a little hairy and uses
// a type lambda because TargetingRelation takes multiple type params
class Like[T : ({type S[x] = TargetingRelation[Like, x]})#S]
implicit object canPostAboutBusiness extends TargetingRelation[Post,Business]
implicit object canLikeBusiness extends TargetingRelation[Like,Business]
Then you can instantiate the classes with Business
scala> val p = new Post[Business]
p: Post[Business] = Post@374c991a
scala> val l = new Like[Business]
l: Like[Business] = Like@1fd348f8
But not with Person
scala> val p1 = new Post[Person]
<console>:15: error: could not find implicit value for parameter ev: TargetingRelation[Post,Person]
val p1 = new Post[Person]
^
scala> val p2 = new Like[Person]
<console>:15: error: could not find implicit value for evidence parameter of type TargetingRelation[Post,Person]
val p2 = new Like[Person]
^
If you search from "scala typeclasses", you'll find plenty of explanations of the details of how this works, but basically, you require the constructor to take an implicit parameter of the type TargetingRelation[TargetingType[_],TargetedType]
and then place an implicit of that type in scope when constructing your class (Post
or Like
). The implicit serves as "evidence" that the TargetedType
has an instance of the typeclass (and plays the role of the explicit dictionary of methods that get automatically passed around in other languages typeclass implementations).
In fact, scala has some synatic sugar to help with this, called the Context Bound. This causes methods written as:
def a[A: B] = ???
to be translated to
def a[A](implicit ev: B[A]) = ???
In your particular example, the contexts bounds syntax is a little tricky because there are multiple type parameters, but it can be done as this SO question describes.
来源:https://stackoverflow.com/questions/20438322/modeling-a-binary-relationship-between-two-types