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的优先顺序:

package test;

// This object contains the bindings/scope tests
object Test {

  def main(arg : Array[String]) : Unit = {
    testSamePackage()
    testWildcardImport()
    testExplicitImport()
    testInlineDefinition()
  }

  // This looks for a binding 'x' within the same package (test) as this scope.
  def testSamePackage() {
     println(x)  // 在另外文件的test package. prints: Externally bound x object in package test
  }

  // This defines a new scope with an 'x' binding that we can import with a wildcard.
  object Wildcard {
    def x = "Wildcard Import x"
  }

  // This function will print the value in the binding 'x' after importing from the Wildcard object
  // using a wildcard import.
  def testWildcardImport() {
    import Wildcard._
    println(x)  // prints: Wildcard Import x
  }

  // This defines another binding of 'x' that we can import explicitly.
  object Explicit {
    def x = "Explicit Import x"
  }

  def testExplicitImport() {
    import Explicit.x
    import Wildcard._
    println(x)  // .x优先于._  prints: Explicit Import x
  }

  // This defines an inline binding for x.  Note that with all the imports, there are no ambiguous naming conflicts.
  def testInlineDefinition() {
    val x = "Inline definition x" //即使写在最前,本地binding x还是最优先
    import Explicit.x
    import Wildcard._
    println(x)  // prints:  Inline definition x
  }
}

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

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

package learn.scalaz
object ab {
 class A
 class B
 implicit def bToA(x: B): A = new A
}
object testApp extends App {
  import ab._
  val a: A = new B  //需要进行B => A的隐式转换
}

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

 

 

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

package learn.scalaz
object ab {
 class A {
   def printA = println("I am A")
 }
 class B
 implicit def bToA(x: B): A = new A
}
object testApp extends App {
  import ab._
  (new B).printA   //需要进行B => A的隐式转换
}

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),如下:

object demo {
 object Container {
   trait Foo
   object Foo {
         implicit def x = new Foo {
            override def toString = "implicit x"
        }
   }
 }
 import Container._
 def foo(implicit p: Foo) = println(p)            //> foo: (implicit p: scalaz.learn.ex1.Container.Foo)Unit
 foo                                              //> implicit x

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

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

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

1、所有类型T的父类:

object demo {
 object Container {
   trait A
   trait B
   class T extends A with B
   object A {
    implicit def x = new T {
            override def toString = "implicit x"
        }
   }
 }
 import Container._
 def foo(implicit p: T) = println(p)            //> foo: (implicit p: scalaz.learn.demo.Container.Foo)Unit
 foo                                              //> implicit x

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

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

object demo {
 object Container {
   trait A
   trait B
   class T[A]
   object A {
    implicit def x = new T[A] {
            override def toString = "implicit x"
        }
   }
 }
 import Container._
 def foo(implicit p: T[A]) = println(p)           //> foo: (implicit p: scalaz.learn.demo.Container.T[scalaz.learn.demo.Container.
                                                  //| A])Unit
 foo                                              //> implicit x

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

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

object demo {
 object Container {
   object T {
     def x = "singleton object T"
   }
   implicit def x =  T
 }
 import Container._
 def foo(implicit p: T.type) = println(p.x)       //> foo: (implicit p: scalaz.learn.demo.Container.T.type)Unit
 foo                                              //> singleton object T

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

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

时间: 2024-07-30 14:48:07

Scalaz(1)- 基础篇:隐式转换解析策略-Implicit resolution的相关文章

Java基础之隐式转换vs强制转换_java

Java中,经常可以遇到类型转换的场景,从变量的定义到复制.数值变量的计算到方法的参数传递.基类与派生类间的造型等,随处可见类型转换的身影.Java中的类型转换在Java编码中具有重要的作用. 在定义变量时,有许多要注意的问题,一不小心就会出现损失精度或者不兼容类型等问题. 例如:     1.定义长整型数据时,必须加后缀l或L               long l =123456789012345L     2.定义单精度类型时(7-8位有效数字),必须加后缀 f 或 F         

Scala入门到精通——第十八节 隐式转换与隐式参数(一)

本节主要内容 隐式转换简介 隐式转换函数 隐式转换规则 隐式参数 1. 隐式转换简介 在scala语言当中,隐式转换是一项强大的程序语言功能,它不仅能够简化程序设计,也能够使程序具有很强的灵活性.要想更进一步地掌握scala语言,了解其隐式转换的作用与原理是很有必要的,否则很难得以应手地处理日常开发中的问题. 在scala语言中,隐式转换是无处不在的,只不过scala语言为我们隐藏了相应的细节,例如scala中的类继承层次结构中: 它们存在固有的隐式转换,不需要人工进行干预,例如Float在必要

RDS SQL Server - 专题分享 - 巧用执行计划缓存之数据类型隐式转换

摘要 SQL Server数据库基表数据类型隐式转换,会导致Index Scan或者Clustered Index Scan的问题,这篇文章分享如何巧用执行计划缓存来发现数据类型隐式转换的查询语句,从而可以有针对性的优化查询,解决高CPU使用率的问题. 问题引入 测试环境 为了更好的展示从执行计划缓存缓存中找出导致数据类型转化的查询语句,我们先建立测试环境. -- Create testing database IF DB_ID('TestDb') IS NULL CREATE DATABASE

struct-c# 在一个实现了隐式转换的结构体进行强制类型转换时报错

问题描述 c# 在一个实现了隐式转换的结构体进行强制类型转换时报错 50C 如题,我有一个结构体,类似这样: public struct AInt{ private int _a; public int ToInt() { reurn _a; } public static implicit operator int(AInt value) { return value.ToInt(); } public static implicit operator AInt(int value) { _a

SQL Server中提前找到隐式转换提升性能的办法

原文:SQL Server中提前找到隐式转换提升性能的办法     http://www.cnblogs.com/shanksgao/p/4254942.html 高兄这篇文章很好的谈论了由于数据隐式转换造成执行计划不准确,从而造成了死锁.那如果在事情出现之前发现了这类潜在的风险岂不是更好?     那么我们来看一个简单的例子,如代码清单1所示.   1: SELECT * 2: FROM HumanResources.Employee 3: WHERE NationalIDNumber = 2

ORACLE绑定变量隐式转换导致性能问题

