建模两种类型之间的二元关系

时间:2013-12-07 06:13:06

标签: scala generics typeclass parametric-polymorphism type-constructor

有企业和人。用户可以喜欢或发布关于某个商家的评论,但同一个人不能。当用户发布有关某个商家或喜欢该商家的内容时,该商家称为target,或者发布:{/ p>

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
}

这里我发明了一个T | P[T]符号,意思是类型参数T,以便它满足某些属性P[T](如果它带有更多类型吸引力,则为T :|: P[T])。代码中的其他地方我想要声明如下:

object canPostAboutBusiness extends TargetingRelation[Post,Business] 
object canLikeBusiness      extends TargetingRelation[Like,Business]

这些对象实际上是证据,类似于Haskell类型类。所以这会输入检查:

val p = new Post[Business]
val l = new Like[Business]

这一个:

val p = new Post[Person]
val l = new Like[Person]

就我对Scala的认识而言,我无法以令人满意的方式模拟这种特殊情况。现在我坚持认为这是子类型,因为商家 a:

class Business extends 
  TargetingRelation[Post,Business] with 
  TargetingRelation[Like,Business]

事实上,Business仍然完全不了解Post,这是非常可取的。这种关系实际上在PostBusiness之外。此外,我认为上面的代码甚至不会编译开始,因为BusinessTargetingRelation继承两次。很受欢迎。

相关:Using a context bound in a class type parameter

1 个答案:

答案 0 :(得分:6)

你可以在scala中使用类似于使用implicits的类型类的东西来执行此操作。例如:

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]

然后,您可以使用Business

实例化类
scala> val p = new Post[Business]
p: Post[Business] = Post@374c991a

scala> val l = new Like[Business]
l: Like[Business] = Like@1fd348f8

但不是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]
                ^

如果你从“scala类型类”中搜索,你会发现很多explanations of the details如何工作,但基本上,你需要构造函数采用类型TargetingRelation[TargetingType[_],TargetedType]的隐式参数然后在构建类(PostLike)时,将该类型的隐式放在范围内。隐式作为“证据”表明TargetedType具有类型类的实例(并且扮演在其他语言类型类实现中自动传递的显式方法字典的角色)。

事实上,scala有一些同步糖来帮助解决这个问题,称为Context Bound。这会导致写为:

的方法
def a[A: B] = ???

将被翻译为

def a[A](implicit ev: B[A]) = ???

在您的特定示例中,上下文边界语法有点棘手,因为有多个类型参数,但它可以在this SO question描述时完成。