scala隱式轉(zhuǎn)換及其DSL實現(xiàn)(二)

在實現(xiàn)具體應(yīng)用前旬迹,先介紹下隱式類的概念。

隱式類介紹

Scala 2.10引入了一種叫做隱式類的新特性求类。隱式類指的是用implicit關(guān)鍵字修飾的類奔垦。在對應(yīng)的作用域內(nèi),帶有這個關(guān)鍵字的類的主構(gòu)造函數(shù)可用于隱式轉(zhuǎn)換仑嗅。

用法

創(chuàng)建隱式類時宴倍,只需要在對應(yīng)的類前加上implicit關(guān)鍵字。比如:

object Helpers {
    implicit class IntWithTimes(x: Int) {
        def times[A](f: => A): Unit = {
            def loop(current: Int): Unit =
                if(current > 0) {
                    f
                    loop(current - 1)
                }
            loop(x)
        }
    }
}

這個例子創(chuàng)建了一個名為IntWithTimes的隱式類仓技。這個類包含一個int值和一個名為times的方法鸵贬。要使用這個類,只需將其導(dǎo)入作用域內(nèi)并調(diào)用times方法脖捻。比如:

scala> import Helpers._
import Helpers._
scala> 5 times println("HI")
HI
HI
HI
HI
HI

使用隱式類時阔逼,類名必須在當(dāng)前作用域內(nèi)可見且無歧義,這一要求與隱式值等其他隱式類型轉(zhuǎn)換方式類似地沮。

限制條件

隱式類有以下限制條件:

  • 只能在別的trait/類/對象內(nèi)部定義嗜浮。
object Helpers {
   implicit class RichInt(x: Int) // 正確!
}
implicit class RichDouble(x: Double) // 錯誤摩疑!
  • 構(gòu)造函數(shù)只能攜帶一個非隱式參數(shù)危融。
implicit class RichDate(date: java.util.Date) // 正確! 
implicit class Indexer[T](collecton: Seq[T], index: Int) // 錯誤雷袋! 
implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正確吉殃!

雖然我們可以創(chuàng)建帶有多個非隱式參數(shù)的隱式類,但這些類無法用于隱式轉(zhuǎn)換楷怒。

  • 在同一作用域內(nèi)蛋勺,不能有任何方法、成員或?qū)ο笈c隱式類同名鸠删。注意:這意味著隱式類不能是case class抱完。
object Bar
implicit class Bar(x: Int) // 錯誤!
val x = 5
implicit class x(y: Int) // 錯誤刃泡!
implicit case class Baz(x: Int) // 錯誤巧娱!

現(xiàn)在,我們來實現(xiàn)一個簡單的DSL捅僵。通常家卖,我們在計算貨幣運算時,如果不涉及到幣種的轉(zhuǎn)換庙楚,通常可以簡單的用數(shù)學(xué)表達(dá)式計算趴樱,比如23 + 12馒闷,在任何編程語言里都能做到酪捡。但是涉及到幣種時,這種簡單的表達(dá)式不滿足要求了纳账,需要做一下匯率轉(zhuǎn)換才能進(jìn)行計算逛薇,然而這樣卻不很直觀。我們需要一種直觀的運算疏虫,同時又要包含匯率轉(zhuǎn)換的操作永罚,比如像這樣的表達(dá)式:23(USD) + 12(EUR),下面卧秘,我們就用scala的隱式轉(zhuǎn)換實現(xiàn)這樣的要求呢袱。

首先,我們定義一個關(guān)于貨幣的伴生類(class)和伴生對象(單例對象object)翅敌。定義它們的成員變量code羞福,name以及object中對應(yīng)的幣種信息。關(guān)鍵代碼如下:

object Currency {
    type Rate = Map[(Currency, Currency), BigDecimal] //first_currency / second_currency = rate: BigDecimal

    def apply (code: String, name: String) = new Currency(code, name)

    /*
    def apply (code: String) = code.toLowerCase match {
        case "USD" => USD
        case "EUR" => EUR
        case "GBP" => GBP
    }
    */

    lazy val USD: Currency = Currency("USD", "美元")
    lazy val EUR: Currency = Currency("EUR", "歐元")
    lazy val GBP: Currency = Currency("GBP", "英鎊")

    def convert (from: Currency, to: Currency, rate: Rate): BigDecimal = {
        if (from.code.equalsIgnoreCase(to.code))
            1
        else
            rate.getOrElse((from, to), 1 / rate((to, from)))
    }

    implicit class BigDecimalExt (value: BigDecimal) {
        def apply(currency: Currency)(implicit rate: Rate): Money = Money(currency, value)
    }

