“Ngữ cảnh ràng buộc” trong Scala là gì?


115

Một trong những tính năng mới của Scala 2.8 là giới hạn ngữ cảnh. Một ngữ cảnh ràng buộc là gì và nó hữu ích ở đâu?

Tất nhiên tôi đã tìm kiếm trước (và tìm ví dụ này ) nhưng tôi không thể tìm thấy bất kỳ thông tin chi tiết và rõ ràng nào.


8
cũng kiểm tra này ra cho một tour du lịch của tất cả các loại giới hạn: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Arjan Blokzijl

2
Câu trả lời tuyệt vời này so sánh / đối chiếu giới hạn ngữ cảnh và giới hạn xem: stackoverflow.com/questions/4465948/…
Aaron Novstrup

Đây là một câu trả lời rất thoải mái stackoverflow.com/a/25250693/1586965
samthebest

Câu trả lời:


107

Bạn đã tìm thấy bài viết này ? Nó bao gồm tính năng ràng buộc ngữ cảnh mới, trong bối cảnh cải tiến mảng.

Nói chung, một tham số kiểu có giới hạn ngữ cảnh có dạng[T: Bound] ; nó được mở rộng thành tham số kiểu thuần túy Tcùng với tham số ngầm định của kiểuBound[T] .

Hãy xem xét phương pháp tabulatetạo thành một mảng từ kết quả của việc áp dụng một hàm f cho trước trên một dải số từ 0 cho đến một độ dài nhất định. Lên đến Scala 2.7, bảng có thể được viết như sau:

