8

私はこの分野をあまり知りません。

コンパイルプリプロセッサやCGLIB、ASM、Bytemanなどのツールを使用したJavaで可能なことと比較して、マクロを使用したScala 2.10で可能なことを誰かが説明できますか?

4

2 に答える 2

16

【更新】 : Slickを使った例を取り入れてみました。Java(Scala以外)のオーディエンスにとって、これらの多くを要約するのは困難です。

Scala 2.10のマクロは、第一級市民として、本格的なメタプログラミングを適切な言語にもたらします。

// we often do this:
log("(myList++otherList).size: " + (myList++otherList).size)
// just to log the string:  
// "(myList++otherList).size: 42"

// Imagine log, if implemented as a macro:
log((myList++otherList).size)
// could potentially log both the EXPRESSION AND IT'S VALUE:
// "(myList++otherList).size: 42"

このような機能は、通常、テキストによる前処理またはバイトコード操作のいずれかを介して安全かつクリーンな方法で実現できますか?

メタプログラミングは、ある段階でのコード生成に帰着しますが、修飾されていません。これは、「自分でコードを少し書く必要がない」という観点から、さまざまな手法の総称です。これらの手法は、段階の大まかな順序で-コンパイル済みの生のソースからコードの実行まで、リストは次のようになります。

  • テキストソースプリプロセッサ(C)
  • テンプレートシステム(C ++)(多くの人が、上記は原始的すぎて考慮できないと主張するかもしれません)
  • 一部の動的言語でのリフレクション「自然に利用可能」(Ruby)
  • 静的に型付けされた言語(Java)での実行時の反映。これにより、型の安全性を犠牲にして、言語にある程度のダイナミズムがもたらされます。
  • バイトコード操作

(コンパイル時に実行されるマクロシステムを省略していることに注意してください。それらについては次の段落で詳しく説明します。)

まず、上記のすべての手法が基本的にコードを生成することを考慮してください。プレーンテキストのソースコードの生成/操作であれ、実行時のバイトコードの生成/操作であれ。

マクロシステムはどうですか?コンパイル時に実行され、テキストのソースコードやコンパイルされたバイトコードではなく、はるかに価値のある段階で動作するマクロは、コンパイル中にプログラムのASTで動作し、そこで利用可能な情報とコンパイルプロセスとの統合によりレンダリングされます。それらにはいくつかの強力な利点があります。それらは動的言語(LispやDylanなど)と静的に型付けされた言語(TemplateHaskellScala2.10のセルフクリーニングマクロ)の両方で利用できます。

Scala 2.10のマクロに関しては、頭から離れて、最も重要な利点は次のとおりです。

型の安全性:コンパイルプリプロセッサとバイトコード操作は、型システムを利用できません。マクロ(特にコンパイル時マクロ)では、Scala 2.10が持つ種類のマクロ言語は、コンパイラーのAPIにアクセスできるScala自体です。マクロでは、通常はコンパイル時にのみ実行される可能性のある完全な型情報を使用した、あらゆる種類の静的分析/ソースコードのチェックを利用できます。

(安全)構文拡張:マクロを使用すると、言語構造を適応させてDSLをより適切に実装できます。良い例は、SQLクエリをタイプセーフなScalaコードとして表現できるデータベースライブラリであるSlickです。

まず、単純なScalaリスト処理について考えてみましょう。データベースやマクロについてはまだ話されていません。

val coffees : List[Coffee] = // gotten from somewhere

// get from this list a list of (name, price) pairs also filtering on some condition
for {
  c <- coffees if c.supID == 101
  //                      ^ comparing an Int to an Int - normal stuff.
} yield (c.name, c.price)

// For Java programmers, the above is Scala's syntactic sugar for
coffees.filter(c => c.supId == 101).map(c => (c.name, c.price))

Slickは、非マクロバージョンであっても、データベーステーブルをScalaコレクションであるかのように扱うことができます。これは、非マクロバージョン(Slickではリフト埋め込みAPIと呼ばれます)が同じことを実現する方法であり、Coffee代わりにSQLテーブルでした。

// WITHOUT MACROS (using enough of Scala's other features):
// Generates a query "SELECT NAME,PRICE FROM COFFEES IF SUP_ID = 101" 
for {
  c <- Coffees if c.supID === 101
  //                      ^ comparing Rep[Int] to Rep[Int]!
  // The above is Scala-shorthand for
  //     c <- Coffees if c.supID.===(Const[Int](101))
} yield (c.name, c.price)

十分近い!ただし、ここでは、SQL列の表現を実際のと比較できないという明らかな理由から、この===メソッドを使用して使用することはできません。==Int

Scala 2.10が手元にある場合、これはマクロバージョンで解決されます。

// WITH MACROS:
// Generates a query "SELECT NAME,PRICE FROM COFFEES IF SUP_ID = 101" 
for {
  c <- coffees if c.supID == 101
  //                      ^ comparing Int to Int!
} yield (c.name, c.price)

したがって、ここではマクロを利用して、SQLとプレーンなScalaコレクションの両方に同じ構文を提供します。マクロを魅力的なものにしているのは、Scalaで利用可能な既存の表現力と構成性に加えて、型安全性とマクロ衛生の組み合わせです。

また、他の回答によって提供されたリンクからこの例を検討してください。

def assert(cond: Boolean, msg: Any) = macro impl

def impl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[Any]) =
  if (assertionsEnabled)
    // if (!cond) raise(msg)
    If(Select(cond.tree, newTermName("$unary_bang")),
        Apply(Ident(newTermName("raise")), List(msg.tree)),
        Literal(Constant(())))
  else
    Literal(Constant(())

つまり、マクロを定義しますassert。その使用法は、メソッド呼び出しに似ています。

import assert
assert(2 + 2 == 4, "weird arithmetic")

のみ、assertはマクロでありメソッドではないため、ブール式2 + 2 == 4 はアサーションが有効になっている場合にのみ評価されます。ASTの表現に役立つ省略形が存在することに注意してください。ただし、この例は、この方法でより明確になることを願っています。

大事なことを言い忘れましたが、Scala 2.10マクロはScala本体の一部になり ます。サードパーティのライブラリによって提供されるのではなく、標準ディストリビューションに統合されます。

于 2012-12-12T10:47:51.433 に答える
4

Faizが述べた点に加えて、Scalaマクロは衛生的です。識別子が誤ってキャプチャされることはありません。特に、Scalaマクロはセルフクリーニングです。具体化自体がマクロである場合、衛生は具体化によって達成されます。これがどのように機能するかについてのより深い洞察については、テクニカルレポートのScalaMacrosを参照してください。

于 2012-12-12T11:39:21.433 に答える