2015-04-23 20 views
5

Tôi đã có ý định thực hiện một toán tử so sánh xích cho Scala, nhưng sau một vài lần thử, tôi không nghĩ có cách nào để thực hiện nó. Đây là cách nó là nghĩa vụ phải làm việc:Scala - Cách biên dịch biểu thức "trì hoãn"

val a = 3 
1 < a < 5 //yields true 
3 < a < 5 //yields false 

Vấn đề là, trình biên dịch scala là khá tham lam khi đánh giá biểu thức, vì vậy các biểu thức trên được đánh giá như sau:

1 < a //yields true 
true < 5 //compilation error 

Tôi đã cố gắng để viết mã để thực hiện nó bằng cách nào đó và đây là những gì tôi đã cố gắng:

  • chuyển đổi ngầm từ loại Int để loại tôi RichComparisonInt - không giúp vì việc đánh giá cách hình trên,
  • lớp Overriding Int với lớp học của tôi - Không thể được thực hiện, bởi vì Int là cả abstractfinal,
  • tôi đã cố gắng tạo case class với tên <, giống như ::, nhưng sau đó tôi đã tìm hiểu , rằng lớp này được tạo chỉ để phù hợp với mẫu,
  • Tôi đã tạo chuyển đổi ngầm từ => Boolean, sẽ hoạt động ở cấp biên dịch nhưng không có cách nào để trích xuất tham số hoạt động, dẫn đến kết quả Boolean.

Có cách nào để thực hiện điều đó trong Scala không? Có lẽ các macro có thể đã thực hiện công việc?

+0

'lớp ngầm' cho 'Boolean' với phương thức' <(y: Int) ', là macro và kiểm tra' tiền tố' của nó, giải mã nó thành 'x.y (a)' và ở đó bạn đi. – sjrd

+1

@sjrd Tôi không nghĩ rằng đó là sẽ làm việc trong trường hợp chung vì gấp. –

+1

Việc xếp chỉ xảy ra nếu 'a' là' giá trị cuối cùng 'không có loại. Trong ví dụ trên, tôi có thể đảm bảo rằng trình biên dịch không gấp nó (ít nhất là không trong quá trình typer, trước khi macro khởi động). – sjrd

Trả lời

3

Đây là giải pháp sử dụng macros. Cách tiếp cận chung ở đây là làm phong phú thêm Boolean sao cho nó có phương thức macro xem xét prefix của ngữ cảnh để tìm so sánh được sử dụng để tạo ra Boolean đó.

Ví dụ, giả sử chúng ta có:

implicit class RichBooleanComparison(val x: Boolean) extends AnyVal { 
    def <(rightConstant: Int): Boolean = macro Compare.ltImpl 
} 

Và một định nghĩa macro với tiêu đề phương pháp:

def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean] 

Bây giờ giả sử rằng trình biên dịch được phân tích cú pháp biểu thức 1 < 2 < 3. Chúng tôi có thể sử dụng rõ ràng c.prefix để nhận biểu thức 1 < 2 trong khi đánh giá nội dung phương thức macro. Tuy nhiên, khái niệm về constant folding ngăn chúng tôi làm như vậy tại đây. Hằng số không đổi là quá trình mà trình biên dịch tính toán các hằng số được xác định trước tại thời gian biên dịch. Vì vậy, theo các macro thời gian đang được đánh giá, c.prefix đã được xếp thành chỉ true trong trường hợp này. Chúng tôi đã mất biểu thức 1 < 2 dẫn đến true. Bạn có thể đọc thêm về xếp liên tục và tương tác của chúng với macro Scala trên this issue và một chút trên this question.

Nếu chúng ta có thể giới hạn phạm vi của các cuộc thảo luận để chỉ biểu hiện dưới hình thức C1 < x < C2, nơi C1C2 là hằng số, và x là một biến, thì điều này trở nên khả thi, vì loại này biểu hiện sẽ không bị ảnh hưởng bởi liên tục gấp. Đây là một thực hiện:

