2012-07-01 32 views
9

Đây là một thiết lập đơn giản với hai đặc điểm, một lớp có tham số kiểu biến đổi được bao bọc bởi các đặc điểm trước đó và lớp thứ hai có tham số kiểu được bao bọc bởi lớp kia. Đối với cả hai lớp, một phương thức cụ thể có sẵn (thông qua bằng chứng tiềm ẩn) chỉ khi một trong hai đặc điểm làm nền tảng cho tham số kiểu. Đây biên dịch tốt:Scala: Bằng chứng ngầm cho lớp có tham số kiểu

trait Foo 
trait ReadableFoo extends Foo {def field: Int} 

case class Bar[+F <: Foo](foo: F) { 
    def readField(implicit evidence: F <:< ReadableFoo) = foo.field 
} 

case class Grill[+F <: Foo, +B <: Bar[F]](bar: B) { 
    def readField(implicit evidence: F <:< ReadableFoo) = bar.readField 
} 

Tuy nhiên, vì Bar là hiệp biến trong F, tôi không cần các F tham số trong Grill. Tôi chỉ cần yêu cầu rằng B là một loại phụ của Bar[ReadableFoo]. Này, tuy nhiên, thất bại:

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField 
} 

với lỗi:

error: Cannot prove that Any <:< this.ReadableFoo. 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField 

Tại sao những bằng chứng tiềm ẩn không được đưa vào tài khoản?

Trả lời

6

Gọi bar.readField là có thể vì ví dụ chứng minh <:< cho phép chuyển đổi ẩn từ B thành Bar[ReadableFoo].

Sự cố tôi cho rằng để gọi readField bạn cần có thông số bằng chứng liên tiếp F <:< ReadableFoo. Vì vậy, tôi đoán là, trình biên dịch không thay thế hoàn toàn tham số kiểu của Bar trong giai đoạn tìm kiếm đầu tiên của độ phân giải ngầm (vì để tìm readField, nó chỉ yêu cầu bất kỳ Bar ở nơi đầu tiên). Và sau đó nó bị nghẹt thở ở độ phân giải ẩn thứ hai, bởi vì không có hình thức 'backtracking' theo như tôi biết.

Dù sao đi nữa. Điều tốt là, bạn biết nhiều hơn các trình biên dịch và bạn có thể tham gia vào việc chuyển đổi một cách rõ ràng, hoặc bằng cách sử dụng phương pháp apply của <:<, hoặc bằng cách sử dụng phương pháp helper implicitly:

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = evidence(bar).readField 
} 

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = 
    implicitly[Bar[ReadableFoo]](bar).readField 
} 

Còn có một khả năng mà có thể được sạch nhất, vì nó không dựa vào việc thực hiện các <:< mà có thể là một vấn đề như @Kaito gợi ý:

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = 
    (bar: Bar[ReadableFoo]).readField 
} 
+0

Tôi không chắc chắn nếu <: Kaito

+0

@Kaito: Bạn có bất kỳ bằng chứng nào cho thấy '<: <' ''áp dụng' không có nghĩa là được gọi? Đó là [hành vi được ghi lại] (http://www.scala-lang.org/api/rc/scala/Predef$$$less$colon$less.html). Bạn có thể viết '(bar: Bar [ReadableFoo]). ReadField' và để cho chuyển đổi ngầm xuất hiện tự động, nhưng phiên bản của Sciss cảm thấy sạch hơn với tôi. –

+0

@TravisBrown: Không. Nhưng tôi không thể tìm thấy dòng mà thực sự bằng lời nói về hành vi của hàm, chỉ là lời giải thích được thừa kế của đặc điểm trừu tượng Function1. Tôi đồng ý rằng nó tốt đẹp nhưng tôi không chắc chắn nếu hành vi đó có thể được dựa vào, nó cũng có thể ném một ngoại lệ trong phiên bản tiếp theo tôi nghĩ. – Kaito

5

