최근 스칼라의 매크로에 대한 읽기를 시작하여 흥미로운 연습 문제를 발견했습니다. val
및 var
값에 대한 포인터 만 구현했지만 코드의 스케치를 요구하기 때문에 지금까지 찾은 것을 공유 할 수 있다고 생각했습니다.
편집 : : 메소드에 대한 포인터 : 뭔가 오해하지 않는다면 이미 스칼라의 기능입니다. 당신은 코드에만 관심이 있다면 class Foo{ def m(i: Int): Unit = println(i)}
주어, 당신은 val f: Int => Unit = new Foo().m _
- 같은 기능을 얻을 대답의 하단으로 스크롤합니다.
매크로는 컴파일 할 때 실행되므로 미리 컴파일해야합니다. IntelliJ (또는 Eclipse)를 사용하는 경우 모든 매크로 관련 코드를 별도의 프로젝트에 넣는 것이 좋습니다.
당신이 언급 한 바와 같이
, 우리는 두 개의 포인터 특성이
trait ValRef[T] {
def get(): T
}
trait VarRef[T] extends ValRef[T] {
def set(x: T): Unit
}
이제 우리는 주어진 참조, 즉, name
또는 qualifier.name
이하는 ValRef
반환하는 방법 &
을 구현하고자합니다. 참조가 변경 가능한 값을 참조하면 결과는 VarRef
이어야합니다.
def &[T](value: T): ValRef[T]
지금까지는 일반적인 스칼라 코드입니다. 메서드 &
은 모든 식을 받아들이고 인수와 같은 형식의 ValRef
을 반환합니다.
def pointer[T: c.WeakTypeTag](c: scala.reflect.macros.blackbox.Context)(value: c.Expr[T]) = ???
이 서명은 대부분 표준으로한다 :
우리가 &
의 논리를 구현하는 매크로를 정의 할 수 있습니다 - Context
-
c
은을 사용하는 컴파일러에 의해 수집 된 정보를 포함 매크로.
T
는
&
value
&
의 인수에 해당 전달되는 식의 형태이지만, 구현은 스칼라의 AST에서 작동하기 때문에, 그것은 유형 Expr[T]
이며 원본이 아닌 유형의 T
약간 특별한 것은 WeakTypeTag
의 사용입니다. 나는 완전히 자신을 이해하지 못합니다.documentation 상태 :
유형 매개 변수는 WeakTypeTag 컨텍스트 경계와 함께 제공 될 수 있습니다. 이 경우 응용 프로그램 사이트에서 인스턴스화 된 실제 유형 인수를 설명하는 해당 유형 태그는 매크로가 확장 될 때 함께 전달됩니다.
삽입 부분은 pointer
의 구현입니다. 메소드 &
이 호출 될 때마다 메소드의 결과는 컴파일러에 의해 사용되기 때문에, AST을 리턴해야합니다. 그러므로 우리는 나무를 만들고 싶습니다. 문제는 트리가 어떻게 생겼을 까?
Scala 2.11 이후 Quasiquotes이라는 것이 있습니다. Quasiquotes는 문자열 값에서 트리를 만드는 데 도움이 될 수 있습니다.
우선 문제를 단순화합시다. val
과 var
개의 참조를 구분하는 대신 항상 VarRef
을 반환합니다. 반환해야 x.y
get()
에 의해 생성 된 VarRef
를 들어 x.y
set(x)
x.y = x
그래서 우리는 VarRef[T]
의 익명 서브 클래스의 인스턴스를 나타내는 트리를 생성 할 실행해야합니다. 다음과 같이 우리가 Quasiquote에 직접 제네릭 형식 T
를 사용할 수 없기 때문에, 우리가 처음 우리가 지금
val tpe = value.tree.tpe
에 의해 얻을 수있는 유형의 트리 표현이 필요, 우리의 Quasiquote 보인다 :
q"""
new VarRef[$tpe] {
def get(): $tpe = $value
def set(x: $tpe): Unit = {
$value = x
}
}
"""
이 구현 var
참조에 대한 포인터 만 작성하는 한 계속 작업해야합니다. 그러나 val
참조에 대한 포인터를 만들 자마자 "val에 대한 재 할당"때문에 컴파일이 실패합니다. 따라서 우리 매크로는이 둘을 구별해야합니다.
분명히 Symbols은 이러한 종류의 정보를 제공합니다. 포인터는 참고 용으로 만 만들어지며, 이는 TermSymbol
을 제공해야합니다.
val symbol: TermSymbol = value.tree.symbol.asTerm
는 이제 TermSymbol
API는 방법 isVal
및 isVar
으로 우리를 제공하지만, 그들은 로컬 변수 일 것으로 보인다. 나는 "올바른 방법"참조가, var
또는 val
가 있는지 여부를 발견 모르겠어요하지만 다음은 작동하는 것 같다 :
if(symbol.isVar || symbol.setter != NoSymbol) {
자격을 갖춘 이름의 상징이 setter
기호를 제공하는 것이다 트릭을 iff은 var
입니다. 그렇지 않은 경우 setter
은 NoSymbol
을 반환합니다.
case class Foo() {
val v = "whatever"
var u = 100
}
object Example{
import PointerMacro.&
def main(args: Array[String]): Unit = {
val x = new Foo
val mainInt = 90
var mainString = "this is main"
val localValPointer: ValRef[Int] = &(mainInt)
val localVarPointer: VarRef[String] = &(mainString).asInstanceOf[VarRef[String]]
val memberValPointer: ValRef[String] = &(x.v)
val memberVarPointer: VarRef[Int] = &(x.u).asInstanceOf[VarRef[Int]]
println(localValPointer.get())
println(localVarPointer.get())
println(memberValPointer.get())
println(memberVarPointer.get())
localVarPointer.set("Hello World")
println(localVarPointer.get())
memberVarPointer.set(62)
println(memberVarPointer.get())
}
}
:이 코드를 컴파일하고 프로젝트의 클래스 패스에 추가하는 경우
trait ValRef[T] {
def get(): T
}
trait VarRef[T] extends ValRef[T] {
def set(x: T): Unit
}
object PointerMacro {
import scala.language.experimental.macros
def pointer[T: c.WeakTypeTag](c: scala.reflect.macros.blackbox.Context)(value: c.Expr[T]) = {
import c.universe._
val symbol: TermSymbol = value.tree.symbol.asTerm
val tpe = value.tree.tpe
if(symbol.isVar || symbol.setter != NoSymbol) {
q"""
new VarRef[$tpe] {
def get(): $tpe = $value
def set(x: $tpe): Unit = {
$value = x
}
}
"""
} else {
q"""
new ValRef[$tpe] {
def get(): $tpe = $value
}
"""
}
}
def &[T](value: T): ValRef[T] = macro pointer[T]
}
는, 당신은 다음과 같이 포인터를 만들 수 있어야 다음과 같이
그래서 매크로 코드가 보인다
하는 실행할 때, 인쇄해야
90
this is main
whatever
100
Hello World
62
그것은 성명이었고 질문이 아니 었습니다. 가능한지 여부는 질문 이었습니까? – johanandren
비슷한 코드 또는 스케치가 제공됩니다. 고마워. –