scala隐式转换及其DSL实现(一)

作者: 安静1337 | 来源:发表于2016-04-21 15:14 被阅读707次

    隐式操作规则

    隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义。比如,x + y不能通过类型检查,此时,如果有某个可用的隐式转换convert,那么编译器会把它改成convert(x) + y。

    隐式转换通用规则:

    • 标记规则:只有标记为implicit的定义才是可用的,它可以标记任何 变量 函数 或者 对象定义,比如隐式函数定义:
    implicit def intToString (i: String) = i.toString
    
    • 作用域规则:插入的隐式转换必须以单一标识符的形式处于作用域中,或与转换的源或目标类型关联在一起,scala编译器仅考虑处于作用域之内的隐式转换。
    • 无歧义规则:隐式转换唯有不存在其他可插入转换的前提下才能插入,比如不能存在既可使用convert1(x) + y,又可使用convert2(x) + y的情况。
    • 单一调用规则:只会尝试一个隐式操作。编译器不会把 x + y重写成convert1(convert2(x)) + y。不会尝试在某个隐式操作期间再添加隐式转换,然而,可以通过让隐式操作带隐式参数绕过这个限制。
    • 显示操作先行规则:若编写的代码类型检查无误,则不会尝试任何隐式操作。

    命名隐式转换:隐式转换可以任意命名,命名仅需要考虑两种情况,你是否在方法应用中明确写明,以及决定哪个隐式转换在程序的任何地方都有效。
    比如第二点,对象带有两个隐式转换:

    object Convert {
        implicit def stringWrapper(s: String): RandomAccessSeq[Char] = ...
        implicit def intToString(i: Int): String = ...
    }
    

    你可以使用像这样使用,从而有选择性的只引用一个:

    import Convert.intToString
    ...
    

    scala中能用到隐式操作的有三个地方:转换为期望类型指定(方法)调用者的转换隐式参数

    隐式转换为期望类型

    很简单,当编译器看见X时发生了错误,需要Y时,就会坚持从X到Y的隐式转换函数。
    比如要修正 val i: Int = 3.5的错误,可以定义这样的隐式转换消除障碍:

    implicit def doubleToInt (d: Double) = d.toInt
    

    转换(方法调用的)接受者

    隐式转换还应用于方法调用的接收者,也就是方法调用的对象。这种隐式转换主要有两种用途,一:接收者转换使得新的类可以更为平滑地集成到现存类层级中。二:支持编写域特定语言(DSL)。
    1 与新类型的交互操作

    class Number (n: Int) {
        def + (that: Number): Number = ...
        def + (that: Int): Number = ...
    }
    
    > val num = new Number(1)
    /*
    显然,num + 1表达式正确
    但是1 + num就会抛出错误,
    为了允许这种混合的运算需要定义Int到Number的隐式转换
    */
    implicit def intToNumber (i: Int) = new Number(i)
    
    > 1 + num //OK
    /*
    其实就是编译器检查失败后,搜索到了从Int到Number的类型转换,并应用了方法,等价于
    intToNumber(1) + num
    */
    

    2 模拟新的语法
    scala里,可以这样创建Map:

    Map(1 -> "one", 2 -> "two")
    

    其实就是元组的写法,等价于:

    Map((1, "one"), (2, "two"))
    

    以下是实现的相关定义:

    package scala
    object Predef {
    ...
        class ArrowAssoc[A] (x: A) {
            def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
        }
        implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
    ...
    }
    

    隐式参数

    通过参数列表可以让编译器插入隐式操作,编译器有时会用call(a)(b)替代call(a), 或者用new obj(a)(b)替代new obj(a),从而通过添加缺失的参数列表以满足函数的调用,被提供的是完整的最后一节 柯里化 参数,而不仅是最后的参数,例如call(a)(b, c, d)替代call(a),并且这最后一组参数列表必须被标记为implicit。

    class A (val name: String)
    ...
    def func (id: Int)(implicit p: A) = ...
    
    val a = new A("abc")
    func(1)(a) //此时只能显示调用
    
    implicit val aa = new A("cba")
    func(1)(aa) or func(1)
    

    scala隐式转换及其DSL实现(二)将介绍DSL实现的具体应用。

    相关文章

      网友评论

        本文标题:scala隐式转换及其DSL实现(一)

        本文链接:https://www.haomeiwen.com/subject/dkczlttx.html