โดยทั่วไปพารามิเตอร์ประเภทcovariantเป็นสิ่งที่ได้รับอนุญาตให้แตกต่างกันไปตามคลาสที่ถูก subtyped (หรือเปลี่ยนแปลงด้วย subtyping ด้วยเหตุนี้คำนำหน้า "co-") เป็นรูปธรรมมากขึ้น:
trait List[+A]
List[Int]เป็นชนิดย่อยของList[AnyVal]เพราะเป็นชนิดย่อยของInt AnyValซึ่งหมายความว่าคุณอาจระบุอินสแตนซ์ที่คาดว่าจะList[Int]มีค่าประเภทเมื่อList[AnyVal]ใด นี่เป็นวิธีที่ใช้งานง่ายมากสำหรับ generics ในการทำงาน แต่ปรากฎว่ามันไม่ปลอดภัย (แบ่งระบบประเภท) เมื่อใช้ต่อหน้าข้อมูลที่ไม่แน่นอน นี่คือเหตุผลที่ generics ไม่แปรเปลี่ยนใน Java ตัวอย่างสั้น ๆ ของความไม่มั่นคงโดยใช้อาร์เรย์ Java (ซึ่งแปรปรวนร่วมอย่างไม่ถูกต้อง):
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";
เราเพียงแค่กำหนดค่าของชนิดไปยังอาร์เรย์ของพิมพ์String Integer[]ด้วยเหตุผลที่ควรชัดเจนนี่เป็นข่าวร้าย ระบบชนิดของ Java อนุญาตให้ทำได้ในเวลารวบรวม JVM จะ "ช่วย" โยนArrayStoreExceptionat runtime ระบบชนิดของ Scala ป้องกันปัญหานี้ได้เนื่องจากพารามิเตอร์ type บนArrayคลาสไม่แปรเปลี่ยน (การประกาศ[A]มากกว่า[+A])
ทราบว่ามีประเภทของความแปรปรวนอื่นที่รู้จักกันเป็นcontravariance สิ่งนี้สำคัญมากเพราะอธิบายได้ว่าทำไมความแปรปรวนร่วมอาจทำให้เกิดปัญหาบางอย่าง ความแปรปรวนเป็นอักษรตรงข้ามของความแปรปรวนร่วม: พารามิเตอร์แปรผันขึ้นกับการพิมพ์ย่อย มันเป็นเรื่องธรรมดาน้อยกว่าบางส่วนเพราะมันตอบโต้ได้ง่ายแม้ว่ามันจะมีแอพพลิเคชั่นที่สำคัญมากอย่างหนึ่ง: ฟังก์ชั่น
trait Function1[-P, +R] {
def apply(p: P): R
}
สังเกตเห็นคำอธิบายประกอบ" - " ความแปรปรวนในPพารามิเตอร์ชนิด ประกาศนี้เป็นวิธีการทั้งที่Function1เป็น contravariant ในPและ covariant Rใน ดังนั้นเราสามารถได้รับสัจพจน์ต่อไปนี้:
T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']
ขอให้สังเกตว่าT1'จะต้องเป็นชนิดย่อย (หรือพิมพ์เดียวกัน) ของT1ในขณะที่มันอยู่ตรงข้ามสำหรับและT2 T2'ในภาษาอังกฤษสามารถอ่านได้ดังต่อไปนี้:
ฟังก์ชั่นเป็นชนิดย่อยของฟังก์ชั่นอื่นBถ้าพารามิเตอร์ชนิดของเป็น supertype ประเภทพารามิเตอร์ของBในขณะที่ประเภทการกลับมาของเป็นชนิดย่อยของประเภทกลับของB
เหตุผลสำหรับกฎนี้เหลือไว้สำหรับการออกกำลังกายให้กับผู้อ่าน (คำใบ้: คิดเกี่ยวกับกรณีที่แตกต่างกันเนื่องจากฟังก์ชั่นจะถูกพิมพ์ย่อยเช่นเดียวกับตัวอย่างอาร์เรย์ของฉันจากด้านบน)
ด้วยความรู้ที่ค้นพบใหม่ของคุณเกี่ยวกับความร่วมมือและความขัดแย้งคุณควรเห็นว่าทำไมตัวอย่างต่อไปนี้จะไม่คอมไพล์:
trait List[+A] {
def cons(hd: A): List[A]
}
ปัญหาคือว่าAเป็น covariant ในขณะที่consฟังก์ชั่นคาดว่าพารามิเตอร์ชนิดที่จะเป็นค่าคงที่ ดังนั้นการAเปลี่ยนแปลงทิศทางที่ผิด ที่น่าสนใจพอที่เราจะแก้ปัญหานี้โดยการListcontravariant ในAแต่แล้วพิมพ์กลับList[A]จะเป็นที่ไม่ถูกต้องในขณะที่consฟังก์ชั่นคาดว่าผลตอบแทนประเภทให้เป็นcovariant
เพียงสองตัวเลือกของเราที่นี่คือ a) ทำให้Aค่าคงที่สูญเสียคุณสมบัติการพิมพ์ย่อยที่ดีและใช้งานง่ายของความแปรปรวนร่วมหรือ b) เพิ่มพารามิเตอร์ประเภทท้องถิ่นลงในconsวิธีที่กำหนดAเป็นขอบเขตล่าง:
def cons[B >: A](v: B): List[B]
ตอนนี้ใช้ได้ คุณสามารถจินตนาการได้ว่าการAเปลี่ยนแปลงนั้นลดลง แต่Bสามารถแปรผันขึ้นไปได้ด้วยความเคารพAเนื่องจากAมันเป็นขอบเขตล่าง ด้วยการประกาศวิธีการนี้เราจะมีAความแปรปรวนร่วมและทุกอย่างได้ผล
ขอให้สังเกตว่าเคล็ดลับนี้จะทำงานเฉพาะถ้าเรากลับตัวอย่างของซึ่งเป็นผู้เชี่ยวชาญในประเภทน้อยที่เฉพาะเจาะจงList Bหากคุณพยายามที่จะทำให้Listไม่แน่นอนสิ่งต่าง ๆ พังทลายลงมาเมื่อคุณพยายามกำหนดค่าประเภทBให้กับตัวแปรประเภทAซึ่งไม่ได้รับอนุญาตจากคอมไพเลอร์ เมื่อใดก็ตามที่คุณมีความไม่แน่นอนคุณจำเป็นต้องมีตัวแปลบางอย่างซึ่งต้องใช้พารามิเตอร์เมธอดบางประเภทซึ่ง (รวมถึงตัวเข้าถึง) แสดงถึงความไม่แปรเปลี่ยน ความแปรปรวนร่วมทำงานกับข้อมูลที่เปลี่ยนแปลงไม่ได้เนื่องจากการดำเนินการที่เป็นไปได้เพียงอย่างเดียวคือการเข้าถึงซึ่งอาจได้รับประเภทผลตอบแทนแปรปรวนร่วม
varเป็นตัวอย่างในขณะที่valไม่ได้ มันเป็นเหตุผลเดียวกันกับที่คอลเลคชั่นที่ไม่เปลี่ยนรูปแบบของสกาล่านั้นแปรเปลี่ยนไป แต่สิ่งที่เปลี่ยนแปลงไม่ได้