Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scala中的context bound #11

Open
cjuexuan opened this issue Feb 26, 2016 · 0 comments
Open

scala中的context bound #11

cjuexuan opened this issue Feb 26, 2016 · 0 comments
Labels

Comments

@cjuexuan
Copy link
Member

context bound

implicitly

implicitly 主要是在当前作用域查找指定类型:

def implicitly[T](implicit e : T) : T 

例子:

implicit val x = 1
val y = implicitly[Int]
val z = implicitly[Double]

result:

scala> implicit val x = 1
x: Int = 1

scala> val y = implicitly[Int]
y: Int = 1

scala> val z = implicitly[Double]
<console>:11: error: could not find implicit value for parameter e: Double
       val z = implicitly[Double]

为啥会报错呢,是因为当前作用域不能查找到Double的隐式值,如果要让他通过编译也很简单,定义一个Double的隐式值就可以了

implicit val x = 1
val y = implicitly[Int]//1
implicit val x = 0.0
val z = implicitly[Double]//0.0

那如果多个隐式值呢:
比如:

implicit val x = 1
implicit val x = 2
val y = implicitly[Int]//2

这就涉及到一个隐式解析规则问题了:
scala的语言规范定义了以下两种查找标记为implicit的实体规则

  • 隐式实体在查找发生的地点可见,并且没有任何前缀,比如不能是foo.x,只能是x
  • 如果按照浅一条规则没有找到隐式实体,那么就会在隐式参数的类型的隐式作用域包含的所有隐式实体中查找

而作用域和绑定主要是以下几条:
同作用域内的高优先级绑定遮挡低优先级的绑定,另外,高优先级或者同优先级的遮挡外部作用域的绑定
如:

class A(val a: Int) {
  def myInt = {
    val a = 2
    a
  }
}
val test = new A(1)
test.myInt // 2
test.a // 1
  1. 本地的,继承的或者通过定义所在源代码文件中的package语句所引入的定义和声明具有最高优先级
  2. 显式的导入具有次高优先级
  3. 通配导入具有更次一级的优先级
  4. 由非定义所在的源文件的package语句所引入的定义优先级最低

以上内容基本来自scala in depth一书

所以这里就解释了关于隐式查找隐式实体的问题

context bound

首先先介绍两个概念,一个是monoid,这个表示有着满足结合律的封闭二元运算和一个单位元,这是一种代数数据结构
另一个是context bound以及简写:

def foo[A](x:A)(implicit x:B[A])

可以重写为

def foo[A : B](x:A) = x 

那么接下来让我们定义出IntMonoid来表示整数,以及求和运算,并且进行求和:

object IntMonoid {
  def mappend(a: Int, b: Int) = a + b

  def mzero = 0
}

def sum(xs: List[Int]) = xs.foldLeft(IntMonoid.mzero)(IntMonoid.mappend)

sum(List(1, 2, 3))

因为我们还可能进行String的运算,所以我们抽象了Monoid:

trait Monoid[A] {
  def mappend(a: A, b: A): A

  def mzero: A
}

object IntMonoid extends Monoid[Int] {
  override def mappend(a: Int, b: Int): Int = a + b

  override def mzero: Int = 0
}

object StringMonoid extends Monoid[String] {
  override def mappend(a: String, b: String): String = a + b

  override def mzero: String = ""
}


def sumInt(xs: List[Int]) = xs.foldLeft(IntMonoid.mzero)(IntMonoid.mappend)
def sumString(xs: List[String]) = xs.foldLeft(StringMonoid.mzero)(StringMonoid.mappend)

sumInt(List(1, 2, 3))
sumString(List("a","b","c"))

再次抽象sum

def sum[A](xs: List[A], m: Monoid[A]) = xs.foldLeft(m.mzero)(m.mappend)
sum(List(1, 2, 3),IntMonoid)
sum(List("a","b","c"),StringMonoid)

这样每次都写一个Monoid显得过于累赘:

def sum[A](xs: List[A])(implicit m: Monoid[A]) = xs.foldLeft(m.mzero)(m.mappend)
sum(List(1,2,3))//error,缺少隐式参数

我们再定义一个隐式参数:

implicit val intMonoid = IntMonoid
sum(List(1,2,3))//6

这样就可以,还记得我们刚才说的上下文绑定么,sum是不是可以改写成上下文绑定的形式呢,当然可以:

def sum[A:Monoid](xs:List[A]) = {
  val m = implicitly[Monoid[A]]//这里从上下文中提取了哪个隐式值,也就是原来的implicit m:Monoid[A]
  xs.foldLeft(m.mzero)(m.mappend)
}

最后我们组织下代码,我们可以将这种代数作为隐式值结构定义在伴生对象中,还记得我们说过的,当当前隐式实体的发生地点没找到,就会调用第二个规则,也就是在隐式参数的类型的隐式作用域所包含的全部隐式实体中查找,那么此时该类型的隐式作用域是指与该类型相关的全部伴生模块,所以可以去伴生对象中找:

trait Monoid[A] {
  def mappend(a: A, b: A): A

  def mzero: A
}
object Monoid {
  implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
    override def mappend(a: Int, b: Int): Int = a + b
    override def mzero: Int = 0
  }
  implicit val stringMonoid: Monoid[String] = new Monoid[String] {
    override def mappend(a: String, b: String): String = a + b
    override def mzero: String = ""
  }
}
def sum[A: Monoid](xs: List[A]) = {
  val m = implicitly[Monoid[A]]
  xs.foldLeft(m.mzero)(m.mappend)
}

sum(List(1, 2, 3))
sum(List("a","b"))

此时都是可以运行的,那么下面看点黑魔法:

object MyNewIntMonoid extends Monoid[Int]{
  override def mappend(a: Int, b: Int): Int = a * b

  override def mzero: Int = 1
}
def product[A: Monoid](xs: List[A]) = {
  val m = implicitly[Monoid[A]]
  xs.foldLeft(m.mzero)(m.mappend)
}
product(List(1,2,3,4))//10
product(List(1,2,3,4))(MyNewIntMonoid)//24

为啥product后面还能加参数,这是怎么了,还记得我们说的sum是什么的简写么,是带隐式参数的那种写法的简写:

def testAdd(x:Int)(implicit y:Int) = x + y
implicit val t = 2
testAdd(5)//7
testAdd(5)(8)//13

和下面这种写法类似:

def testAdd(x:Int)(y:Int) = x + y
def testAdd2 = testAdd(2)(_)
testAdd2(2) // 4 

再看一个context bound的例子

trait Show[T] { def show(t: T): String }
object Show {
  implicit def IntShow: Show[Int] = new Show[Int] { def show(i: Int) = i.toString }
  implicit def StringShow: Show[String] = new Show[String] { def show(s: String) = s }

  def ShoutyStringShow: Show[String] = new Show[String] { def show(s: String) = s.toUpperCase }
}

case class Person(name: String, age: Int)
object Person {
  implicit def PersonShow(implicit si: Show[Int], ss: Show[String]): Show[Person] = new Show[Person] {
    def show(p: Person) = "Person(name=" + ss.show(p.name) + ", age=" + si.show(p.age) + ")"
  }
}

val p = Person("bob", 25)
implicitly[Show[Person]].show(p) //从隐式中提取的StringShow
Person.PersonShow(implicitly,Show.ShoutyStringShow).show(p)//指明用ShouttyStringShow

上述demo出自learn scalaz,我稍微做了一些改变和讲解,另一个出自stackoverflow

@cjuexuan cjuexuan added the scala label Feb 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant