2012-03-14 33 views
10

Giả sử rằng tôi muốn viết một lớp trường hợp Stepper như sau:lớp Trường hợp và tuyến tính của đặc điểm

case class Stepper(step: Int) {def apply(x: Int) = x + step} 

Nó đi kèm với một đẹp toString thực hiện:

scala> Stepper(42).toString 
res0: String = Stepper(42) 

nhưng nó không thực sự là một chức năng :

scala> Some(2) map Stepper(2) 
<console>:10: error: type mismatch; 
found : Stepper 
required: Int => ? 
       Some(2) map Stepper(2) 

Giải pháp thay thế là triển khai Function đặc điểm ...

case class Stepper(step: Int) extends (Int => Int) {def apply(x: Int) = x + step} 

Nhưng sau đó, tôi không thể có đối với việc thực hiện toString đẹp miễn phí nữa:

scala> Stepper(42).toString 
res2: java.lang.String = <function1> 

Sau đó, câu hỏi là: Tôi có thể có tốt nhất của hai thế giới ? Có một giải pháp mà tôi có thực hiện tốt đẹp toString miễn phí thực hiện đặc điểm Function. Nói cách khác, có cách nào để áp dụng tuyến tính theo cách mà cuối cùng là áp dụng đường cú pháp case class?

Trả lời

8

Câu hỏi này không thực sự liên quan đến tuyến tính. Trong trường hợp các lớp học toString là một phương pháp được tạo tự động bởi trình biên dịch nếu và chỉ khi Any.toString không được ghi đè trong loại kết thúc.

Tuy nhiên, câu trả lời là một phần để làm với linearisation - chúng ta cần phải ghi đè Function1.toString với phương pháp mà có thể đã được tạo ra bởi trình biên dịch nếu không muốn nói cho các phiên bản được giới thiệu bởi Function1:

trait ProperName extends Product { 
    override lazy val toString = scala.runtime.ScalaRunTime._toString(this) 
} 

// now just mix in ProperName and... magic! 
case class Stepper(step: Int) extends (Int => Int) with ProperName { 
    def apply(x:Int) = x+step 
} 

Sau đó

println(Some(2) map Stepper(2)) 
println(Stepper(2)) 

Sẽ tạo ra

Some(4) 
Stepper(2) 

Cập nhật

Đây là một phiên bản của ProperName đặc điểm mà không dựa vào phương pháp API không có giấy tờ:

trait ProperName extends Product { 
    override lazy val toString = { 
    val caseFields = { 
     val arity = productArity 
     def fields(from: Int): List[Any] = 
     if (from == arity) List() 
     else productElement(from) :: fields(from + 1) 
     fields(0) 
    } 
    caseFields.mkString(productPrefix + "(", ",", ")") 
    } 
} 

Alternative toString thực hiện có nguồn gốc từ mã nguồn cho bản gốc _toString phương pháp scala.runtime.ScalaRunTime._toString.

Xin lưu ý rằng triển khai thay thế này vẫn dựa trên giả định rằng một trường hợp luôn mở rộng đặc điểm Product. Mặc dù sau này đúng như Scala 2.9.0 và là một thực tế được biết đến và dựa vào bởi một số thành viên của cộng đồng Scala nó không chính thức được ghi nhận như là một phần của Scala Language Spec.

+0

Có, nó không thực sự tuyến tính, nhưng tôi không tìm thấy bất kỳ tên thích hợp nào khác cho nó. Và đó là loại mẹo tôi mong đợi, cảm ơn. – Nicolas

+0

@Nicolas Tôi hiểu bạn rất rõ, tôi thấy mình thường xuyên khó mô tả một vấn đề chính xác khi tôi không chắc điều gì đang diễn ra. –

+0

@Vlad: Tôi đã luôn tránh sử dụng mọi thứ từ 'scala.runtime' không hiển thị trong tài liệu API Scala. Tôi đồng ý rằng đây là một cách giải quyết thông minh, nhưng bạn có thực sự nghĩ rằng nó có giá trị nó, cho rằng có những giải pháp tốt như nhau mà sử dụng các tính năng ngôn ngữ Scala cũ đồng bằng? –

2

CHỈNH SỬA: Còn aboutString trọng hơn thì sao?

case class Stepper(step: Int) extends (Int => Int) { 
    def apply(x: Int) = x + step 
    override def toString = "Stepper(" + step + ")" 
} 
+0

Vâng, tôi biết điều đó. nhưng tôi thực sự không muốn thêm không áp dụng (tưởng tượng nó trong một DSL). – Nicolas

+0

Ồ, OK. Làm thế nào về điều này? –

+0

@TalPressman bạn quên mở rộng Function1 –

1

Bạn có thể sử dụng một chuyển đổi ngầm để có Stepper đối xử như một chức năng chỉ khi cần thiết:

case class Stepper(step: Int) { def apply(x: Int) = x + step } 

implicit def s2f(s: Stepper) = new Function[Int, Int] { 
    def apply(x: Int) = s.apply(x) 
} 

Bây giờ bạn sẽ có được lớp trường hợp của toString khi bạn gọi Stepper(42).toString, nhưng Some(2) map Stepper(2) cũng hoạt động như mong muốn.

(Lưu ý rằng tôi đã tiết lộ chi tiết hơn mức cần thiết ở trên để giữ cho cơ chế rõ ràng. Bạn cũng có thể viết implicit def s2f(s: Stepper) = s.apply _ hoặc bất kỳ số công thức súc tích nào khác).

+0

Có, nhưng việc xây dựng ngầm định này là đau đớn hơn viết lại toString;) – Nicolas

+0

@ Nicolas: Thật sao? Đó là 40 ký tự. Điều này dường như ít "đau đớn" hơn là sử dụng một bit không có giấy tờ của API thời gian chạy. –

+0

Nó không phải là một câu hỏi về số lượng ký tự, một câu hỏi của việc thêm một số mã "không khô" vào một lớp. Giải pháp 'ScalaRuntime' không hoàn hảo, rõ ràng là không sẵn sàng sản xuất nhưng đi theo hướng DRY. BTW, ban đầu tôi đã so sánh nó để viết lại toString (xem câu trả lời của Tal Pressman) – Nicolas

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