Scalaz(1)- 基础篇:隐式转换解析策略-Implicit resolution

在正式进入scalaz讨论前我们需要理顺一些基础的scalaz结构组成概念和技巧。scalaz是由即兴多态(ad-hoc polymorphism)类型(typeclass)组成。scalaz typeclass在scala中的应用有赖于scala compiler的一项特别功能:隐式转换(implicit conversion),使程序表述更精简。由于隐式转换是一项compiler功能,在程序编译(compile)的时候是由compiler来进行类型转换代码的产生和替代的。

  让我们先了解一下作用域(scope)和绑定(binding)。这两样都是在编译程序时compiler需要解决的问题。所谓作用域解析(scope resolution)就是要确定一个绑定在一个作用域里是可视的,否则程序无法通过编译。

作用域就是一个绑定在一个程序范围内的可视型。作用域可以是某个类的内部或者是某个方法或函数的内部,基本上用{}就可以创造一个新的作用域了。在scala作用域可以是多层的,一个域可以存在于另一个作用域内。外部域的绑定在内部域内是可视的,反之则不然:

1 class Foo(x: Int) {
2   def temp = {
3     val y = x + 1  //x是本地域外的一个绑定
4   }  
5 }

在以上的例子里x在temp{}内是可视的。一个作用域内的绑定可以屏蔽(shadow)外域定义的绑定:

1 class Foo(x: Int) {
2   def temp = {
3     val x = 0      //本地域绑定。屏蔽了外域的x
4     val y = x + 1  //y=1,x是本地域的一个绑定
5   }  
6 }

绑定屏蔽是分优先次序如下:

1、本地声明、定义或者透过继承又或者在同一源代码文件内的package定义的绑定最优先

2、明式申明的import如:import obj.Foo 所定义的绑定次优先

3、通配符式的import如:import obj._ 所定义的绑定再次之

4、同一package但处于不同源代码文件内的绑定最次优先

我们用个例子来示范scope binding的优先顺序:

 1 package test;
 2 
 3 // This object contains the bindings/scope tests
 4 object Test {
 5 
 6   def main(arg : Array[String]) : Unit = {
 7     testSamePackage()
 8     testWildcardImport()
 9     testExplicitImport()
10     testInlineDefinition()
11   }
12 
13   // This looks for a binding 'x' within the same package (test) as this scope.
14   def testSamePackage() {
15      println(x)  // 在另外文件的test package. prints: Externally bound x object in package test
16   }
17 
18   // This defines a new scope with an 'x' binding that we can import with a wildcard.
19   object Wildcard {
20     def x = "Wildcard Import x"
21   }
22 
23   // This function will print the value in the binding 'x' after importing from the Wildcard object
24   // using a wildcard import.
25   def testWildcardImport() {
26     import Wildcard._
27     println(x)  // prints: Wildcard Import x
28   }
29 
30   // This defines another binding of 'x' that we can import explicitly.
31   object Explicit {
32     def x = "Explicit Import x"
33   }
34 
35   def testExplicitImport() {
36     import Explicit.x  
37     import Wildcard._
38     println(x)  // .x优先于._  prints: Explicit Import x
39   }
40 
41   // This defines an inline binding for x.  Note that with all the imports, there are no ambiguous naming conflicts.
42   def testInlineDefinition() {
43     val x = "Inline definition x" //即使写在最前,本地binding x还是最优先
44     import Explicit.x
45     import Wildcard._
46     println(x)  // prints:  Inline definition x
47   }
48 }

scala compiler 在编译程序时会根据情况自动进行隐式转换,即代码替代。在两种情况下scala会进行隐形转换:

1、在期待一个类型的地方发现了另外一个类型:

 1 package learn.scalaz
 2 object ab {
 3  class A
 4  class B
 5  implicit def bToA(x: B): A = new A
 6 }
 7 object testApp extends App {
 8   import ab._
 9   val a: A = new B  //需要进行B => A的隐式转换
10 }

在这里由于A类和B类没有任何继承关系,应该无法通过编译,但scala compiler会首先尝试搜寻B=>A的隐式转换实例,当找到bToA函数时compiler会把new B替代成bToA(new B),如此这般才能通过编译。

2、当一个类型并不支持某个方法时:

 1 package learn.scalaz
 2 object ab {
 3  class A {
 4    def printA = println("I am A")   
 5  }
 6  class B
 7  implicit def bToA(x: B): A = new A
 8 }
 9 object testApp extends App {
10   import ab._
11   (new B).printA   //需要进行B => A的隐式转换
12 }

scala compiler 在隐式转换中的隐式解析(implicit resolution)会用以下的策略来查找标示为implicit的实例:

1、能用作用域解析的不带前缀的隐式绑定即:如Bar,而Foo.Bar则不符合要求

这个在以上的例子里已经示范证明了。

2、如果以上方式无法解析隐式转换的话compiler会搜寻目标类型的隐式作用域(implicit scope)内任何对象中的隐式转换。一个类型的隐式作用域(implicit scope)包括了涉及这个类型的所有伴生模块(companion module)内定义的隐式转换。例如:

