2012-12-10 16 views
12

Tôi đã làm việc với Scala Macros và có mã sau đây trong macro: Làm thế nào để sử dụng Loại được tính toán trong Scala Macro trong một mệnh đề sửa đổi?

val fieldMemberType = fieldMember.typeSignatureIn(objectType) match { 
     case NullaryMethodType(tpe) => tpe 
     case _      => doesntCompile(s"$propertyName isn't a field, it must be another thing") 
    } 

    reify{ 
     new TypeBuilder() { 
     type fieldType = fieldMemberType.type 
     } 
    } 

Như bạn thấy đấy, tôi đã quản lý để có được một c.universe.Type fieldMemberType. Điều này thể hiện loại trường nhất định trong đối tượng. Khi tôi nhận được điều đó, tôi muốn tạo một đối tượng TypeBuilder mới trong quá trình thống nhất. TypeBuilder là lớp trừu tượng có tham số trừu tượng. Thông số trừu tượng này là fieldType. Tôi muốn điều này fieldType là loại mà tôi đã tìm thấy trước đây.

Chạy mã được hiển thị ở đây trả về cho tôi một fieldMemberType not found. Có cách nào để tôi có thể nhận được fieldMemberType để làm việc bên trong mệnh đề quy định lại không?

Trả lời

24

Vấn đề là mã bạn chuyển đến reify về cơ bản sẽ được đặt đúng nguyên văn tại thời điểm macro đang được mở rộng và fieldMemberType sẽ không có ý nghĩa gì ở đó.

Trong một số trường hợp, bạn có thể sử dụng splice để ẩn một biểu thức mà bạn có thời gian mở rộng macro thành mã mà bạn đang xác minh lại. Ví dụ, nếu chúng ta đã cố gắng để tạo ra một thể hiện của đặc điểm này:

trait Foo { def i: Int } 

Và đã biến này lúc vĩ mô mở rộng:

val myInt = 10 

Chúng ta có thể viết như sau:

reify { new Foo { def i = c.literal(myInt).splice } } 

Điều đó sẽ không hoạt động ở đây, có nghĩa là bạn sẽ phải quên đi một chút đẹp reify và viết ra AST bằng tay. Bạn sẽ thấy điều này xảy ra rất nhiều, thật không may. tiếp cận tiêu chuẩn của tôi là để bắt đầu một REPL mới và gõ một cái gì đó như thế này:

import scala.reflect.runtime.universe._ 

trait TypeBuilder { type fieldType } 

showRaw(reify(new TypeBuilder { type fieldType = String })) 

này sẽ nhổ ra một vài dòng AST, sau đó bạn có thể cắt và dán vào định nghĩa macro của bạn như là một điểm khởi đầu. Sau đó, bạn fiddle với nó, thay thế cho những thứ như thế này:

Ident(TypeBuilder) 

Với điều này:

Ident(newTypeName("TypeBuilder")) 

FINAL với Flag.FINAL, và vân vân. Tôi muốn các phương thức toString cho các kiểu AST tương ứng chính xác hơn với mã cần thiết để xây dựng chúng, nhưng bạn sẽ nhanh chóng hiểu được những gì bạn cần thay đổi. Bạn sẽ kết thúc với một cái gì đó như thế này:

c.Expr(
    Block(
    ClassDef(
     Modifiers(Flag.FINAL), 
     anon, 
     Nil, 
     Template(
     Ident(newTypeName("TypeBuilder")) :: Nil, 
     emptyValDef, 
     List(
      constructor(c), 
      TypeDef(
      Modifiers(), 
      newTypeName("fieldType"), 
      Nil, 
      TypeTree(fieldMemberType) 
     ) 
     ) 
    ) 
    ), 
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil) 
) 
) 

đâu anon là một cái tên kiểu bạn đã tạo trước cho lớp ẩn danh của bạn, và constructor là một phương pháp thuận tiện tôi sử dụng để làm loại điều một chút ít ghê tởm hơn (bạn có thể tìm thấy định nghĩa của nó ở cuối this complete working example).

