2012-06-14 710 views
8

Tôi đang phát triển một DSL và tôi nhận được lỗi "miễn phí" trong khi mở rộng macro. Tôi muốn biết nếu nó có thể tránh được. Tôi đã đơn giản hóa vấn đề với tình huống sau.Có thể tránh được lỗi biến tự do này (được tạo ra ở việc mở rộng macro) không?

Giả sử chúng ta có biểu thức này:

val list = join { 
    0 
    1 
    2 
    3 
} 
println(list) 

nơi tham gia là một vĩ mô mà thực hiện là:

def join(c: Ctx)(a: c.Expr[Int]): c.Expr[List[Int]] = { 
    import c.mirror._ 
    a.tree match { 
    case Block(list, ret) => 
     // c.reify(List(new c.Expr(list(0)).eval, 
     //    new c.Expr(list(1)).eval, 
     //    new c.Expr(list(2)).eval) :+ new c.Expr(ret).eval) 
     c.reify((for (expr <- list) yield new c.Expr(expr).eval) :+ new c.Expr(ret).eval) 
    } 
} 

Mục đích của vĩ mô là để tham gia tất cả các yếu tố trong khối đối số và lợi nhuận chúng trong một danh sách duy nhất. Vì nội dung của khối có thể thay đổi, tôi không thể sử dụng tính năng sửa đổi nhận xét (hoạt động tốt). Một trong những uncommented một với một cho hiểu, mà tạo ra các điều khoản miễn phí-ném thông điệp:

"Macro mở rộng chứa danh sách biến hạn miễn phí được xác định bởi tham gia trong Macros.scala: 48: 18. Bạn đã quên sử dụng eval khi Nếu bạn gặp khó khăn trong việc theo dõi biến số miễn phí, hãy cân nhắc sử dụng -Xlog-free-terms "

Có cách nào để giới thiệu hiểu (hoặc một trình lặp hay bất kỳ thứ gì) mà không nhận được điều này lỗi? Nhân tiện, tôi đang sử dụng 2.10-M3.

Trả lời

15

Vấn đề là mã của bạn trộn các khái niệm biên dịch và thời gian chạy.

Biến "danh sách" bạn đang sử dụng là giá trị thời gian biên dịch (nghĩa là nó được lặp lại trong thời gian biên dịch) và bạn yêu cầu sửa lại để giữ lại cho đến khi chạy (bằng cách ghép nối bắt nguồn từ giá trị). Câu hỏi hóc búa xuyên suốt giai đoạn này dẫn đến việc tạo ra một thuật ngữ được gọi là miễn phí.

Trong ngắn hạn, các thuật ngữ miễn phí là các nhánh đề cập đến các giá trị từ các giai đoạn trước đó. Ví dụ, đoạn mã sau:

val x = 2 
reify(x) 

sẽ được biên dịch như sau:

val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x); 
Ident(free$x1) 

Clever, huh? Kết quả duy trì thực tế là x là một Ident, giữ nguyên kiểu của nó (các đặc tính biên dịch), nhưng, tuy nhiên, cũng đề cập đến giá trị của nó (một đặc tính thời gian chạy). Điều này được thực hiện bằng phạm vi từ vựng.

Nhưng nếu bạn cố gắng trả lại cây này từ việc mở rộng macro (được đưa vào trang web cuộc gọi của macro), mọi thứ sẽ phát nổ. Trang web cuộc gọi của macro sẽ rất có thể không có x trong phạm vi từ vựng của nó, do đó, nó sẽ không thể tham chiếu đến giá trị của x.

Còn gì tệ hơn nữa. Nếu đoạn mã ở trên được viết bên trong macro, thì x chỉ tồn tại trong thời gian biên dịch, tức là trong JVM chạy trình biên dịch. Nhưng khi trình biên dịch chấm dứt, nó đã biến mất.

Tuy nhiên, kết quả mở rộng macro chứa tham chiếu đến x được cho là chạy trong thời gian chạy (rất có thể, trong một JVM khác). Để hiểu được điều này, bạn sẽ cần sự kiên trì giữa các giai đoạn, tức là khả năng bằng cách nào đó sắp xếp theo thứ tự các giá trị biên dịch thời gian tùy ý và deserialize chúng trong thời gian chạy. Tôi không biết làm thế nào để làm điều này trong một ngôn ngữ biên soạn như Scala.


Lưu ý rằng trong một số trường hợp, sự kiên trì giữa các giai đoạn là có thể.Ví dụ, nếu x là một lĩnh vực của một đối tượng tĩnh:

object Foo { val x = 2 } 
import Foo._ 
reify(x) 

Sau đó, nó sẽ không kết thúc như một thuật ngữ miễn phí, nhưng sẽ được reified một cách đơn giản:

Select(Ident(staticModule("Foo")), newTermName("x")) 

Đây là một khái niệm thú vị cũng đã được thảo luận trong buổi nói chuyện của SPJ tại Scala Days 2012: http://skillsmatter.com/podcast/scala/haskell-cloud.

Để xác minh rằng một số cụm từ không chứa cụm từ miễn phí, trong Haskell, chúng thêm một nguyên mẫu dựng sẵn mới vào trình biên dịch, hàm tạo kiểu Static. Với các macro, chúng ta có thể thực hiện điều này một cách tự nhiên bằng cách sử dụng reify (bản thân nó chỉ là một macro). Xem thảo luận tại đây: https://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA.


Bây giờ chúng ta đã thấy chính xác vấn đề với mã ban đầu là gì, vậy làm cách nào để chúng tôi hoạt động?

Thật không may, chúng tôi sẽ phải quay trở lại xây dựng AST thủ công, bởi vì reify có thời gian khó khăn thể hiện cây năng động. Các trường hợp sử dụng lý tưởng cho reify trong macrology là có một mẫu tĩnh với các loại lỗ được biết đến tại thời gian biên dịch vĩ mô. Đi một bước sang một bên - và bạn sẽ phải nghỉ mát để xây dựng cây bằng tay.

Điểm mấu chốt là bạn phải đi như sau (chỉ hoạt động với thời gian gần đây phát hành 2.10.0-M4, xem hướng dẫn chuyển đổi tại scala-ngôn ngữ để xem chính xác những gì đã thay đổi: http://groups.google.com/group/scala-language/browse_thread/thread/bf079865ad42249c):

import scala.reflect.makro.Context 

object Macros { 
    def join_impl(c: Context)(a: c.Expr[Int]): c.Expr[List[Int]] = { 
    import c.universe._ 
    import definitions._ 
    a.tree match { 
     case Block(list, ret) => 
     c.Expr((list :+ ret).foldRight(Ident(NilModule): Tree)((el, acc) => 
      Apply(Select(acc, newTermName("$colon$colon")), List(el)))) 
    } 
    } 

    def join(a: Int): List[Int] = macro join_impl 
} 
+0

Sẽ có một cái gì đó dễ dàng hơn trong scala 2.11? Có phải dấu ngoặc kép sẽ giúp ích? (Tôi đã không nghiên cứu macro ở sâu, nhưng bất cứ khi nào tôi cố gắng tôi tiếp tục đụng vào điều này) – HRJ

+0

Quasiquotes sẽ giúp một chút: https://gist.github.com/densh/6209261 –