การใช้สมาชิกประเภทใด ๆ (เช่นซ้อนกัน) ไม่มากก็น้อยอาจทำให้เกิดความต้องการประเภทเมธอดที่ต้องพึ่งพา โดยเฉพาะอย่างยิ่งฉันยืนยันว่ารูปแบบเค้กคลาสสิกที่ไม่มีวิธีขึ้นอยู่กับการต่อต้านรูปแบบ
แล้วปัญหาคืออะไร? ประเภทที่ซ้อนกันใน Scala ขึ้นอยู่กับอินสแตนซ์ที่แนบมา ดังนั้นในกรณีที่ไม่มีประเภทเมธอดที่ขึ้นต่อกันความพยายามที่จะใช้พวกมันนอกอินสแตนซ์นั้นอาจเป็นเรื่องยากอย่างน่าผิดหวัง สิ่งนี้สามารถเปลี่ยนการออกแบบที่ในตอนแรกดูสง่างามและน่าดึงดูดให้กลายเป็นสิ่งที่น่ากลัวซึ่งมีความเข้มงวดและยากต่อการปรับโครงสร้างใหม่
ผมจะแสดงให้เห็นว่ามีการออกกำลังกายในระหว่างที่ฉันให้ฉันหลักสูตรการฝึกอบรมขั้นสูง Scala ,
trait ResourceManager {
type Resource <: BasicResource
trait BasicResource {
def hash : String
def duplicates(r : Resource) : Boolean
}
def create : Resource
// Test methods: exercise is to move them outside ResourceManager
def testHash(r : Resource) = assert(r.hash == "9e47088d")
def testDuplicates(r : Resource) = assert(r.duplicates(r))
}
trait FileManager extends ResourceManager {
type Resource <: File
trait File extends BasicResource {
def local : Boolean
}
override def create : Resource
}
class NetworkFileManager extends FileManager {
type Resource = RemoteFile
class RemoteFile extends File {
def local = false
def hash = "9e47088d"
def duplicates(r : Resource) = (local == r.local) && (hash == r.hash)
}
override def create : Resource = new RemoteFile
}
เป็นตัวอย่างของรูปแบบเค้กคลาสสิก: เรามีกลุ่มนามธรรมที่ค่อยๆถูกกลั่นกรองผ่านการสืบทอด ( ResourceManager
/ ได้Resource
รับการขัดเกลาโดยFileManager
/ File
ซึ่งจะถูกขัดเกลาโดยNetworkFileManager
/ RemoteFile
) เป็นตัวอย่างของเล่น แต่รูปแบบเป็นของจริง: ใช้ตลอดทั้งคอมไพเลอร์ Scala และถูกใช้อย่างกว้างขวางในปลั๊กอิน Scala Eclipse
นี่คือตัวอย่างของสิ่งที่เป็นนามธรรมที่ใช้อยู่
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
โปรดสังเกตว่าการขึ้นต่อกันของพา ธ หมายความว่าคอมไพลเลอร์จะรับประกันว่าtestHash
และtestDuplicates
เมธอดบนNetworkFileManager
สามารถเรียกใช้ด้วยอาร์กิวเมนต์ที่สอดคล้องกับมันเท่านั้นเช่น เป็นของตัวเองRemoteFiles
และไม่มีอะไรอื่น
นั่นเป็นคุณสมบัติที่ต้องการอย่างปฏิเสธไม่ได้ แต่สมมติว่าเราต้องการย้ายรหัสทดสอบนี้ไปยังไฟล์ต้นฉบับอื่น? ด้วยประเภทเมธอดที่ขึ้นต่อกันมันง่ายมากที่จะกำหนดวิธีการเหล่านั้นใหม่นอกResourceManager
ลำดับชั้น
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
สังเกตการใช้ประเภทเมธอดที่เกี่ยวข้องที่นี่: ประเภทของอาร์กิวเมนต์ที่สอง ( rm.Resource
) ขึ้นอยู่กับค่าของอาร์กิวเมนต์แรก ( rm
)
เป็นไปได้ที่จะทำสิ่งนี้โดยไม่ต้องพึ่งพาประเภทวิธีการ แต่มันก็น่าอึดอัดมากและกลไกก็ไม่ได้ใช้งานง่าย: ฉันสอนหลักสูตรนี้มาเกือบสองปีแล้วและในเวลานั้นไม่มีใครคิดวิธีแก้ปัญหาที่ใช้งานได้
ลองด้วยตัวคุณเอง ...
// Reimplement the testHash and testDuplicates methods outside
// the ResourceManager hierarchy without using dependent method types
def testHash // TODO ...
def testDuplicates // TODO ...
testHash(rf)
testDuplicates(rf)
หลังจากต่อสู้กับมันไม่นานคุณอาจจะค้นพบว่าทำไมฉัน (หรืออาจจะเป็น David MacIver เราจำไม่ได้ว่าเราคนไหนเป็นคนบัญญัติศัพท์) เรียกสิ่งนี้ว่า Bakery of Doom
แก้ไข:ฉันทามติคือ Bakery of Doom เป็นเหรียญของ David MacIver ...
สำหรับโบนัส: รูปแบบของประเภทที่ขึ้นต่อกันโดยทั่วไปของ Scala (และประเภทวิธีการขึ้นอยู่กับส่วนหนึ่ง) ได้รับแรงบันดาลใจจากภาษาการเขียนโปรแกรมBeta ... ซึ่งเกิดขึ้นโดยธรรมชาติจากความหมายการซ้อนกันของ Beta ฉันไม่รู้ภาษาการเขียนโปรแกรมกระแสหลักอื่น ๆ ที่มีประเภทขึ้นอยู่กับในรูปแบบนี้ ภาษาเช่น Coq, Cayenne, Epigram และ Agda มีรูปแบบการพิมพ์ที่แตกต่างกันซึ่งมีลักษณะทั่วไปมากกว่า แต่แตกต่างอย่างมีนัยสำคัญโดยเป็นส่วนหนึ่งของระบบประเภทซึ่งไม่เหมือนกับ Scala ที่ไม่มีการพิมพ์ย่อย