2013-04-30 163 views
7

我正在嘗試編寫一個簡單的查詢monad,並且遇到問題,我的通用類型註釋正確。Scala推斷的類型參數 - 類型範圍推斷爲'Nothing'

我第一次嘗試去如下(大大簡化了簡潔)

case class Person(val name: String) 
abstract class Schema[T]  
object People extends Schema[Person] 

case class Query[U <: Schema[T], T](schema: U) {  <---- Type signature 
    def results: Seq[T] = ... 
    def where(f: U => Operation) = ... 
} 

class TypeText extends Application { 
    val query = Query(People)      <---- Type inference fails 
} 

編譯器不喜歡這一點,因爲它無法推斷「T」的類型。

error: inferred type arguments [People.type,Nothing] do not conform to method apply's type parameter bounds [U <: Schema[T],T]

雖然嘗試我發現,使用視圖的邊界,而不是按預期工作

case class Query[U <% Schema[T], T](schema: U) { 

(注意使用的角度約束「<%」,而不是約束型「<」)

然而,在我對類型系統有限的理解中,因爲我期待Schema [T]的實際子類(而不僅僅是可轉換性),所以我會假定類型綁定「<:」是在這裏使用的正確界限?

如果是這樣的話,我錯過了什麼 - 我怎麼給編譯器足夠的提示,以便在使用類型邊界而不是視圖邊界時正確推斷T?

回答

2

爲了編碼的兩個類型參數之間的關係,你可以使用像

case class Query[U, T](schema: U)(implicit ev: U <:< Schema[T]) { ... } 

參見4.3節和Scala Language Spec的§4.4獲取更多信息。

+0

謝謝。我曾經看到過這種語法,並在想這是什麼意思。 – 2013-05-01 06:36:24

+0

我不認爲這個答案是正確的。請在我的答案底部查看我的回覆(此評論不會爲討論提供足夠的空間)。 – 2013-05-01 09:01:01

+0

@Régis你是對的。我誤解了規範中的範圍規則。這確實是一個推理問題,正如你在答案中所描述的那樣。也就是說,使用隱式證據參數是對關係進行編碼並支持所需推理的最簡單方法。 – 2013-05-01 19:51:11

1

我總是發現,一類/函數使用兩種類型標識符時,類型推理系統不能如預期般工作,你必須要明確的,像這樣:

val query = Query[People.type, Person](People) 

如果你改變了你Query聲明如下:

case class Query[U <: Schema[_](schema: U) 

你會能夠做到這一點:

val query = Query(People) 

但是,您不知道提供的Schema的基礎類型,並且將無法正確實現results函數。

5

這不是一個完全統計的答案(至少對我來說),因爲我不得不承認,我不能確切地說出這裏的推理失敗的原因和原因。我對此只有一些模糊的直覺。 該問題與編譯器一次推斷兩個類型參數有關。 至於爲什麼改變綁定到視圖邊界的類型會修復編譯,我的理解是現在有兩個參數列表,結果我們現在有兩個連續的類型推斷階段,而不是兩次推斷。事實上,以下內容:

case class Query[U <% Schema[T], T](schema: U) 

是一樣的:

case class Query[U, T](schema: U)(implicit conv: U => Schema[T]) 

第一個參數驅動器列表的U推理,然後第二個(注意:U現在知道)將推動推斷T

在表達式Query(People)的情況下,參數People將驅動類型推理器將U設置爲People.type。然後,編譯器將查找從People.typeSchema[T]的隱式轉換,以傳入第二個參數列表。範圍中的唯一一個是從People.typeSchema[Person]的(微不足道的)轉換,從而推斷推導出T = Person

要修復編譯而不訴諸約束視圖,你可以用一個抽象類型取代類型參數T

case class Person(val name: String) 
sealed trait Schema { 
    type T 
} 
abstract class SchemaImpl[_T] extends Schema { 
    type T = _T 
} 
object People extends SchemaImpl[Person] 
case class Query[U <: Schema](schema: U) { 
    def results: Seq[schema.T] = ??? 
} 
class TypeText extends Application { 
    val query = Query(People) 
} 

UPDATE

@Aaron Novstrup的: 據我所知,你的回答是不正確的(更新更新:來自Aaron的原始答案聲稱Query聲明等於case class Query[U <: Schema[X], T](schema: U))。

case class Query[U <: Schema[X], T](schema: U) 

甚至沒有編譯。 比方說,你的意思是

case class Query[U <: Schema[_], T](schema: U) 

(其中確實編譯),很容易在REPL檢查,這是不一樣的兩種。

事實上,以下罰款編譯:

case class Query[U <: Schema[_], T](schema: U) 
type MyQuery = Query[Schema[String], Int] 

雖然,以下不會:

case class Query[U <: Schema[T], T](schema: U) 
type MyQuery = Query[Schema[String], Int] 

因此證明了差異。錯誤是:

<console>:10: error: type arguments [Schema[String],Int] do not conform to class Query's type parameter bounds [U <: Schema[T],T] 
     type MyQuery = Query[Schema[String], Int] 

明確顯示的T第一和第二OCCURENCES表示相同類型的,我們有兩種類型的參數之間的關係。

2

我有同樣的問題。以下爲我工作:

case class Query[U <: Schema[T], T](schema: U with Schema[T]) { 
    ... 
}