object Compare { 
    def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean] = { 
    import c.universe._ 
    c.prefix.tree match { 
     case Apply(_, Apply(Select([email protected](Constant(_)), _), ([email protected](_, TermName(_))) :: Nil) :: Nil) => 
      val leftConstant = c.Expr[Int](lhs) 
      val variable = c.Expr[Int](x) 
      reify((leftConstant.splice < variable.splice) && (variable.splice < rightConstant.splice)) 

     case _ => c.abort(c.enclosingPosition, s"Invalid format. Must have format c1<x<c2, where c1 and c2 are constants, and x is variable.") 
    } 
    } 
} 

Ở đây, chúng tôi phù hợp với bối cảnh prefix với loại dự kiến, trích xuất các bộ phận liên quan (lhsx), xây dựng subtrees mới sử dụng c.Expr[Int], và xây dựng một cây đầy đủ biểu hiện mới sử dụng reifysplice để thực hiện so sánh 3 chiều mong muốn. Nếu không phù hợp với loại dự kiến, điều này sẽ không biên dịch được.

Điều này cho phép chúng ta làm:

val x = 5 
1 < x < 5 //true 
6 < x < 7 //false 
3 < x < 4 //false 

Như mong muốn!

docs about macros, treesthis presentation là tài nguyên tốt để tìm hiểu thêm về macro.

+1

Chà. Điều đó thật đáng chú ý. Cảm ơn bạn. Lời giải thích tuyệt vời. –

3

Bạn sẽ gặp sự cố khi đặt tên phương thức < mà không sử dụng macro, vì trình biên dịch sẽ luôn chọn phương thức < thực tế là trên Int, trái ngược với bất kỳ lớp được làm giàu nào. Nhưng nếu bạn có thể cho rằng, bạn có thể làm giàu Int như bạn đề nghị, và trả về một loại trung gian mà theo dõi những sự so sánh như vậy, cho đến nay:

implicit class RichIntComparison(val x: Int) extends AnyVal { 
    def <<<(y: Int) = Comparison(x < y, y) 
} 

case class Comparison(soFar: Boolean, y: Int) { 
    def <<<(z: Int) = soFar && (y < z) 
} 

Sau đó chúng ta có thể làm:

1 <<< 2 <<< 3 

//Equivalent to: 
val rc: Comparison = RichIntComparison(1).<<<(2) 
rc.<<<(3) 

Nếu bạn muốn, bạn cũng có thể thêm một chuyển đổi ngầm từ Comparison để Boolean để bạn có thể sử dụng <<< cho một nửa so sánh cũng như:

object Comparison { 
    implicit def comparisonToBoolean(c: Comparison): Boolean = c.soFar 
} 

Trong đó sẽ cho phép bạn làm:

val comp1: Boolean = 1 <<< 2 //true 
val comp2: Boolean = 1 <<< 2 <<< 3 //true 

Bây giờ khi bạn đã giới thiệu chuyển đổi này tiềm ẩn, bạn có thể quay trở lại và làm <<< trên Comparison trở Comparison thay vào đó, cho phép bạn làm chaining thậm chí mở rộng hơn:

case class Comparison(soFar: Boolean, y: Int) { 
    def <<<(z: Int): Comparison = Comparison(soFar && (y < z), z) 
    //You can also use < for everything after the first comparison: 
    def <(z: Int) = <<<(z) 
} 

//Now, we can chain: 
val x: Boolean = 1 <<< 2 <<< 3 <<< 4 <<< 3 //false 
val x: Boolean = 1 <<< 2 < 3 < 4 < 7 //true 
+0

Đây là một giải pháp tốt, nhưng nó là một giải pháp dễ dàng. Mục tiêu chính của câu hỏi của tôi là không cho phép so sánh như vậy. Nó đã được thêm về vấn đề đơn độc của trình biên dịch biểu thức và khai thác cấu trúc biểu thức. –

+1

@BartekAndrzejczak Ah tôi hiểu rồi. Tôi sẽ để nó ở đây trong trường hợp nó phù hợp với nhu cầu của người khác. Macros chắc chắn là cách để đi ở đây trong trường hợp của bạn, sau đó. Tôi sẽ xem xét nếu tôi có cơ hội sau này và không ai khác có được nó. –

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