8000 [Translation to Japanese] Macros overview by eed3si9n · Pull Request #175 · scala/docs.scala-lang · GitHub
[go: up one dir, main page]

Skip to content

[Translation to Japanese] Macros overview #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 23, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ja/overviews/core/macros.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
layout: overview
partof: macros
overview: macros
language: ja
label-color: important
label-text: Experimental
title: マクロ
---
61 changes: 61 additions & 0 deletions ja/overviews/macros/bundles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
layout: overview-large
language: ja

disqus: true

partof: macros
num: 6
outof: 7
title: マクロバンドル
---
<span class="label important" style="float: right;">MACRO PARADISE</span>

**Eugene Burmako 著**<br>
**Eugene Yokota 訳**

マクロバンドル (macro bundle) はマクロパラダイスと呼ばれているオフィシャル Scala リポジトリ内の実験的なブランチに含まれるリリース前の機能だ。[マクロパラダイス](/ja/overviews/macros/paradise.html)ページの説明にしたがってナイトリービルドをダウンロードしてほしい。

## マクロバンドル

現行の Scala 2.10.0 においてマクロ実装は関数として表されている。コンパイラがマクロ定義の適用を見つけると、マクロ実装を呼び出すという単純なものだ。しかし、実際に使ってみると以下の理由によりただの関数では不十分なことがあることが分かった:

<ol>
<li>関数に制限されることで複雑なマクロのモジュール化がしづらくなる。マクロのロジックがマクロ実装外のヘルパートレイトに集中していて、マクロ実装がヘルパーをインスタンス化するだけのラッパーになってしまっているのは典型的な例だ。</li>
<li>さらに、マクロのパラメータがマクロのコンテキストにパス依存であるため、ヘルパーと実装をつなぐのに<a href="/ja/overviews/macros/overview.html#writing_bigger_macros">特殊なおまじない</a>を必要とする。</li>
<li>マクロが進化してくると、コンパイラとマクロ間において別のコミュニケーションのインターフェイスが必要であることが<a href="https://twitter.com/milessabin/status/281379835773857792">明らかになってきた</a>。現在はコンパイラはマクロ展開しかできないが、例えばマクロを型推論に使いたいとしたらどうだろう?</li>
</ol>

マクロバンドルは、マクロ実装を `scala.reflect.macros.Macro` を継承したトレイトで実装することで、これらの問題に対する解決策となる。この基底トレイトは `c: Context` 変数を定義してあるため、マクロ実装側のシグネチャで宣言しなくても済むようになり、モジュール化を簡単にする。将来には `Macro` は `onInfer` などのコールバックメソッドを提供するかもしれない。

trait Macro {
val c: Context
}

バンドルで定義されたマクロ実装を参照するのは、オブジェクトで定義された実装を参照するのと同じ方法で行われる。まずバンドル名を指定して、必要ならば型引数付きでメソッドを選択する。

import scala.reflect.macros.Context
import scala.reflect.macros.Macro

trait Impl extends Macro {
def mono = c.literalUnit
def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString)
}

object Macros {
def mono = macro Impl.mono
def poly[T] = macro Impl.poly[T]
}

## マクロコンパイラ

マクロバンドルを実装する際に気付いたのはマクロ定義とマクロ実装をリンクしている機構が硬直すぎるということだ。この機構は単に `scala/tools/nsc/typechecker/Macros.scala` でハードコードされたロジックを使っていて、マクロ定義の右辺値を静的なメソッドの参照として型検査して、そのメソッドを対応するマクロ実装に使っている。

これからはマクロ定義のコンパイルが拡張できるようになる。ハードコードされた実装でマクロ実装を照会するのではなく、マクロエンジンはスコープ内にある `MacroCompiler` を implicit 検索して、マクロ定義の `DefDef` を渡して `resolveMacroImpl` メソッドを呼び出し、静的なメソッドへの参照を返してもらう。もちろんこれが正しく動作するためには `resolveMacroImpl` そのものも[型指定の無い](/ja/overviews/macros/untypedmacros.html)マクロであるべきだ。

trait MacroCompiler {
def resolveMacroImpl(macroDef: _): _ = macro ???
}

この型クラスのデフォルトのインスタンス `Predef.DefaultMacroCompiler` はこれまでハードコードされていた型検査のロジックを実装する。
代替実装を提供することで、例えばマクロ定義のためのライトウェイトな構文や `c.introduceTopLevel` を使ったアドホックに生成されるマクロ実装を提供することができる。
182 changes: 182 additions & 0 deletions ja/overviews/macros/inference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
layout: overview-large
language: ja

