在討論F-bounded Polymorphism之前,有一個結構是支撐它的,我已經很難理解了。
trait Container[A]
trait Contained extends Container[Contained]/span>
這個結構似乎是面向物件的小事,因為它也存在于java中,但已經讓我有點困惑了。
問題在于,當我看到這個trait Contained extends Container[Contained]時,我感覺它是一個無限的型別遞回。
當我們定義List型別時,即使我們有Cons[A](a:A, tail:List[A]),我們也有case object Nil。所以遞回可以以Nil結束。
但是在這里,我不明白我們怎么會不在一個無限的遞回中?以及為什么它能起作用。
誰能幫我解開這個疑惑?或者是否有任何檔案、博客或其他什么東西可以解釋這一點是如何作業的,或者也許是如何實作的。
uj5u.com熱心網友回復:
我認為你的問題是由對術語遞回型別的含義以及種類、型別和類之間的區別的困惑所驅動。
讓我們首先解決遞回型別的問題。有時人們誤用遞回型別,實際上是指這個型別對應于一個資料結構,該結構遞回地包含自己。
下面的Tree是一個遞回資料strcuture,但不是一個遞回型別,
trait Tree[ A]
case class NonEmptyNode[A](value: A, left: Tree[A], right: Tree[A]/span>) extends Tree[A]。
case object EmptyNode extends Tree[Nothing]
而下面的東西不是一個遞回資料strcuture,而是一個遞回型別
trait Mergeable[ A]
class A(val i。Int) extends Mergeable[A]
有趣的是,這也與許多現代語言的一些有爭議的特性的 "重要性 "有關--null和mutability。
那么,讓我們假設您是 Java 語言的設計者之一(早在 2000 年初),并希望通過增加對通用編程的支持來增強您的用戶。
您希望您的用戶能夠為他們的類定義通用合約。例如,一個可合并類的契約。
public abstract >class Mergable<A> {
public A merge(A other)
}
這是很好的。但是,這也為下面的事情打開了方便之門
public abstract class HasBrother<A> {
public A brother。
}
public class Human extends HasBrother<Human> {
public Human brother;
公共的 Human(Human brother) {
this.brother = brother。
}
而這正是問題的開始。你將如何能夠創建一個Human的實體呢?
但是他們對此有一個 "了不起 "的解決方案。只要使用null并保持兄弟的mutable(不要使用final)。
Human h1 = new Human(null)。
Human h2 = new Human(null) 。
h1.兄弟 = h2.兄弟
h2.brother = h1;
但是Scala中的scala.collection.immutable.List(以及上面創建的Tree)資料strcuture與此非常相似。而且我們不喜歡null和mutability.
這在Scala中是可能的,只是因為支持型別引數差異和名為Nothing的特殊底層型別。
現在,讓我們談談
種類、型別和類別。
type可以被認為是一個定義的contract。
class可以被認為是上述契約的運行時實作。
kind實際上是一個type建構式。它需要一個型別引數來構造型別。
讓我們以下面的List為例,
trait MyList[ A]
class MyNil extends MyList[Nothing]/span>
class MyCons[A](val value。A, val tail: MyList[A]/span>) extends MyList[A]。
注意::我故意不使用case object或case class,以避免同伴物件造成的混亂。
在這里,
kind對于MyList是F[ A]。
kind對于MyCons是F[ A].
kind對于MyNil是A.
MyList沒有相應的型別,但是它有一個相應的類MyList。
MyCons沒有相應的型別,但是它有一個相應的類MyCons.
這些對應的 在 但是,在MyNil沒有相應的型別,但是它有一個相應的類MyNil>。
MyNil有一個對應的型別 MyNil和一個對應的類MyNil。型別(在大多數語言中只在編譯時可用)和類(在運行時存在)在創建時被系結到變數。
val l: MyCons[Int] = new MyCons(1, new MyNil)中,l將具有MyCons[Int]的型別和運行時的類MyCons(它將是Class[_ <: MyCons[Int]>的實體)。
val l: MyList[Int] = new MyCons(1, new MyNil)中,l將具有MyList[Int]型別和運行時類MyCons(它將是Class[_ <: MyList[Int]]的一個實體)。
現在,讓我們談談實際的遞回型別?我們之前說過,一個遞回型別看起來像下面這樣,
trait Mergeable[ A]
class Abc extends Mergeable[Abc]
但是,說上面是一個遞回型別是有點錯誤的。更準確的說法是,Mergeable是一種型別,可以產生遞回型別。
val abc: Abc = new Abc
// type - Abc; class - Abc (Class[_ < : Abc])
val abc: Mergeable[Abc] = new Abc
// type - Mergeable[Abc]; class - Abc (Class[_ < : Mergeable[Abc]])
val abc: Mergeable[Mergeable[Abc]] = new Abc
// type - Mergeable[Mergeable[Abc]]; class - Abc (Class[_ <: Mergeable[Mergeable[Abc]])
// ... and so on to Infinity
但是,如果我們去掉使A不變數,那么這個型別就不能導致遞回型別。
trait Mergeable[ A]
class Abc extends Mergeable[Abc]
val abc: Abc = new Abc
// type - Abc; class - Abc (Class[_ < : Abc])
val abc: Mergeable[Abc] = new Abc
// type - Mergeable[Abc]; class - Abc (Class[_ <: Abc])
val abc: Mergeable[Mergeable[Abc]] = new Abc
// ^
// error: type mismatch;
//發現 : Abc
// required: Mergeable[Mergeable[Abc]]
//注意:Abc <: Mergeable[Abc](和Abc <: Mergeable[Abc]),但trait Mergeable在型別A中是不變的。
//你可能希望將A定義為 A而不是。(SLS 4.5)
這些遞回型別不同于F-Bound多型性。
以下是F-Bound但不是遞回
trait Fruit[A < 。Fruit[A]]_span>
class Apple extends Fruit[Apple]
在這里,Fruit的種類是F[A <: iw$Fruit[A]]。而我們在A上添加了一個上界,即A必須是Fruit[A]的子型別(它是一個F)。這就是F-Bound這個名字的由來。
下面的內容既是F-Bound又是遞回。
trait Fruit[ A < 。Fruit[A]]_span>
class Apple extends Fruit[Apple]
在這里,水果的種類是F[ A <: iw$Fruit[A]]。
現在,我可以在許多遞回的深度指定任何Apple的型別。
val f: Apple = new Apple
// type - Apple; class - Apple (Class[_ <: Apple])
val f: Fruit[Apple] = new Apple
// type - Fruit[Apple]; class - Apple (Class[_ <: Fruit[Apple]])
val f: Fruit[Fruit[Apple] ] = new Apple
// type - Fruit[Fruit[Apple]]; class - Apple (Class[_ <: Fruite[Fruit[Apple]])}
// ... and so on to Infinity.
任何不支持更高的型別的語言都不能有F系結的型別。
現在,我們終于可以討論你的疑問了,你在考慮無限回圈的問題。
就像我們前面說的,一個型別可以被認為是像一個標簽,用來指代某個契約。所以,那種急切的回圈實際上不會發生。
(我認為)Scala編譯器使用 所以,如果你有代碼 只有這樣,編譯器才需要 "思考 "這個型別 然后它將能夠通過使用型別關系 萬一,如果這個 如果你看一下https://www.scala-lang.org/files/archive/spec/2.13/07-implicits.html的隱式決議規則,你會發現以下內容 為了防止無限擴展,比如上面的神奇例子,編譯器會跟蹤一個 "開放隱式型別 "的堆疊,這些隱式引數目前正在被搜索。每當搜索到TT型別的隱式引數時,TT就會被添加到堆疊中,并與產生它的隱式定義配對,以及它是否需要滿足一個副名隱式引數。一旦對隱式引數的搜索肯定失敗或成功,該型別就會從堆疊中洗掉。每當一個型別要被添加到堆疊中時,它就會與由同一隱式定義產生的現有條目進行核對,然后, 在這里,TT的核心型別是TT,它擴展了別名,洗掉了頂層型別注釋和細化,并且頂層存在性約束變數的出現被其上界所取代。
如果TT與UU等價,或者如果TT和UU的頂層型別構造者有一個共同的元素,并且TT比UU更復雜,并且TT和UU的覆寫集相等,那么核心型別TT就支配著一個型別UU。
因此,當Scala編譯器在隱式約束搜索中發現一個回圈時,它將選擇該約束,避免進入無限回圈。
uj5u.com熱心網友回復: 與其從遞回的角度思考,不如純粹從量化和邊界的角度來看待它可能會有幫助。例如,我們來解釋一下 如是說 也就是說,型別構造器 接下來,讓我們收緊其中的一個邊界,使之成為 被解釋為 也就是說,型別建構式 所以不用過多考慮遞回問題,我們可以承認上面是合法的。 題外話,關于 "遞回型別 "這個術語,和計算機科學中的許多術語一樣,我懷疑是否有一個普遍接受的定義,它到底是什么意思。例如,論文面向物件編程的F-Bounded Polymorphism呼叫 一個 "遞回型別",因為
標籤:implicit evidences(=:=, <:<約束)進行型別比較。這些證據是由編譯器通過使用型別引數上的型別界限而懶散地生成的。所以,編譯器有能力為這些遞回型別中的任何深度的型別反復生成evidences。
val f。Fruit[Fruit[Fruit[Fruit[Apple]]]] = new Apple
Fruit[Fruit[Fruit[Apple]]]]以及它與型別Apple的對比。Apple <:<Fruit[Fruit[Fruit[Apple]]]]>產生證據Apple <: Fruit[Apple](由繼承性提供)和Fruit[T2] <: Fruit[T1]對于任何T2 <: T1(由A在種類Fruit[A]中的共變性提供)。因此,上述代碼將成功地型別檢查。隱式證據生成以某種方式遇到了一個回圈,它實際上將不是一個問題,因為這已經在隱式決議/生成規則中得到了照顧。
trait Container[A]
trait Container[A > 。Nothing <: Any]。
Container接受所有型別A作為引數。既然它接受所有型別,那么它也將接受我們尚未定義的型別Contained,因為無論我們定義什么,它最終都必須在Nothing和Any之間,因此,不用考慮遞回問題,我們可以承認下面的做法是合法的trait Contained extends Container[Contained]
trait Container[A < 。Container[A]】
trait Container[A > 。Nothing <: Container[A]】
Container接受Nothing和Container[A]之間的所有型別A作為引數。現在我們尚未定義的型別A = Contained最終將成為Container[A]的一個子型別,因為這是我們明確告訴編譯器的trait Contained extends Container[Contained]
class Point(val x: Double, val y: Double) {
def move(t: (Double, Double) 。) Point
def equal(p: Point)。Boolean(p: Point).
}
Point宣告了采取和回傳Point值的計算。
