scala 上下文界定

一. 传统隐式转换实现

先看一个传统的隐式转换方法实现 

/** 上下文界定
  * 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)
}

这里我们有几个改动:

  1. 定义了一个新类: MyFunctionClass[T],其中含有一个类型参数T。这个类是为了封装我们之前定义的(Poet => Boolean)方法。之所以定义这个类,是与上下文界定的语法有关的: 上下文界定语法要求传入有一个类型参数的类的实例。虽然说(Poet => Boolean)也是Function[Poet, Boolean]的实例,但是它没法再有一个类型参数,所以我这里采用了封装的思想,专门定义了MyFunctionClass类,用来传参。
  2. 在object ContextBounds中,我声明了这个类的实现:implicit val ageCondition: MyFunctionClass[Poet],并且定义它为隐式转换。这样,这个ageCondition就可以加载到其他需要此类型的隐式参数中了。
  3.  对于原先传入隐式参数的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))

可以看到与之前的结果相同

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭Albert

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值