def tabulate[T](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

Trong Scala 2.8, điều này không còn khả thi nữa, vì thông tin thời gian chạy là cần thiết để tạo ra biểu diễn phù hợp Array[T]. Người ta cần cung cấp thông tin này bằng cách chuyển một ClassManifest[T]vào phương thức dưới dạng một tham số ngầm định:

def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

Dưới dạng viết tắt, thay vào đó, một ràng buộc ngữ cảnh có thể được sử dụng trên tham số kiểu T, đưa ra:

def tabulate[T: ClassManifest](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

145

Câu trả lời của Robert bao gồm các chi tiết kỹ thuật của Context Bounds. Tôi sẽ cung cấp cho bạn cách giải thích của tôi về ý nghĩa của chúng.

Trong Scala, một View Bound ( A <% B) nắm bắt khái niệm 'có thể được xem là' (trong khi một giới hạn trên <:nắm bắt khái niệm 'là một'). Một ngữ cảnh bị ràng buộc ( A : C) cho biết 'có một' về một loại. Bạn có thể đọc các ví dụ về tệp kê khai là " Tcó một Manifest". Ví dụ bạn liên kết đến about Orderedvs Orderingminh họa sự khác biệt. Một phương pháp

def example[T <% Ordered[T]](param: T)

nói rằng tham số có thể được xem như là một Ordered. So sánh với

def example[T : Ordering](param: T)

cho biết rằng tham số có một liên kết Ordering.

Về mặt sử dụng, phải mất một thời gian để các quy ước được thiết lập, nhưng giới hạn ngữ cảnh được ưu tiên hơn giới hạn chế độ xem ( giới hạn chế độ xem hiện không được dùng nữa ). Một gợi ý là ràng buộc ngữ cảnh được ưu tiên hơn khi bạn cần chuyển một định nghĩa ngầm định từ phạm vi này sang phạm vi khác mà không cần tham chiếu trực tiếp đến nó (điều này chắc chắn là trường hợp ClassManifestđược sử dụng để tạo một mảng).

Một cách khác để suy nghĩ về giới hạn lượt xem và giới hạn ngữ cảnh là giới hạn đầu tiên chuyển các chuyển đổi ngầm khỏi phạm vi của người gọi. Thứ hai chuyển các đối tượng ngầm khỏi phạm vi của người gọi.


2
"have a" chứ không phải "is a" hoặc "coi như" là cái nhìn sâu sắc nhất đối với tôi - không thấy điều này trong bất kỳ giải thích nào khác. Có một phiên bản tiếng Anh đơn giản của các toán tử / hàm hơi khó hiểu sẽ giúp bạn dễ dàng tiếp thu hơn nhiều - cảm ơn!
DNA

1
@Ben Lings Ý bạn là gì khi .... 'có một' về một loại ...? Những gì về một loại ?
jhegedus

1
@jhegedus Đây là cách phân tích cú pháp của tôi: "about a type" có nghĩa là A đề cập đến một loại. Cụm từ "có một" thường được sử dụng trong thiết kế hướng đối tượng để mô tả các mối quan hệ đối tượng (ví dụ: Khách hàng "có" Địa chỉ). Nhưng ở đây mối quan hệ "có một" là giữa các loại, không phải đối tượng. Đó là một sự tương tự lỏng lẻo bởi vì mối quan hệ "có một" không cố hữu hoặc phổ biến như cách nó có trong thiết kế OO; Khách hàng luôn có Địa chỉ nhưng đối với ngữ cảnh ràng buộc A không phải lúc nào cũng có C. Thay vào đó, ngữ cảnh ràng buộc chỉ định rằng một phiên bản của C [A] phải được cung cấp ngầm.
jbyler

Tôi đã học Scala được một tháng, và đây là lời giải thích hay nhất mà tôi thấy trong tháng này! Xin cảm ơn @Ben!
Lifu Huang

@Ben Zerglings: Cảm ơn, sau khi trải qua thời gian quá lâu để hiểu những gì đang bối cảnh bị ràng buộc, câu trả lời của bạn là rất hữu ích [. has aÝ nghĩa hơn đối với tôi]
Shankar

39

(Đây là ghi chú trong ngoặc đơn. Hãy đọc và hiểu các câu trả lời khác trước.)

Context Bounds thực sự khái quát hóa View Bounds.

Vì vậy, với mã này được biểu thị bằng một Giới hạn Chế độ xem:

scala> implicit def int2str(i: Int): String = i.toString
int2str: (i: Int)String

scala> def f1[T <% String](t: T) = 0
f1: [T](t: T)(implicit evidence$1: (T) => String)Int

Điều này cũng có thể được thể hiện bằng Ranh giới ngữ cảnh, với sự trợ giúp của bí danh kiểu đại diện cho các hàm từ kiểu này Fsang kiểu khác T.

scala> trait To[T] { type From[F] = F => T }           
defined trait To

scala> def f2[T : To[String]#From](t: T) = 0       
f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int

scala> f2(1)
res1: Int = 0

Một ràng buộc ngữ cảnh phải được sử dụng với một phương thức khởi tạo kiểu thuộc loại * => *. Tuy nhiên, hàm tạo kiểu Function1là loại (*, *) => *. Việc sử dụng bí danh kiểu áp dụng một phần tham số kiểu thứ hai với kiểuString , tạo ra một phương thức khởi tạo kiểu thuộc loại chính xác để sử dụng làm giới hạn ngữ cảnh.

Có một đề xuất cho phép bạn thể hiện trực tiếp các kiểu được áp dụng một phần trong Scala mà không cần sử dụng bí danh kiểu bên trong một đặc điểm. Sau đó bạn có thể viết:

def f3[T : [X](X => String)](t: T) = 0 

Bạn có thể giải thích ý nghĩa của #From trong định nghĩa của f2 không? Tôi không chắc loại F đang được xây dựng ở đâu (tôi đã nói điều này chính xác chưa?)
Collin

1
Nó được gọi là phép chiếu kiểu, tham chiếu đến một thành viên kiểu Fromcủa kiểu To[String]. Chúng tôi không cung cấp một đối số kiểu From, vì vậy chúng tôi đề cập đến phương thức tạo kiểu, không phải là một kiểu. Phương thức khởi tạo kiểu này thuộc loại phù hợp để được sử dụng làm ràng buộc ngữ cảnh - * -> *. Điều này giới hạn tham số kiểu Tbằng cách yêu cầu một tham số ngầm định của kiểu To[String]#From[T]. Mở rộng loại bí danh, và thì đấy, bạn còn lại Function1[String, T].
viết tắt

đó có phải là Function1 [T, String] không?
ssanj

18

Đây là một ghi chú ngoặc đơn khác.

Như Ben đã chỉ ra , một ràng buộc ngữ cảnh đại diện cho một ràng buộc "có-một" giữa một tham số kiểu và một lớp kiểu. Nói cách khác, nó đại diện cho một ràng buộc rằng tồn tại một giá trị ngầm định của một lớp kiểu cụ thể.

Khi sử dụng một ngữ cảnh bị ràng buộc, người ta thường cần hiển thị giá trị tiềm ẩn đó. Ví dụ, với ràng buộc T : Ordering, người ta thường sẽ cần thể hiện của Ordering[T]nó thỏa mãn ràng buộc. Như đã trình bày ở đây , có thể truy cập giá trị ngầm định bằng cách sử dụng implicitlyphương thức hoặc contextphương pháp hữu ích hơn một chút :

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = 
   xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }

hoặc là

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
   xs zip ys map { t => context[T]().times(t._1, t._2) }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.