1. 程式人生 > >scala中的上下文繫結(context bound)

scala中的上下文繫結(context bound)

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