一. 传统隐式转换实现
先看一个传统的隐式转换方法实现
/** 上下文界定
* author: leisu
* date 2018/8/15 17:36
*/
object ContextBounds extends App {
implicit def ageCondition(person: Poet) = person.age > 18
val cb = new ContextBounds
val list: List[Poet] = List(new Poet("李白", 35),
new Poet("白居易", 19),
new Poet("苏轼", 34),
new Poet("辛弃疾", 17),
new Poet("杜甫", 21),
new Poet("张良", 18)
)
println(cb.doFilter(list))
}
class ContextBounds {
/**
* 根据传入的data和判断条件con,进行数据过滤,并返回
* 其中T是混入了Condition[T]的子类
*/
def doFilter[T <: Condition[T]](data: List[T])(implicit condition: T => Boolean): List[T] = data.filter(_.judge(condition))
}
/**
* 定义一个Condition特质,包含condition方法,混入此特质的类,可以使用condition进行筛选
*
* @tparam T 协变
*/
trait Condition[+T] {
def judge(condition: T => Boolean): Boolean
}
/**
* 诗人类
*/
case class Poet(val name: String, val age: Int) extends Condition[Poet] {
//复写方法,调用con函数,传入this
override def judge(condition: Poet => Boolean): Boolean = condition(this)
}
可以看到,隐式传入的参数是: ageCondition,这是一个def方法,类型为(Poet => Boolean),是((T<: Condition)=> Boolean)的子类,所以可以隐式传入。在调用时,只显式传入类第一个参数,具体调用为:cb.doFilter(list),就可以成功。
调用后的输出为:
List(Poet(李白,35), Poet(白居易,19), Poet(苏轼,34), Poet(杜甫,21))
起到了过滤的效果
二. 上下文界定:初步改变
我们对上面对例子进行初步改变
/** 上下文界定
* author: leisu
* date 2018/8/15 17:36
*/
object ContextBounds extends App {
implicit def ageCondition(person: Poet) = person.age > 18
val cb = new ContextBounds
val list: List[Poet] = List(new Poet("李白", 35),
new Poet("白居易", 19),
new Poet("苏轼", 34),
new Poet("辛弃疾", 17),
new Poet("杜甫", 21),
new Poet("张良", 18)
)
println(cb.doFilter(list))
}
class ContextBounds {
/**
* 根据传入的data和判断条件con,进行数据过滤,并返回
* 其中T是混入了Condition[T]的子类
*/
def doFilter[T <: Condition[T]](data: List[T])(implicit condition: T => Boolean): List[T] =
data.filter(_.judge(implicitly[T => Boolean])) //XXX 注意这里的改动
}
/**
* 定义一个Condition特质,包含condition方法,混入此特质的类,可以使用condition进行筛选
*
* @tparam T 协变
*/
trait Condition[+T] {
def judge(condition: T => Boolean): Boolean
}
/**
* 诗人类
*/
case class Poet(val name: String, val age: Int) extends Condition[Poet] {
//复写方法,调用con函数,传入this
override def judge(condition: Poet => Boolean): Boolean = condition(this)
}
在这个改动版本中,我们其他地方都没变,只有class ContextBounds类的 doFilter方法进行类变化:
def doFilter[T <: Condition[T]](data: List[T])(implicit condition: T => Boolean): List[T] =
data.filter(_.judge(implicitly[T => Boolean])) //XXX 注意这里的改动
- 可以看到,调用data.filter()时,我们并没有像刚才那样,直接将的condition传入,而是用了implicitly[T => Boolean]
- 这里的效果其实跟我们之前用隐式参数condition传入一样。因为scala标准库定义的implicitly这个方法,内容如下:
def implicitly[T](implicit t: T) = t
- 也就是说,implicitly的作用是获取作用域内已经生效的隐式转换,获取的方式,是通过传入隐式转换的类型来确定(这就是T类型参数的作用)。它会查找隐式转换类型为T的隐式转换定义,并把它的实例t返回。
- 所以我们这里调用的话,系统能获取到已经在作用域内生效的、类型为T => Boolean的隐式定义:condition,并放入方法内执行。也就是说,跟之前直接调用condition一致
- 这里的方法入参名:condition无论起什么都可以,可以叫
def doFilter[T <: Condition[T]](data: List[T])(implicit makeSure: T => Boolean):
或者
def doFilter[T <: Condition[T]](data: List[T])(implicit judge: T => Boolean):
它的作用只是将一个隐式转换传入作用域内,好让下面的implicitly[T => Boolean]能查找到已定义好的隐式转换。
三. 上下文界定:最终语法
上面例子中,我们发现了:方法定义的隐式参数入参名不重要,因为函数实体内不直接调用此参数名,它只是一个声明。这就给了我们再次优化的可能。scala对引入隐式类型有一个新的语法,也就是:上下文界定。例子如下
/**
* 封装原方法的类,可以看到这里有一个类型参数U,并且我们的方法带上类型参数,可用度更加广了
*/
abstract class MyFunctionClass[U] {
val myFunction: (U => Boolean)
}
/** 上下文界定
* author: leisu
* date 2018/8/15 17:36
*/
object ContextBounds extends App {
/*XXX 改动一: 这里封装了下原先的方法,把它放到了MyFunctionClass类之下
* 因为上下文引入必须是带有一个类型参数的类的实例
* 虽然原先的方法时Function1[Poet, Boolean]的实例,但无法再给它加其他类型参数,所以封装*/
implicit val ageCondition: MyFunctionClass[Poet] = new MyFunctionClass[Poet] {
val myFunction: (Poet => Boolean) = _.age > 18
};
val cb = new ContextBounds
val list: List[Poet] = List(new Poet("李白", 35),
new Poet("白居易", 19),
new Poet("苏轼", 34),
new Poet("辛弃疾", 17),
new Poet("杜甫", 21),
new Poet("张良", 18)
)
println("完全上下界实现:" + cb.doFilter(list))
}
class ContextBounds {
/**
* 根据传入的data和判断条件con,进行数据过滤,并返回
* 这里类型参数变为: [T: MyFunctionClass] 有两个意思:
* 1. 引入类一个类型参数T,并且这个类型参数与 MyFunctionClass无直接关联(不同于上界下界,需要是后面的父类或者子类)
* 2. 自动为此方法添加了一个MyFunctionClass[T]的隐式入参,也就是上面object中定义的:implicit val ageCondition
*/
def doFilter[T: MyFunctionClass](data: List[T]): List[T] = data.filter(implicitly[MyFunctionClass[T]].myFunction(_)) //XXX 注意这里的改动
}
/**
* 定义一个Condition特质,包含condition方法,混入此特质的类,可以使用condition进行筛选
*
* @tparam T 协变
*/
trait Condition[T] {
def judge(condition: T => Boolean): Boolean
}
/**
* 诗人类
*/
case class Poet(val name: String, val age: Int) extends Condition[Poet] {
//复写方法,调用con函数,传入this
override def judge(condition: Poet => Boolean): Boolean = condition(this)
}
这里我们有几个改动:
- 定义了一个新类: MyFunctionClass[T],其中含有一个类型参数T。这个类是为了封装我们之前定义的(Poet => Boolean)方法。之所以定义这个类,是与上下文界定的语法有关的: 上下文界定语法要求传入有一个类型参数的类的实例。虽然说(Poet => Boolean)也是Function[Poet, Boolean]的实例,但是它没法再有一个类型参数,所以我这里采用了封装的思想,专门定义了MyFunctionClass类,用来传参。
- 在object ContextBounds中,我声明了这个类的实现:implicit val ageCondition: MyFunctionClass[Poet],并且定义它为隐式转换。这样,这个ageCondition就可以加载到其他需要此类型的隐式参数中了。
- 对于原先传入隐式参数的doFilter方法,我们现在做如下定义:
def doFilter[T: MyFunctionClass](data: List[T]): List[T] = data.filter(implicitly[MyFunctionClass[T]].myFunction(_))
这里可以看到并没有像之前那样,再方法体上直接传入隐式入参(implicit condition ...), 那是如何生效的呢?注意看之前的类型参数那里,这里的定义方法为[T: MyFunctionClass],这一步声明有两个效果:
- 引入了一个类型参数T,并且这个类型参数与 MyFunctionClass无直接父子类关系(不同于上界下界,需要是后面的父类或者子类)
- 自动为doFilter方法添加了一个MyFunctionClass[T]的隐式入参,也就是说,它会找到作用域内有效的、类型为MyFunctionClass[T]的隐式定义(即object ContextBounds中的:implicit val ageCondition),作为参数传入doFilter方法,并在方法体内,通过取此类型implicitly[MyFunctionClass[T]]再得到ageCondition: MyFunctionClass[Poet],调用myFunction真正取到(Poet => Boolean)的方法体,放入List.filter中做入参,依次调用。
这样整个通过上下文界定实现传入隐式参数的功能就全部实现。最后执行的效果为:
完全上下界实现:List(Poet(李白,35), Poet(白居易,19), Poet(苏轼,34), Poet(杜甫,21))
可以看到与之前的结果相同