標籤:

Scala從2.10開始試驗性的引入宏,是為了讓Scala獲得什麼樣的能力,這種能力在實際中究竟有什麼用處?


應用:

資料庫 slick/slick · GitHub

測試 ScalaMock 3.0 Preview Release

序列化 scala/pickling · GitHub

...

-----------------------

目前的情況來看,有了quasiquote之後,雖然還要知道些AST(有showRaw還是可以解決很多煩惱的),比以前的def macro時候要好了很多, 但是2.10.x版本還是不要用了,如簡單的

case q"..${a: List[Int]}"

會報錯:this quasiquote pattern is only supported in Scala 2.11

idea的scala插件對macro的編寫支持的一塌糊塗

如果說編寫 def macro的編寫複雜度估計是O(n),使用quasiquote大概就是O(logn),quasiquote的模板語法也在變得豐富,不知是好是壞,也許強類型語言的宏就該是這個樣子吧,期待Scala3.0

-------------------------

作用就是編譯期元編程唄,我可以在編譯期動態生成case class去裝數據,而不是運行期使用map;可以調用不存在的方法等,就等你腦動大開了


這是Scala Macro作者做的一個幻燈片What Are Macros Good For?:http://scalamacros.org/paperstalks/2013-07-17-WhatAreMacrosGoodFor.pdf

以及目前的進展:http://scalamacros.org/paperstalks/2014-11-22-TheStateOfTheMeta.pdf


上面的回答已經很全了,這裡提供另一個視角。

首先思考一個問題:如果你的應用程序有bug,那麼你希望在什麼情況下被檢測出來呢?

  • 編譯時:這是最理想的狀態,如果一個bug可以通過編譯器檢查出來,那麼程序員可以在第一時間發現問題,基本上就是一邊寫一邊fix。這也正是靜態編譯型語言的強大優勢。
  • 單元測試:沒有那麼理想但是也不差。每寫完一段跑一下測試,看看有沒有新的bug出來。對於scala來說,現在的工具鏈已經不錯了,左屏sbt &> ~test,右屏寫代碼愜意得很。
  • 運行時:這個就比較糟糕了。運行時才報錯意味著你得首先打包部署,這個時間開銷通常就比較大,而且在許多公司時不時要解決環境問題很讓人抓狂。

現在Scala macro存在的一大理由,其實就是增加了一種可能性,使得以前單元測試和運行時才能檢測到的bug,變成一個編譯時的錯誤。

舉個例子,假設我們要new一個Date,用某一個日期庫支持可以用ISO格式:

val d = Date("2015-02-29")

很顯然,2015年沒有2月29日,但是這段代碼編譯通過沒有任何問題。如果是單元測試有覆蓋到的話那麼還好,幾分鐘就知道有問題。但是如果是運行時才報錯就很糟糕,畢竟這只是一個很小的問題。

現在有了宏以後,這個日期庫就可以在編譯的時候對argument進行檢測,於是這一行就直接變成了編譯錯誤,順手就fix了。

再舉個例子,假設我們有一個case class:

case class Person(id: String, firstName: String, lastNmae: String, dob: Date)

來映射資料庫裡面的一個表:

CREATE TABLE PERSON (
id INTEGER PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(75) NOT NULL,
dob DATE NULL
);

這個映射有幾個問題:

  1. 生日在資料庫裡面是可以接受空值的,而在代碼里卻是非空的

  2. id的類型在代碼里是一個string,但是在資料庫裡面確是一個INT

這些問題在以前都只能是運行時的問題。尤其是第一個,往往在integration test裡面也不會覆蓋到,直接成為一個production issue,解決成本非常高昂。

現在有了宏,編輯器就可以在編譯的時候檢測你的內部模型是不是匹配資料庫的DDL。上面的兩個問題就都成了編譯時的錯誤。

這樣一來,只要發布的代碼和發布的資料庫DDL可以編譯通過,最終部署的時候會大大減少bug的幾率!

當然,Macro在2.10隻是一個測試功能,目前只支持method macro,但隨著功能更強大的macro paradise已經日趨成熟,在可以預見的未來會讓scala的開發更加註重邏輯本身,而把所有的細節都丟給編譯器。

最後第一句:目前已經有不少庫用了macro(尤其是typelevel的那些)。剛剛提到的第二個例子也已經有了一個不是很成熟的實現:https://github.com/jonifreeman/sqltyped


舉個我自己的簡單例子:在某些函數中寫參數檢查的代碼,檢查通不過就拋異常。

一般的寫法是這樣的:

require(name.length &> 3, "name.length should be &> 3")

require 需要一個 boolean 表達式和一個字元串——用於錯誤信息。但用宏我們可以做到更簡潔。

回想 C 語言的 assert 宏:

assert(x &> 3)

如果斷言失敗,會直接把「x &> 3」作為一個字元串輸出到錯誤信息中。那麼 Scala 的宏是否允許我們這樣做呢?答案是肯定的。

我寫了一個 check 宏,它的第一個參數是一個 ScalaTest 的表達式,同時也是錯誤信息:

check(name.length should be &> 3)

如果把 check 實現成一個普通函數的話那就必須這麼寫:

check(name.length should be &> 3, "name.length should be &> 3")

可見,通過宏可以實現消除重複。


樓主的問題, 宏的實現者

Eugene Burmako 給出的答案: http://scalamacros.org/paperstalks/2014-02-04-WhatAreMacrosGoodFor.pdf

實際的案例(都不是玩具):

- https://github.com/sirthias/parboiled2, 基於宏實現的 PEG 解析器生成器

- https://github.com/kifi/json-annotation, 基於宏註解實現的 JSON 序列化

- https://github.com/wacai/config-annotation, 基於宏註解實現配置項綁定(映射)


那她不是Scala了, 是一個複雜的 Lisp


推薦閱讀:

TAG:Scala |