diff --git a/ja/overviews/macros/implicits.md b/ja/overviews/macros/implicits.md index be2bf3a984..5069d75bac 100644 --- a/ja/overviews/macros/implicits.md +++ b/ja/overviews/macros/implicits.md @@ -104,7 +104,7 @@ Scala implicit の標準機能である複数のパラメータや重複した ### 提案 -[https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499) が示すとおり、上記の問題の解法は非常にシンプルでエレガントなものだ。NEW 現在これは [マクロパラダイス](/ja/overviews/macros/paradise.html) と Scala 2.11.0-M5 の両方で実装されている。 +[https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499) が示すとおり、上記の問題の解法は非常にシンプルでエレガントなものだ。 Scala 2.10 においてはマクロの適用は全ての型引数が推論されるまでは展開されない。しかし、そうする必要は特に無い。 タイプチェッカはできる所まで推論して (この例の場合、`C` は `Foo` と推論され、`L` は未定となる) そこで一旦停止する。その後マクロを展開して、展開された型を補助にタイプチェッカは再び以前未定だった型引数の型検査を続行する。 diff --git a/overviews/macros/annotations.md b/overviews/macros/annotations.md index d5f297df06..fa87c75e85 100644 --- a/overviews/macros/annotations.md +++ b/overviews/macros/annotations.md @@ -5,8 +5,8 @@ title: Macro Annotations disqus: true partof: macros -num: 8 -outof: 10 +num: 9 +outof: 11 languages: [ja] --- MACRO PARADISE diff --git a/overviews/macros/blackbox-whitebox.md b/overviews/macros/blackbox-whitebox.md index ba94d69f39..b8bc99a1e5 100644 --- a/overviews/macros/blackbox-whitebox.md +++ b/overviews/macros/blackbox-whitebox.md @@ -6,7 +6,7 @@ disqus: true partof: macros num: 3 -outof: 10 +outof: 11 --- EXPERIMENTAL diff --git a/overviews/macros/bundles.md b/overviews/macros/bundles.md index 54ce38e529..9cfdec4ed1 100644 --- a/overviews/macros/bundles.md +++ b/overviews/macros/bundles.md @@ -6,7 +6,7 @@ disqus: true partof: macros num: 5 -outof: 10 +outof: 11 languages: [ja] --- EXPERIMENTAL diff --git a/overviews/macros/implicits.md b/overviews/macros/implicits.md index 5fe55e2f9c..9661e5f2e2 100644 --- a/overviews/macros/implicits.md +++ b/overviews/macros/implicits.md @@ -6,7 +6,7 @@ disqus: true partof: macros num: 6 -outof: 10 +outof: 11 languages: [ja] --- EXPERIMENTAL @@ -136,8 +136,7 @@ macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothin ### Proposed solution As demonstrated by [https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499), the solution to the outlined -problem is extremely simple and elegant. NEW It is currently being implemented -both for [macro paradise](/overviews/macros/paradise.html) and Scala 2.11.0-M5. +problem is extremely simple and elegant. In 2.10 we don't allow macro applications to expand until all their type arguments are inferred. However we don't have to do that. The typechecker can infer as much as it possibly can (e.g. in the running example `C` will be inferred to `Foo` and diff --git a/overviews/macros/overview.md b/overviews/macros/overview.md index a04ce43237..7b62f158cf 100644 --- a/overviews/macros/overview.md +++ b/overviews/macros/overview.md @@ -6,7 +6,7 @@ disqus: true partof: macros num: 4 -outof: 10 +outof: 11 languages: [ja] --- EXPERIMENTAL diff --git a/overviews/macros/paradise.md b/overviews/macros/paradise.md index ca3dbfeb22..6e401f6fd9 100644 --- a/overviews/macros/paradise.md +++ b/overviews/macros/paradise.md @@ -5,8 +5,8 @@ title: Macro Paradise disqus: true partof: macros -num: 9 -outof: 10 +num: 10 +outof: 11 languages: [ja] --- NEW diff --git a/overviews/macros/quasiquotes.md b/overviews/macros/quasiquotes.md index 8e02b5e9d8..1a2ae836cd 100644 --- a/overviews/macros/quasiquotes.md +++ b/overviews/macros/quasiquotes.md @@ -5,8 +5,8 @@ title: Quasiquotes disqus: true partof: macros -num: 7 -outof: 10 +num: 8 +outof: 11 languages: [ja] --- EXPERIMENTAL diff --git a/overviews/macros/roadmap.md b/overviews/macros/roadmap.md index fbca1b14ac..7f11926b2c 100644 --- a/overviews/macros/roadmap.md +++ b/overviews/macros/roadmap.md @@ -5,8 +5,8 @@ title: Roadmap disqus: true partof: macros -num: 10 -outof: 10 +num: 11 +outof: 11 languages: [ja] --- @@ -14,17 +14,18 @@ languages: [ja] **Eugene Burmako** -| Feature | Scala 2.10 | [Paradise 2.10](/overviews/macros/paradise.html) | [Paradise 2.11](/overviews/macros/paradise.html) | Scala 2.11 | -|-----------------------------------------------------------------------------------|--------------------|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|--------------| -| [Blackbox/whitebox separation](/overviews/macros/blackbox-whitebox.html) | No | No 1 | Yes 1 | Yes | -| [Def macros](/overviews/macros/overview.html) | Yes | Yes 1 | Yes 1 | Yes | -| [Macro bundles](/overviews/macros/bundles.html) | No | No 1 | Yes 1 | Yes | -| [Implicit macros](/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes 1 | Yes 1 | Yes | -| [Fundep materialization](/overviews/macros/implicits.html#fundep_materialization) | No | Yes 2 | Yes 1 | Yes | -| [Quasiquotes](/overviews/macros/quasiquotes.html) | No | Yes 1 | Yes 1 | Yes | -| [Type macros](/overviews/macros/typemacros.html) | No | [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html)| [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) | No | -| [Untyped macros](/overviews/macros/untypedmacros.html) | No | [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html)| [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) | No | -| [Macro annotations](/overviews/macros/annotations.html) | No | Yes 2 | Yes 2 | No | +| Feature | Scala 2.10 | [Paradise 2.10](/overviews/macros/paradise.html) | [Paradise 2.11](/overviews/macros/paradise.html) | Scala 2.11 | +|-----------------------------------------------------------------------------------|--------------------|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|-----------------| +| [Blackbox/whitebox separation](/overviews/macros/blackbox-whitebox.html) | No | No 1 | Yes 1 | Yes | +| [Def macros](/overviews/macros/overview.html) | Yes | Yes 1 | Yes 1 | Yes | +| [Macro bundles](/overviews/macros/bundles.html) | No | No 1 | Yes 1 | Yes | +| [Implicit macros](/overviews/macros/implicits.html) | Yes (since 2.10.2) | Yes 1 | Yes 1 | Yes | +| [Fundep materialization](/overviews/macros/implicits.html#fundep_materialization) | No | Yes 2 | Yes 1 | Yes | +| [Type providers](/overviews/macros/typeproviders.html) | Partial support | Yes 2 | Yes 2 | Partial support | +| [Quasiquotes](/overviews/macros/quasiquotes.html) | No | Yes 1 | Yes 1 | Yes | +| [Type macros](/overviews/macros/typemacros.html) | No | [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html)| [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) | No | +| [Untyped macros](/overviews/macros/untypedmacros.html) | No | [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html)| [Discontinued](http://scalamacros.org/news/2013/08/05/macro-paradise-2.0.0-snapshot.html) | No | +| [Macro annotations](/overviews/macros/annotations.html) | No | Yes 2 | Yes 2 | No |
1 This feature doesn't bring a compile-time or a runtime dependency on macro paradise. This means that neither compiling against your bytecode that uses this feature, nor running this bytecode requires the macro paradise plugin to be present on classpath.
2 This feature brings a compile-time, but not a runtime dependency on macro paradise. This means that compiling against your bytecode that uses this feature will need the plugin to be added to your users' builds, however running this bytecode or results of macro expansions produced by this bytecode doesn't need additional classpath entries.
\ No newline at end of file diff --git a/overviews/macros/toc.md b/overviews/macros/toc.md index b6f732406f..c5c84578fb 100644 --- a/overviews/macros/toc.md +++ b/overviews/macros/toc.md @@ -6,7 +6,7 @@ disqus: true partof: macros num: 1 -outof: 10 +outof: 11 languages: [ja] --- @@ -15,6 +15,7 @@ languages: [ja] 1. [Def Macros](/overviews/macros/overview.html) 1. [Macro Bundles](/overviews/macros/bundles.html) 1. [Implicit Macros](/overviews/macros/implicits.html) +1. [Type Providers](/overviews/macros/typeproviders.html) 1. [Quasiquotes](/overviews/macros/quasiquotes.html) 1. [Macro Annotations](/overviews/macros/annotations.html) 1. [Macro Paradise](/overviews/macros/paradise.html) diff --git a/overviews/macros/typeproviders.md b/overviews/macros/typeproviders.md new file mode 100644 index 0000000000..2ffe745be1 --- /dev/null +++ b/overviews/macros/typeproviders.md @@ -0,0 +1,133 @@ +--- +layout: overview-large +title: Type Providers + +disqus: true + +partof: macros +num: 7 +outof: 11 +languages: [ja] +--- +EXPERIMENTAL + +**Eugene Burmako** + +Type providers aren't implemented as a dedicated macro flavor, but can rather built on top of the functionality +that Scala macros already provide. + +There are two strategies of emulating type providers: one based on structural types (referred to as "fake type providers") +and one based on macro annotations (referred to as "real type providers"). The former builds on functionality available +in 2.10.x and 2.11, while the latter requires macro paradise. Both strategies can be used to implement erased type providers +as described below. + +Note that macro paradise is needed both to compile and to expand macro annotations, +which means that both authors and users of real type providers will have to add macro paradise to their builds. +However, after macro annotations expand, the resulting code will no longer have any references to macro paradise +and won't require its presence at compile-time or at runtime. + +## Type providers + +Type providers are a strongly-typed type-bridging mechanism, which enables information-rich programming in F# 3.0. +A type provider is a compile-time facility, which is capable of generating definitions and their implementations +based on static parameters describing datasources. Type providers can operate in two modes: non-erased and erased. +The former is similar to textual code generation in the sense that every generated type becomes bytecode, while +in the latter case generated types only manifest themselves during type checking, but before bytecode generation +get erased to programmer-provided upper bounds. + +In Scala, macro expansions can generate whatever code the programmer likes, including `ClassDef`, `ModuleDef`, `DefDef`, +and other definition nodes, so the code generation part of type providers is covered. Keeping that in mind, in order +to emulate type providers we need to solve two more challenges: + +1. Make generated definitions publicly visible (def macros, the only available macro flavor in Scala 2.10 and 2.11, +are local in the sense that the scope of their expansions is limited: [https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ](https://groups.google.com/d/msg/scala-user/97ARwwoaq2U/kIGWeiqSGzcJ)). +1. Make generated definitions optionally erasable (Scala supports erasure for a number of language constructs, +e.g. for abstract type members and value classes, but the mechanism is not extensible, which means that macro writers can't customize it). + +### Fake type providers + +Even though the scope of definitions introduced by expansions of def macros is limited to those expansions, +these definitions can escape their scopes by turning into structural types. For instance, consider the `h2db` macro that +takes a connection string and generates a module that encapsulates the given database, expanding as follows. + + def h2db(connString: String): Any = macro ... + + // an invocation of the `h2db` macro + val db = h2db("jdbc:h2:coffees.h2.db") + + // expands into the following code + val db = { + trait Db { + case class Coffee(...) + val Coffees: Table[Coffee] = ... + } + new Db {} + } + +It is true that noone outside the macro expansion block would be able to refer to the `Coffee` class directly, +however if we inspect the type of `db`, we will find something fascinating. + + scala> val db = h2db("jdbc:h2:coffees.h2.db") + db: AnyRef { + type Coffee { val name: String; val price: Int; ... } + val Coffees: Table[this.Coffee] + } = $anon$1... + +As we can see, when the typechecker tried to infer a type for `db`, it took all the references to locally declared classes +and replaced them with structural types that contain all publicly visible members of those classes. The resulting type +captures the essence of the generated classes, providing a statically typed interface to their members. + + scala> db.Coffees.all + res1: List[Db$1.this.Coffee] = List(Coffee(Brazilian,99,0)) + +This approach to type providers is quite neat, because it can be used with production versions of Scala, however +it has performance problems caused by the fact that Scala emits reflective calls when compiling accesses to members +of structural types. There are several strategies of dealing with that, but this margin is too narrow to contain them +so I refer you to an amazing blog series by Travis Brown for details: [post 1](http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/), [post 2](http://meta.plasm.us/posts/2013/07/11/fake-type-providers-part-2/), [post 3](http://meta.plasm.us/posts/2013/07/12/vampire-methods-for-structural-types/). + +### Real type providers + +With the help of [macro paradise](/overviews/macros/paradise.html) and its [macro annotations](/overviews/macros/annotations.html), it becomes +possible to easily generate publicly visible classes, without having to apply workarounds based on structural types. The annotation-based +solution is very straightforward, so I won't be writing much about it here. + + class H2Db(connString: String) extends StaticAnnotation { + def macroTransform(annottees: Any*) = macro ... + } + + @H2Db("jdbc:h2:coffees.h2.db") object Db + println(Db.Coffees.all) + Db.Coffees.insert("Brazilian", 99, 0) + +### Addressing the erasure problem + +We haven't looked into this in much detail, but there's a hypothesis that a combination of type members +and singleton types can provide an equivalent of erased type providers in F#. Concretely, classes that we don't want to erase +should be declared as usual, whereas classes that should be erased to a given upper bound should be declared as type aliases +to that upper bound parameterized by a singleton type that carries unique identifiers. With that approach, every new generated type +would still incur the overhead of additional bytecode to the metadata of type aliases, but that bytecode would be significantly smaller +than bytecode of a full-fledged class. This technique applies to both fake and real type providers. + + object Netflix { + type Title = XmlEntity["http://.../Title".type] + def Titles: List[Title] = ... + type Director = XmlEntity["http://.../Director".type] + def Directors: List[Director] = ... + ... + } + + class XmlEntity[Url] extends Dynamic { + def selectDynamic(field: String) = macro XmlEntity.impl + } + + object XmlEntity { + def impl(c: Context)(field: c.Tree) = { + import c.universe._ + val TypeRef(_, _, tUrl) = c.prefix.tpe + val ConstantType(Constant(sUrl: String)) = tUrl + val schema = loadSchema(sUrl) + val Literal(Constant(sField: String)) = field + if (schema.contains(sField)) q"${c.prefix}($sField)" + else c.abort(s"value $sField is not a member of $sUrl") + } + } \ No newline at end of file diff --git a/overviews/macros/usecases.md b/overviews/macros/usecases.md index d76675c4d5..acb63e0411 100644 --- a/overviews/macros/usecases.md +++ b/overviews/macros/usecases.md @@ -6,7 +6,7 @@ disqus: true partof: macros num: 2 -outof: 10 +outof: 11 languages: [ja] ---