   年后一次系统升级后,监控数据库的工具DPA发现数据库的Total Wait时间突然飙增,如下截图所示,数据库的总体等待时间对比升级前飙增了非常多 另 外就是发现出现了较多的等待事件,主要有latch: cache buffers chains. latch: shared pool .db file scattered read.根据这边的监控发现TOP SQL里面从升级前的0次变为了一天的一万多次(有些甚至更多),分析过后我们就找开发人员了解一下系统升级变跟的内容和改动 开 发人员坚定的

第3课 隐式转换

1)val array=Array(1,2,3,4,5); array.map(item => 2*item);map的作用是循环遍历array中的每一个元素,并把每个元素做为具体的值传给map中的做为参数的函数.map执行结果为Array[Int] =Array(2,4,6,8,10);函数的参数也是函数,item时匿名函数,这个也是高阶函数 2)高阶函数的返回值也可能是函数.高阶函数的参数是函数,这是高阶函数的2个层面.高阶函数可以自动推断出函数参数的类型.而且对只有一个参数的函数,可以省略

JavaScript的隐式转换

JavaScript的数据类型分为六种,分别为null,undefined,boolean,string,number,object.object是引用类型,其它的五种是基本类型或者是原始类型.我们可以用typeof方法打印来某个是属于哪个类型的.不同类型的变量比较要先转类型,叫做类型转换,类型转换也叫隐式转换.隐式转换通常发生在运算符加减乘除,等于,还有小于,大于等.. typeof '11'  //string       typeof(11) //number '11' < 4     /

Oracle隐式转换会影响物化视图查询重写

今天有人问我一个物化视图查询重写的问题,最后发现问题其实和物化视图的功能没有多大的关系,而是隐式转换导致的问题. 还是通过例子来说明这个问题: SQL> create table t ( 2  id number, 3  time date, 4  other varchar2(4000)) 5  partition by range (time) 6  (partition p1 values less than (to_date('2008-1-1', 'yyyy-mm-dd')), 7