    implicit class IntExt (value: Int) {
        def apply(currency: Currency)(implicit rate: Rate): Money = (value: BigDecimal).apply(currency)
    }

    implicit class CurrIntExt (c: Currency) {
        def apply(value: Int)(implicit rate: Rate): Money = Money(c, value)(rate)
    }

}


class Currency (val code: String, val name: String = "未知")

object代碼中蚯涮,我們定義了匯率的類型Rate治专,也就是匯率等于先后兩個幣種的比值。convert方法用于后面金錢的具體轉(zhuǎn)換遭顶。

然后定義Money類:

case class Money (currency: Currency,amount: BigDecimal)(implicit rate: Rate) {
    def to (to: Currency): Money = {
        val rates = Currency.convert(currency, to, rate)
        Money(to, rates * amount)
    }

    def operation (that: Money, operate: (BigDecimal, BigDecimal) => BigDecimal): Money = {
        that match {
            case Money(curr, am) if (currency.code equalsIgnoreCase curr.code) => Money(currency, operate(amount, am))
            case Money(curr, am) => operation(that.to(currency), operate)
        }
    }

    def + (that: Money): Money = {
        operation(that, (a, b)=> a + b) //占位符寫法 operation(that, _ + _)
    }
}

Money類中张峰,方法to(to: Curreyct)用于幣種轉(zhuǎn)換,+用于幣種計算棒旗,此處僅僅實現(xiàn)了+方法喘批。
至此,我們實現(xiàn)了不同幣種加法的運算嗦哆。利用上面的代碼谤祖,我們可以有三種不同的調(diào)用方法。

  • 傳統(tǒng)的方法調(diào)用
    val rate_0: Rate = Map(
    (GBP, EUR) -> 1.39,
    (EUR, USD) -> 1.08,
    (GBP, USD) -> 1.5
    )

      val r0 = (Money(USD, 42)(rate_0) + Money(EUR, 35)(rate_0)) to GBP
      println("r0: " + r0.currency.name + " " + r0.currency.code + " " + r0.amount)
    
  • 兩種DSL方式寫法(涉及到Currency里的隱式類)
    implicit val rate_1: Rate = Map(
    (GBP, EUR) -> 1.39,
    (EUR, USD) -> 1.08,
    (GBP, USD) -> 1.5
    )//for r1 and r2

      import Currency.IntExt
      val r1 = (42(USD) + 35(EUR)) to GBP
      println("r1: " + r1.currency.name + " " + r1.currency.code + " " + r1.amount)
    
      import Currency.CurrIntExt
      val r2 = (USD(42) + EUR(35)) to GBP
      println("r2: " + r2.currency.name + " " + r2.currency.code + " " + r2.amount)
    

小記:雖然scala隱式轉(zhuǎn)換是一個非常強(qiáng)大的功能老速,但是實現(xiàn)起來很困難粥喜,dsl雖然使用方便,但是這樣的“魔幻”代碼理解起來還是費勁橘券。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末额湘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子旁舰,更是在濱河造成了極大的恐慌锋华,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芹橡,居然都是意外死亡赂蠢,警方通過查閱死者的電腦和手機(jī)誓竿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門纳猫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婆咸,“玉大人,你說我怎么就攤上這事芜辕∩薪荆” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵侵续,是天一觀的道長倔丈。 經(jīng)常有香客問我,道長状蜗,這世上最難降的妖魔是什么需五? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮诗舰,結(jié)果婚禮上警儒,老公的妹妹穿的比我還像新娘。我一直安慰自己眶根,他們只是感情好蜀铲,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著属百,像睡著了一般记劝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上族扰,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天厌丑,我揣著相機(jī)與錄音,去河邊找鬼渔呵。 笑死怒竿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扩氢。 我是一名探鬼主播耕驰,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼录豺!你這毒婦竟也來了朦肘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤双饥,失蹤者是張志新(化名)和其女友劉穎媒抠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咏花,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡趴生,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冲秽。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡舍咖,死狀恐怖矩父,靈堂內(nèi)的尸體忽然破棺而出锉桑,到底是詐尸還是另有隱情,我是刑警寧澤窍株,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布民轴,位于F島的核電站,受9級特大地震影響球订,放射性物質(zhì)發(fā)生泄漏后裸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一冒滩、第九天 我趴在偏房一處隱蔽的房頂上張望微驶。 院中可真熱鬧,春花似錦开睡、人聲如沸因苹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扶檐。三九已至,卻和暖如春胁艰,著一層夾襖步出監(jiān)牢的瞬間款筑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工腾么, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留奈梳,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓解虱,卻偏偏與公主長得像攘须,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饭寺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容