disqus: true

partof: macros
num: 7
outof: 7
title: 型推論補助マクロ
---
<span class="label important" style="float: right;">MACRO PARADISE</span>

**Eugene Burmako 著**<br>
**Eugene Yokota 訳**

型推論補助マクロ (inference-driving macro) はマクロパラダイスと呼ばれているオフィシャル Scala リポジトリ内の実験的なブランチに含まれるリリース前の機能だ。[マクロパラダイス](/ja/overviews/macros/paradise.html)ページの説明にしたがってナイトリービルドをダウンロードしてほしい。

注意! これはパラダイスの実験的機能の中でも最も実験的なものだ。型マクロのような完成に近づいているものと違って、この機能は API の詳細が定まっていないだけでなく、現行の方法が正しいものであるかも確信できていない。フィードバックをこれまでになく必要としている。この[原版](/overviews/macros/inference.html)のコメント欄に直接コメントするか、[scala-internals での議論](http://groups.google.com/group/scala-internals/browse_thread/thread/ad309e68d645b775)に参加してほしい。

## 動機となった具体例

型推論補助マクロが生まれるキッカケとなったユースケースは、Miles Sabin 氏と氏が作った [shapeless](https://github.com/milessabin/shapeless) ライブラリにより提供された。Miles は 型間の同型射 (isomorphism) を表す `Iso` トレイトを定義した。

trait Iso[T, U] {
def to(t : T) : U
def from(u : U) : T
}

現在は `Iso` のインスタンスは手動で定義され、暗黙の値として公開される。定義された同型射を利用したいメソッドは `Iso` 型の暗黙のパラメータを宣言して、そこに implicit 検索時に値が渡される。

def foo[C](c: C)(implicit iso: Iso[C, L]): L = iso.from(c)

case class Foo(i: Int, s: String, b: Boolean)
implicit val fooIsoTuple = Iso.tuple(Foo.apply _, Foo.unapply _)

val tp = foo(Foo(23, "foo", true))
tp : (Int, String, Boolean)
tp == (23, "foo", true)

見ての通り、ケースクラスとタプル間の同型射は簡単に書くことができる (実際には shapeless は Iso を用いてケースクラスと HList 間を変換するが、話を簡潔にするためにここではタプルを使う)。コンパイラは必要なメソッドを既に生成しているため、それを利用するだけでいい。残念ながら Scala 2.10.0 ではこれより単純化することは不可能で、全てのケースクラスに対して手動で implicit の `Iso` インスタンスを定義する必要がある。

これまでマクロはボイラープレイトを撤去するのに役立つをことを示してきたが、2.10.0 の形のままではここでは役に立たない。問題は master では数週間まえに修正されている [SI-5923](https://issues.scala-lang.org/browse/SI-5923) だけではない。

真の問題は `foo` のようなメソッドの適用を型検査する際に、scalac は何も知らない型引数 `L` を推論する必要があることだ (これはドメインに特化した知識なのでコンパイラのせいではない)。結果として、`Iso[C, L]` を合成する暗黙のマクロを定義したとしても、scalac はマクロを展開する前に `L` を `Nothing` と推論して全てが崩れてしまう。

## 型推論の内部構造

この数日で分かったのは Scala の型推論は `scala/tools/nsc/typechecker/Infer.scala` 内の [`inferExprInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1123) と
[`inferMethodInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1173) という 2つのメソッドで行われているということだ。
今の所、型推論を使った様々なコードに対して `-Yinfer-debug` のログを表示させてみる以外、特に書くことは無い。

def foo[T1](x: T1) = ???
foo(2)

[solve types] solving for T1 in ?T1
[infer method] solving for T1 in (x: T1)Nothing based on (Int)Nothing (solved: T1=Int)

def bar[T2] = ???
bar

[solve types] solving for T2 in ?T2
inferExprInstance {
tree C.this.bar[T2]
tree.tpe Nothing
tparams type T2
pt ?
targs Nothing
tvars =?Nothing
}

class Baz[T]
implicit val ibaz = new Baz[Int]
def baz[T3](implicit ibaz: Baz[T3]) = ???
baz

[solve types] solving for T3 in ?T3
inferExprInstance {
tree C.this.baz[T3]
tree.tpe (implicit ibaz: C.this.Baz[T3])Nothing
tparams type T3
pt ?
targs Nothing
tvars =?Nothing
}
inferExprInstance/AdjustedTypeArgs {
okParams
okArgs
leftUndet type T3
}
[infer implicit] C.this.baz[T3] with pt=C.this.Baz[T3] in class C
[search] C.this.baz[T3] with pt=C.this.Baz[T3] in class C, eligible:
ibaz: => C.this.Baz[Int]
[search] considering T3 (pt contains ?T3) trying C.this.Baz[Int] against pt=C.this.Baz[T3]
[solve types] solving for T3 in ?T3
[success] found SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int))) for pt C.this.Baz[=?Int]
[infer implicit] inferred SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int)))