Bây giờ nếu chúng ta quấn biểu thức này trong một cái gì đó giống như this, chúng ta có thể viết như sau:

scala> TypeMemberExample.builderWithType[String] 
res0: TypeBuilder{type fieldType = String} = [email protected] 

Vì vậy, nó hoạt động.Chúng tôi đã chụp c.universe.Type (mà tôi nhận được ở đây từ WeakTypeTag của thông số loại trên builderWithType, nhưng nó sẽ hoạt động theo cách tương tự với bất kỳ số Type cũ nào) và sử dụng nó để xác định loại thành viên của đặc điểm TypeBuilder của chúng tôi.

+0

Chỉ một câu hỏi, cách "newTypeName" tìm thấy Loại? Tôi có nghĩa là làm thế nào nó biết trong đó gói này là? – mgonto

+0

Cảm ơn! Và 'newTypeName' không truy tìm chính nó - nó chỉ sao chép chuỗi mà bạn đưa nó vào mã mà macro đang sinh ra, sau đó nó sẽ được trình biên dịch kiểm tra kiểu như bình thường. Nếu bạn cần chỉ định gói bạn có thể viết, ví dụ: 'Select (Ident (" package "), newTypeName (" MyClass "))'. –

+3

Bạn có thể sử dụng '-Yreify-copypaste' và/hoặc' showRaw' để lấy mã xây dựng đoạn mã bạn quan tâm. –

6

Có cách tiếp cận đơn giản hơn là viết cây cho trường hợp sử dụng của bạn. Thật vậy, tôi luôn luôn sử dụng nó để giữ cây ở vịnh, vì nó có thể thực sự khó khăn khi lập trình với cây cối. Tôi thích tính toán các loại và sử dụng reify để tạo cây. Điều này làm cho các macro mạnh mẽ hơn và "vệ sinh" và ít lỗi thời gian biên dịch hơn. IMO sử dụng cây phải là phương sách cuối cùng, chỉ trong một vài trường hợp, chẳng hạn như biến đổi cây hoặc lập trình chung cho một nhóm các loại như bộ dữ liệu.

Mẹo ở đây là để xác định hàm lấy tham số kiểu, loại bạn muốn sử dụng trong thân xác minh, với ngữ cảnh được ràng buộc trên WeakTypeTag. Sau đó, bạn gọi hàm này bằng cách truyền một cách rõ ràng các WeakTypeTags mà bạn có thể xây dựng từ các kiểu vũ trụ nhờ vào phương thức WeakTypeTag bối cảnh.

Vì vậy, trong trường hợp của bạn, điều đó sẽ cung cấp những điều sau đây.

val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match { 
     case NullaryMethodType(tpe) => tpe 
     case _      => doesntCompile(s"$propertyName isn't a field, it must be   another thing") 
    } 

    def genRes[T: WeakTypeTag] = reify{ 
    new TypeBuilder() { 
     type fieldType = T 
    } 
    } 

    genRes(c.WeakTypeTag(fieldMemberType)) 
+0

Đây là một cách tiếp cận sạch hơn nhiều. Cảm ơn – mgonto

+0

+1 (và tôi đã nhận thấy rằng cách tiếp cận này hoạt động ở đây), nhưng trong kinh nghiệm của tôi có rất nhiều trường hợp (có thể nhất) khi làm việc với cây trực tiếp là không thể tránh khỏi - đặc biệt bất cứ lúc nào bạn cần tham khảo (hoặc tạo) các phương thức hoặc các lớp theo tên của chúng dưới dạng các chuỗi. Ví dụ: [tại đây] (https://github.com/travisbrown/rillit), [tại đây] (http://stackoverflow.com/a/13335741/334519), [tại đây] (http://stackoverflow.com/a/13447439/334519) v.v. –

Các vấn đề liên quan