Skip to content

Commit 55f8c46

Browse files
committed
Merge pull request #28 from enshahar/translate-ko-macro
매크로 overview.md 번역
2 parents a6b920d + cd9014c commit 55f8c46

File tree

16 files changed

+968
-8
lines changed

16 files changed

+968
-8
lines changed

‎ko/overviews/core/macros.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
layout: overview
3+
title: 매크로
4+
disqus: true
5+
partof: macros
6+
overview: macros
7+
language: ko
8+
label-color: important
9+
label-text: Experimental
10+
---

‎ko/overviews/macros/bundles.md‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
layout: overview-large
3+
title: Macro Bundles
4+
5+
disqus: true
6+
7+
partof: macros
8+
num: 6
9+
outof: 7
10+
language: ko
11+
---
12+
<spanclass="label important"style="float: right;">MACRO PARADISE</span>
13+
14+
**Eugene Burmako**
15+
16+
Macro bundles and macro compilers are pre-release features included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds.
17+
18+
## Macro bundles
19+
20+
Currently, in Scala 2.10.0, macro implementations are represented with functions. Once the compiler sees an application of a macro definition,
21+
it calls the macro implementation - as simple as that. However practice shows that just functions are often not enough due to the
22+
following reasons:
23+
24+
1. Being limited to functions makes modularizing complex macros awkward. It's quite typical to see macro logic concentrate in helper
25+
traits outside macro implementations, turning implementations into trivial wrappers, which just instantiate and call helpers.
26+
27+
2. Moreover, since macro parameters are path-dependent on the macro context, [special incantations](/overviews/macros/overview.html#writing_bigger_macros) are required to wire implementations and helpers together.
28+
29+
3. As macros evolved it [became apparent](https://twitter.com/milessabin/status/281379835773857792) that there should exist different
30+
interfaces of communication between the compiler and macros. At the moment compiler can only expand macros, but what if it wanted to
31+
ask a macro to help it with type inference?
32+
33+
Macro bundles provide a solution to these problems by allowing macro implementations to be declared in traits, which extend
34+
`scala.reflect.macros.Macro`. This base trait predefines the `c: Context` variable, relieving macro implementations from having
35+
to declare it in their signatures, which simplifies modularization. Later on `Macro` could come with preloaded callback methods
36+
such as, for example, `onInfer`.
37+
38+
trait Macro{
39+
val c: Context
40+
}
41+
42+
Referencing macro implementations defined in bundles works in the same way as with impls defined in objects. You specify a bundle name
43+
and then select a method from it, providing type arguments if necessary.
44+
45+
import scala.reflect.macros.Context
46+
import scala.reflect.macros.Macro
47+
48+
trait Impl extends Macro{
49+
def mono = c.literalUnit
50+
def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString)
51+
}
52+
53+
object Macros{
54+
def mono = macro Impl.mono
55+
def poly[T] = macro Impl.poly[T]
56+
}
57+
58+
## Macro compilers (deprecated)
59+
60+
It turns out that the flexibility provided by externalizing the strategy of macro compilation hasn't really been useful.
61+
Therefore I'm removing this functionality from macro paradise <spanclass="label success">NEW</span>.
62+
63+
When I was implementing macro bundles, it became apparent that the mechanism which links macro definitions with macro implementations
64+
is too rigid. This mechanism simply used hardcoded logic in `scala/tools/nsc/typechecker/Macros.scala`, which takes the right-hand side
65+
of a macro def, typechecks it as a reference to a static method and then uses that method as a corresponding macro implementation.
66+
67+
Now compilation of macro defs is extensible. Instead of using a hardcoded implementation to look up macro impls,
68+
the macro engine performs an implicit search of a `MacroCompiler` in scope and then invokes its `resolveMacroImpl` method,
69+
passing it the `DefDef` of a macro def and expecting a reference to a static method in return. Of course, `resolveMacroImpl`
70+
should itself be a macro, namely [an untyped one](/overviews/macros/untypedmacros.html), for this to work.
71+
72+
trait MacroCompiler{
73+
def resolveMacroImpl(macroDef: _): _ = macro ???
74+
}
75+
76+
Default instance of the type class, `Predef.DefaultMacroCompiler`, implements formerly hardcoded typechecking logic.
77+
Alternative implementations could, for instance, provide lightweight syntax for macro defs, generating macro impls
78+
on-the-fly using `c.introduceTopLevel`.

‎ko/overviews/macros/inference.md‎

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
---
2+
layout: overview-large
3+
title: Inference-Driving Macros
4+
5+
disqus: true
6+
7+
partof: macros
8+
num: 7
9+
outof: 7
10+
language: ko
11+
---
12+
<spanclass="label important"style="float: right;">MACRO PARADISE</span>
13+
14+
**Eugene Burmako**
15+
16+
Inference-driving macros are pre-release features included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds.
17+
18+
## A motivating example
19+
20+
The use case, which gave birth to inference-driving macros, is provided by Miles Sabin and his [shapeless](https://github.com/milessabin/shapeless) library. Miles has defined the `Iso` trait, which represents isomorphisms between types.
21+
22+
trait Iso[T, U]{
23+
def to(t : T) : U
24+
def from(u : U) : T
25+
}
26+
27+
Currently instances of `Iso` are defined manually and then published as implicit values. Methods, which want to make use of
28+
defined isomorphisms, declare implicit parameters of type `Iso`, which then get filled in during implicit search.
29+
30+
def foo[C](c: C)(implicit iso: Iso[C, L]): L = iso.from(c)
31+
32+
case class Foo(i: Int, s: String, b: Boolean)
33+
implicit val fooIsoTuple = Iso.tuple(Foo.apply _, Foo.unapply _)
34+
35+
val tp = foo(Foo(23, "foo", true))
36+
tp : (Int, String, Boolean)
37+
tp == (23, "foo", true)
38+
39+
As we can see, the isomorphism between a case class and a tuple is trivial (actually, shapeless uses Iso's to convert between case
40+
classes and HLists, but for simplicity let's use tuples). The compiler already generates the necessary methods,
41+
and we just have to make use of them. Unfortunately in Scala 2.10.0 it's impossible to simplify this even further - for every case class
42+
you have manually define an implicit `Iso` instance.
43+
44+
The real showstopper is the fact that when typechecking applications of methods like `foo`, scalac has to infer the type argument `L`,
45+
which it has no clue about (and that's no wonder, since this is domain-specific knowledge). As a result, even if you define an implicit
46+
macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothing` before expanding the macro and then everything crumbles.
47+
48+
## The proposed solution
49+
50+
As demonstrated by [https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499), the solution to the outlined
51+
problem is extremely simple and elegant. <spanclass="label success">NEW</span>
52+
53+
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.
54+
The typechecker can infer as much as it possibly can (e.g. in the running example `C` will be inferred to `Foo` and
55+
`L` will remain uninferred) and then stop. After that we expand the macro and then proceed with type inference using the type of the
56+
expansion to help the typechecker with previously undetermined type arguments.
57+
58+
An illustration of this technique in action can be found in our [files/run/t5923c](https://github.com/scalamacros/kepler/tree/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923c) tests.
59+
Note how simple everything is. The `materializeIso` implicit macro just takes its first type argument and uses it to produce an expansion.
60+
We don't need to make sense of the second type argument (which isn't inferred yet), we don't need to interact with type inference -
61+
everything happens automatically.
62+
63+
Please note that there is [a funny caveat](https://github.com/scalamacros/kepler/blob/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923a/Macros_1.scala)
64+
with Nothings that we plan to address later.
65+
66+
## Internals of type inference (deprecated)
67+
68+
From what I learned about this over a few days, type inference in Scala is performed by the following two methods
69+
in `scala/tools/nsc/typechecker/Infer.scala`: [`inferExprInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1123) and
70+
[`inferMethodInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1173).
71+
So far I have nothing to say here other than showing `-Yinfer-debug` logs of various code snippets, which involve type inference.
72+
73+
def foo[T1](x: T1) = ???
74+
foo(2)
75+
76+
[solve types] solving for T1 in ?T1
77+
[infer method] solving for T1 in (x: T1)Nothing based on (Int)Nothing (solved: T1=Int)
78+
79+
def bar[T2] = ???
80+
bar
81+
82+
[solve types] solving for T2 in ?T2
83+
inferExprInstance{
84+
tree C.this.bar[T2]
85+
tree.tpe Nothing
86+
tparams type T2
87+
pt ?
88+
targs Nothing
89+
tvars =?Nothing
90+
}
91+
92+
class Baz[T]
93+
implicit val ibaz = new Baz[Int]
94+
def baz[T3](implicit ibaz: Baz[T3]) = ???
95+
baz
96+
97+
[solve types] solving for T3 in ?T3
98+
inferExprInstance{
99+
tree C.this.baz[T3]
100+
tree.tpe (implicit ibaz: C.this.Baz[T3])Nothing
101+
tparams type T3
102+
pt ?
103+
targs Nothing
104+
tvars =?Nothing
105+
}
106+
inferExprInstance/AdjustedTypeArgs{
107+
okParams
108+
okArgs
109+
leftUndet type T3
110+
}
111+
[infer implicit] C.this.baz[T3] with pt=C.this.Baz[T3] in class C
112+
[search] C.this.baz[T3] with pt=C.this.Baz[T3] in class C, eligible:
113+
ibaz: => C.this.Baz[Int]
114+
[search] considering T3 (pt contains ?T3) trying C.this.Baz[Int] against pt=C.this.Baz[T3]
115+
[solve types] solving for T3 in ?T3
116+
[success] found SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int))) for pt C.this.Baz[=?Int]
117+
[infer implicit] inferred SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int)))
118+
119+
class Qwe[T]
120+
implicit def idef[T4] = new Qwe[T4]
121+
def qwe[T4](implicit xs: Qwe[T4]) = ???
122+
qwe
123+
124+
[solve types] solving for T4 in ?T4
125+
inferExprInstance{
126+
tree C.this.qwe[T4]
127+
tree.tpe (implicit xs: C.this.Qwe[T4])Nothing
128+
tparams type T4
129+
pt ?
130+
targs Nothing
131+
tvars =?Nothing
132+
}
133+
inferExprInstance/AdjustedTypeArgs{
134+
okParams
135+
okArgs
136+
leftUndet type T4
137+
}
138+
[infer implicit] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C
139+
[search] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C, eligible:
140+
idef: [T4]=> C.this.Qwe[T4]
141+
[solve types] solving for T4 in ?T4
142+
inferExprInstance{
143+
tree C.this.idef[T4]
144+
tree.tpe C.this.Qwe[T4]
145+
tparams type T4
146+
pt C.this.Qwe[?]
147+
targs Nothing
148+
tvars =?Nothing
149+
}
150+
[search] considering T4 (pt contains ?T4) trying C.this.Qwe[Nothing] against pt=C.this.Qwe[T4]
151+
[solve types] solving for T4 in ?T4
152+
[success] found SearchResult(C.this.idef[Nothing], ) for pt C.this.Qwe[=?Nothing]
153+
[infer implicit] inferred SearchResult(C.this.idef[Nothing], )
154+
[solve types] solving for T4 in ?T4
155+
[infer method] solving for T4 in (implicit xs: C.this.Qwe[T4])Nothing based on (C.this.Qwe[Nothing])Nothing (solved: T4=Nothing)
156+
157+
## Previously proposed solution (deprecated)
158+
159+
It turns out that it's unnecessary to introduce a low-level hack in the type inference mechanism.
160+
As outlined above, there is a much more elegant and powerful solution <spanclass="label success">NEW</span>.
161+
162+
Using the infrastructure provided by [macro bundles](/overviews/macros/bundles.html) (in principle, we could achieve exactly the same
163+
thing using the traditional way of defining macro implementations, but that's not important here), we introduce the `onInfer` callback,
164+
which macros can define to be called by the compiler from `inferExprInstance` and `inferMethodInstance`. The callback takes a single
165+
parameter of type `c.TypeInferenceContext`, which encapsulates the arguments of `inferXXX` methods and provides methods to infer
166+
unknown type parameters.
167+
168+
trait Macro{
169+
val c: Context
170+
def onInfer(tc: c.TypeInferenceContext): Unit = tc.inferDefault()
171+
}
172+
173+
type TypeInferenceContext <: TypeInferenceContextApi
174+
trait TypeInferenceContextApi{
175+
def tree: Tree
176+
def unknowns: List[Symbol]
177+
def expectedType: Type
178+
def actualType: Type
179+
180+
// TODO: can we get rid of this couple?
181+
def keepNothings: Boolean
182+
def useWeaklyCompatible: Boolean
183+
184+
def infer(sym: Symbol, tpe: Type): Unit
185+
186+
// TODO: would be lovely to have a different signature here, namely:
187+
// def inferDefault(sym: Symbol): Type
188+
// so that the macro can partially rely on out-of-the-box inference
189+
// and infer the rest afterwards
190+
def inferDefault(): Unit
191+
}
192+
193+
With this infrastructure in place, we can write the `materializeIso` macro, which obviates the need for manual declaration of implicits.
194+
The full source code is available in [paradise/macros](https://github.com/scalamacros/kepler/blob/paradise/macros/test/files/run/macro-programmable-type-inference/Impls_Macros_1.scala), here's the relevant excerpt:
195+
196+
override def onInfer(tic: c.TypeInferenceContext): Unit ={
197+
val C = tic.unknowns(0)
198+
val L = tic.unknowns(1)
199+
import c.universe._
200+
import definitions._
201+
val TypeRef(_, _, caseClassTpe :: _ :: Nil) = tic.expectedType // Iso[Test.Foo,?]
202+
tic.infer(C, caseClassTpe)
203+
val fields = caseClassTpe.typeSymbol.typeSignature.declarations.toList.collect{case x: TermSymbol if x.isVal && x.isCaseAccessor => x }
204+
val core = (TupleClass(fields.length) orElse UnitClass).asType.toType
205+
val tequiv = if (fields.length == 0) core else appliedType(core, fields map (_.typeSignature))
206+
tic.infer(L, tequiv)
207+
}

0 commit comments

Comments
(0)