2014-11-05 14 views
22

Tôi muốn thực hiện một danh sách vô hạn:Trường hợp Scala cấm các tham số gọi theo tên?

abstract class MyList[+T] 
case object MyNil extends MyList[Nothing] 
case class MyNode[T](h:T,t: => MyList[T]) extends MyList[T] 

//error: `val' parameters may not be call-by-name 

vấn đề là call-by-name không được phép.

Tôi nghe nói rằng đó là do tham số val hoặc var hàm tạo không được phép cho call-by-name. Ví dụ:

class A(val x: =>Int) 
//error: `val' parameters may not be call-by-name 

Nhưng mâu thuẫn là các tham số nhà xây dựng bình thường vẫn là val, mặc dù private. Ví dụ:

class A(x: =>Int) 
// pass 

Vậy câu hỏi:

  • là vấn đề thực sự về val hoặc var?
    • Nếu có. Vì điểm cho từng cuộc gọi là trì hoãn tính toán, tại sao không thể hoãn lại val hoặc var tính toán (hoặc khởi tạo)?
  • Làm cách nào để đi quanh lớp học cass để triển khai danh sách vô hạn?
+4

Đối với cấu trúc dữ liệu vô hạn, đường trường hợp cung cấp giá trị bao nhiêu? 'equals',' hashCode', 'toString' sẽ không hoạt động. Và tôi không chắc những gì tôi mong đợi từ 'unapply'. –

Trả lời

15

Không có mâu thuẫn: class A(x: => Int) tương đương với class A(private[this] val x: => Int) và không class A(private val x: => Int). private[this] đánh dấu một cá thể có giá trị riêng tư, trong khi một trình sửa đổi riêng mà không có đặc tả kỹ hơn cho phép truy cập vào giá trị từ bất kỳ cá thể nào của lớp đó.

Thật không may, việc xác định case class A(private[this] val x: => Int) cũng không được phép. Tôi cho rằng đó là vì các kiểu chữ thường cần truy cập vào các giá trị của các hàm tạo khác của các cá thể khác, vì chúng thực hiện phương thức equals.

Tuy nhiên, bạn có thể thực hiện các tính năng mà một lớp trường hợp sẽ cung cấp bằng tay:

abstract class MyList[+T] 

class MyNode[T](val h: T, t: => MyList[T]) extends MyList[T]{ 

    def getT = t // we need to be able to access t 

    /* EDIT: Actually, this will also lead to an infinite recursion 
    override def equals(other: Any): Boolean = other match{ 
    case MyNode(i, y) if (getT == y) && (h == i) => true 
    case _ => false 
    }*/ 

    override def hashCode = h.hashCode 

    override def toString = "MyNode[" + h + "]" 

} 

object MyNode { 
    def apply[T](h: T, t: => MyList[T]) = new MyNode(h, t) 
    def unapply[T](n: MyNode[T]) = Some(n.h -> n.getT) 
} 

Để kiểm tra mã này, bạn có thể thử:

def main(args: Array[String]): Unit = { 
    lazy val first: MyNode[String] = MyNode("hello", second) 
    lazy val second: MyNode[String] = MyNode("world", first) 
    println(first) 
    println(second) 
    first match { 
    case MyNode("hello", s) => println("the second node is " + s) 
    case _ => println("false") 
    } 
} 

Thật không may, tôi không biết chắc chắn tại sao các thành viên val và var gọi theo tên đều bị cấm. Tuy nhiên, có ít nhất một mối nguy hiểm cho nó: Hãy suy nghĩ về cách các trường hợp lớp học thực hiện toString; Giá trị toString của mọi giá trị hàm tạo được gọi. Điều này có thể (và trong ví dụ này sẽ) dẫn đến các giá trị tự gọi là vô hạn. Bạn có thể kiểm tra điều này bằng cách thêm t.toString đến 's toString -method.

Edit: Sau khi đọc comment Chris Martin: Việc thực hiện equals cũng sẽ đặt ra một vấn đề mà có lẽ là nghiêm trọng hơn việc thực hiện các toString (mà chủ yếu được sử dụng để gỡ lỗi) và hashCode (mà sẽ chỉ dẫn đến cao tỷ lệ va chạm nếu bạn không thể đưa thông số vào tài khoản). Bạn phải suy nghĩ cẩn thận về cách bạn sẽ triển khai equals để có ý nghĩa.

+2

Tôi nghĩ rằng bạn chỉ để lại 'bằng' theo cách của nó, với báo trước rằng nó sẽ chỉ làm việc để chấm dứt danh sách. Đó là cách 'Stream' hoạt động, đúng không? 'Stream (1) == Stream (1)' là 'true', nhưng' Stream.from (1) == Stream.from (1) 'không dừng lại. –

+0

@ChrisMartin Tùy thuộc vào cách danh sách được sử dụng (không có chu kỳ, nó là tốt), nhưng tôi có lẽ sẽ thực hiện một phương thức 'equals' an toàn hơn. Ví dụ, một ví dụ có thể truyền một tập các cá thể 'MyList' đã truy cập đến một' equalsMyList chuyên biệt (khác: MyList [T], đã truy cập: Đặt [MyList [T]]) '-method. Phương thức chuyên biệt sau đó có thể kiểm tra đệ quy bằng cách kiểm tra xem liệu 'this' đã được chứa trong' visited', và trả về true trong trường hợp này. Tiêu chí phá thai có lẽ sẽ cần phải kiểm tra ** nhận dạng đối tượng ** thay vì bình đẳng thông thường, hoặc chúng ta có thể chạy vào đệ quy vô tận tiếp theo. –

4

Tôi cũng không tìm thấy lý do chính xác các thông số tên theo tên bị cấm trong trường hợp các lớp học. Tôi đoán lời giải thích nên khá phức tạp và phức tạp. Nhưng Runar Bjarnason trong cuốn sách của mình "Functional Programming in Scala" cung cấp một cách tiếp cận tốt để xử lý trở ngại này. Anh ta sử dụng khái niệm "thunk" cùng với việc ghi nhớ. Dưới đây là một ví dụ về thực hiện Suối:

sealed trait Stream[+A] 
case object Empty extends Stream[Nothing] 
case class Cons[+A](h:() => A, t:() => Stream[A]) extends Stream[A] 
object Stream { 
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = { 
    lazy val head = hd 
    lazy val tail = tl 
    Cons(() => head,() => tail) 
} 
def empty[A]: Stream[A] = Empty 
def apply[A](as: A*): Stream[A] = 
    if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*)) 
} 
} 

Như bạn thấy, thay vì một tham số theo tên thông thường đối với trường hợp nhà xây dựng lớp dữ liệu mà họ sử dụng những gì họ gọi là một "thunk", một chức năng của zero-luận () => T. Sau đó, để làm cho điều này minh bạch cho người dùng, họ khai báo một hàm tạo thông minh trong đối tượng đồng hành cho phép bạn cung cấp các tham số theo tên và làm cho chúng được ghi nhớ.

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