0 __ 's câu trả lời (dùng tham số bằng chứng tiềm ẩn để biến bar vào đúng chủng loại) là câu trả lời Tôi sẽ cung cấp cho các nhiệm vụ cụ thể ion bạn hỏi (mặc dù tôi khuyên bạn không nên sử dụng implicitly nếu bạn đã có đối số ẩn nằm ngay tại đó).

Cần lưu ý rằng tình huống bạn mô tả âm thanh giống như trường hợp sử dụng tốt cho tính đa hình đặc biệt thông qua các lớp loại, tuy nhiên.Nói ví dụ mà chúng ta đã có những thiết lập sau đây:

trait Foo 
case class Bar[F <: Foo](foo: F) 
case class Grill[B <: Bar[_]](bar: B) 

Và một lớp loại, cùng với một số phương pháp thuận tiện cho việc tạo ra các trường hợp mới và cho mối lái một phương pháp readField vào bất kỳ loại có một thể hiện trong phạm vi:

trait Readable[A] { def field(a: A): Int } 

object Readable { 
    def apply[A, B: Readable](f: A => B) = new Readable[A] { 
    def field(a: A) = implicitly[Readable[B]].field(f(a)) 
    } 

    implicit def enrich[A: Readable](a: A) = new { 
    def readField = implicitly[Readable[A]].field(a) 
    } 
} 

import Readable.enrich 

Và một vài trường hợp:

implicit def barInstance[F <: Foo: Readable] = Readable((_: Bar[F]).foo) 
implicit def grillInstance[B <: Bar[_]: Readable] = Readable((_: Grill[B]).bar) 

Và cuối cùng là một thể đọc được Foo:

case class MyFoo(x: Int) extends Foo 

implicit object MyFooInstance extends Readable[MyFoo] { 
    def field(foo: MyFoo) = foo.x 
} 

này cho phép chúng ta làm như sau, ví dụ:

scala> val readableGrill = Grill(Bar(MyFoo(11))) 
readableGrill: Grill[Bar[MyFoo]] = Grill(Bar(MyFoo(11))) 

scala> val anyOldGrill = Grill(Bar(new Foo {})) 
anyOldGrill: Grill[Bar[java.lang.Object with Foo]] = Grill(Bar([email protected])) 

scala> readableGrill.readField 
res0: Int = 11 

scala> anyOldGrill.readField 
<console>:22: error: could not find implicit value for evidence parameter of 
type Readable[Grill[Bar[java.lang.Object with Foo]]] 
       anyOldGrill.readField 
      ^

Đó là những gì chúng ta muốn.

1

Đây không phải là một câu trả lời cho câu hỏi, nhưng để chứng minh rằng 'loại hạn chế' thực sự chỉ là một chuyển đổi ngầm:

Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_33). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> trait A { def test() {} } 
defined trait A 

scala> class WhatHappens[T] { def test(t: T)(implicit ev: T <:< A) = t.test() } 
defined class WhatHappens 

scala> :javap -v WhatHappens 
... 
public void test(java.lang.Object, scala.Predef$$less$colon$less); 
    Code: 
    Stack=2, Locals=3, Args_size=3 
    0: aload_2 
    1: aload_1 
    2: invokeinterface #12, 2; //InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object; 
    7: checkcast #14; //class A 
    10: invokeinterface #17, 1; //InterfaceMethod A.test:()V 
    15: return 
... 
    LocalVariableTable: 
    Start Length Slot Name Signature 
    0  16  0 this  LWhatHappens; 
    0  16  1 t  Ljava/lang/Object; 
    0  16  2 ev  Lscala/Predef$$less$colon$less; 
... 
+0

Tôi biết cách hoạt động của nó. Câu hỏi mà tôi đã hỏi đơn giản là liệu đó có phải là hành vi dự định và có thể dựa vào những cập nhật trong tương lai hay không. Tài liệu chỉ đề cập đến chúng là các ràng buộc kiểu, chứ không phải cách sử dụng của chúng như các chuyển đổi ngầm định. – Kaito