def foo(implicit p: Foo),这个方法的参数必须是Foo类型。如果compiler无法进行作用域解析的话就必须搜寻隐式作用域内的匹配隐式转换。比如Foo的伴生对象(companion object),如下:

 1 object demo {
 2  object Container {
 3    trait Foo
 4    object Foo {
 5          implicit def x = new Foo {
 6             override def toString = "implicit x"
 7         }
 8    }
 9  }
10  import Container._
11  def foo(implicit p: Foo) = println(p)            //> foo: (implicit p: scalaz.learn.ex1.Container.Foo)Unit
12  foo                                              //> implicit x

compiler在object Foo内找到了匹配的隐式转换,程序通过了编译。

由于compiler会首先进行作用域解析,失败后才搜寻隐式转换作用域,所以我们可以把一些默认隐式转换放到隐式作用域里。然后其它编程人员可以通过import来覆载(override)使用他们自己的隐式转换。

综合以上所述:一个类型T的隐式作用域就是组成这个类型的所有类的伴生对象(companion object)。也就是说,T的形成有可能涉及到一组类型。在进行隐式转换解析过程中,compiler会搜寻这些类型的伴生对象。类型T的组成部分如下:

1、所有类型T的父类:

 1 object demo {
 2  object Container {
 3    trait A
 4    trait B
 5    class T extends A with B
 6    object A {
 7     implicit def x = new T {
 8             override def toString = "implicit x"
 9         }
10    }
11  }
12  import Container._
13  def foo(implicit p: T) = println(p)            //> foo: (implicit p: scalaz.learn.demo.Container.Foo)Unit
14  foo                                              //> implicit x

类型T由A,B组成。compiler从A的伴生对象中解析到隐式转换。

2、如果T是参数化类型,那么所有类型参数的组成类型及包嵌类的组成类型的伴生对象都在隐式转换解析域中。如在解析List[String]中,所有List和String的伴生对象都在解析域中:

 1 object demo {
 2  object Container {
 3    trait A
 4    trait B
 5    class T[A]
 6    object A {
 7     implicit def x = new T[A] {
 8             override def toString = "implicit x"
 9         }
10    }
11  }
12  import Container._
13  def foo(implicit p: T[A]) = println(p)           //> foo: (implicit p: scalaz.learn.demo.Container.T[scalaz.learn.demo.Container.
14                                                   //| A])Unit
15  foo                                              //> implicit x

A是T[A]的类型参数。compiler从A的伴生对象中解析到隐式转换。

3、如果T是个单例对象(singleton object),那么T的包嵌对象(container object)就是解析域:

 1 object demo {
 2  object Container {
 3    object T {
 4      def x = "singleton object T"
 5    }
 6    implicit def x =  T 
 7  }
 8  import Container._
 9  def foo(implicit p: T.type) = println(p.x)       //> foo: (implicit p: scalaz.learn.demo.Container.T.type)Unit
10  foo                                              //> singleton object T

单例对象T定义于包嵌对象Container内。compiler从Container中解析到隐式转换。

这是一篇隐式转换解析原理的讨论,不会对scala有关隐式转换语法和调用做任何解说,希望读者体谅。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小樱的经验随笔

洛谷 P1598 垂直柱状图【字符串+模拟】

P1598 垂直柱状图 题目描述 写一个程序从输入文件中去读取四行大写字母(全都是大写的,每行不超过72个字符),然后用柱状图输出每个字符在输入文件中出现的次数...

2815
来自专栏一个会写诗的程序员的博客

React极简教程: Hello,World!React简史React安装Hello,World

A programming paradigm is a fundamental style of computer programming. There are...

731
来自专栏sunseekers

初探 TypeScript函数基本类型泛型接口类内置对象

前段时间有朋友和我推荐 TypeScript ,他说写起来特别爽,让我去试一试,那时候我还在那是啥高深莫测的东西。刚好那段时间忙,一直没有时间看。最近也很忙,还...

662
来自专栏闰土大叔

闰土说JS进阶之变量

前言 前端世界如此喧嚣,能进阶的何其稀少。大家好,你们的闰土哥在沉寂了数月之后又回来了!(此处应有掌声~~~) 前段时间在群里关于“闰土去哪儿了”的话题,让我既...

32610
来自专栏web前端教室

重学javascript 红皮高程(3)

继续啊,继续JS基础知识补全之路。 昨天说到JS的几种数据类型,像我这种脑子不太好使,记不清JS共有几种对象的人,可以这么记,JS这东西根本不支持自定义类型,所...

1949
来自专栏数据和云

轻轻揭开 b*tree 索引结构的神秘面纱

李翔宇 云和恩墨西区技术专家 大家好,我是云和恩墨的技术专家李翔宇,今天要为大家分享的主题是《轻轻揭开b*tree索引结构的神秘面纱》。 说到索引,大家应该...

2756
来自专栏用户画像

JAVA反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对...

551
来自专栏逸鹏说道

Python3 与 C# 基础语法对比(新排版)

VSCode设置python3的开发环境(linux下默认是python2)https://www.cnblogs.com/dotnetcrazy/p/9095...

842
来自专栏.NET技术

C#系列之String和StringBuilder

      首先和博园的各位打声招呼,小弟在博园呆了也有一年多了。平常一有时间就会过来看看文章,学习各位的经验,现在养成了一种一天不来博园,心里就不踏实的习惯,...

804
来自专栏JavaQ

烂代码吐槽汇 | 奇葩命名

代码首先是给人看的,其次才是给机器看的。 烂代码特征:可读性差、逻辑混乱、性能低下。 1.奇葩项目(模块)名 项目(模块)名称使用汉语拼音、英汉双拼、超长的字母...

3225

扫码关注云+社区