class Qwe[T]
implicit def idef[T4] = new Qwe[T4]
def qwe[T4](implicit xs: Qwe[T4]) = ???
qwe

[solve types] solving for T4 in ?T4
inferExprInstance {
tree C.this.qwe[T4]
tree.tpe (implicit xs: C.this.Qwe[T4])Nothing
tparams type T4
pt ?
targs Nothing
tvars =?Nothing
}
inferExprInstance/AdjustedTypeArgs {
okParams
okArgs
leftUndet type T4
}
[infer implicit] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C
[search] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C, eligible:
idef: [T4]=> C.this.Qwe[T4]
[solve types] solving for T4 in ?T4
inferExprInstance {
tree C.this.idef[T4]
tree.tpe C.this.Qwe[T4]
tparams type T4
pt C.this.Qwe[?]
targs Nothing
tvars =?Nothing
}
[search] considering T4 (pt contains ?T4) trying C.this.Qwe[Nothing] against pt=C.this.Qwe[T4]
[solve types] solving for T4 in ?T4
[success] found SearchResult(C.this.idef[Nothing], ) for pt C.this.Qwe[=?Nothing]
[infer implicit] inferred SearchResult(C.this.idef[Nothing], )
[solve types] solving for T4 in ?T4
[infer method] solving for T4 in (implicit xs: C.this.Qwe[T4])Nothing based on (C.this.Qwe[Nothing])Nothing (solved: T4=Nothing)

## 提案

[マクロバンドル](/ja/overviews/macros/bundles.html)で提供されるインフラを使って、`onInfer` というコールバックを導入する。
このコールバックが定義されていれば、コンパイラによって `inferExprInstance` と `inferMethodInstance` から呼び出される。
これは、`inferXXX` メソッドへの引数をカプセル化し未知の型パラメータの型推論を実行するメソッドを提供する `c.TypeInferenceContext` 型のパラメータを 1つ受け取る。

trait Macro {
val c: Context
def onInfer(tc: c.TypeInferenceContext): Unit = tc.inferDefault()
}

type TypeInferenceContext <: TypeInferenceContextApi
trait TypeInferenceContextApi {
def tree: Tree
def unknowns: List[Symbol]
def expectedType: Type
def actualType: Type

// TODO: can we get rid of this couple?
def keepNothings: Boolean
def useWeaklyCompatible: Boolean

def infer(sym: Symbol, tpe: Type): Unit

// TODO: would be lovely to have a different signature here, namely:
// def inferDefault(sym: Symbol): Type
// so that the macro can partially rely on out-of-the-box inference
// and infer the rest afterwards
def inferDefault(): Unit
}

このインフラがあれば、`materializeIso` マクロを書いて手動で implicit を宣言する手間を省くことができる。
完全なソースコードは [paradise/macros](https://github.com/scalamacros/kepler/blob/paradise/macros/test/files/run/macro-programmable-type-inference/Impls_Macros_1.scala) より入手できるが、以下に主な部分を抜粋する:

override def onInfer(tic: c.TypeInferenceContext): Unit = {
val C = tic.unknowns(0)
val L = tic.unknowns(1)
import c.universe._
import definitions._
val TypeRef(_, _, caseClassTpe :: _ :: Nil) = tic.expectedType // Iso[Test.Foo,?]
tic.infer(C, caseClassTpe)
val fields = caseClassTpe.typeSymbol.typeSignature.declarations.toList.collect{ case x: TermSymbol if x.isVal && x.isCaseAccessor => x }
val core = (TupleClass(fields.length) orElse UnitClass).asType.toType
val tequiv = if (fields.length == 0) core else appliedType(core, fields map (_.typeSignature))
tic.infer(L, tequiv)
}
Loading
0