ความสะดวกสบายวากยสัมพันธ์กันการรวมกันของประเภทเดี่ยวประเภทขึ้นอยู่กับเส้นทางและค่านิยมโดยปริยายหมายความว่าสกาล่าได้รับการสนับสนุนที่ดีที่น่าแปลกใจสำหรับการพิมพ์ขึ้นอยู่กับที่ผมได้พยายามที่จะแสดงให้เห็นในไม่มีรูปแบบ
การสนับสนุนที่แท้จริง Scala สำหรับประเภทการขึ้นผ่านทางประเภทขึ้นอยู่กับเส้นทาง สิ่งเหล่านี้อนุญาตให้ประเภทขึ้นอยู่กับเส้นทางตัวเลือกผ่านกราฟวัตถุ - (เช่นค่า -) เช่นนั้น
scala> class Foo { class Bar }
defined class Foo
scala> val foo1 = new Foo
foo1: Foo = Foo@24bc0658
scala> val foo2 = new Foo
foo2: Foo = Foo@6f7f757
scala> implicitly[foo1.Bar =:= foo1.Bar] // OK: equal types
res0: =:=[foo1.Bar,foo1.Bar] = <function1>
scala> implicitly[foo1.Bar =:= foo2.Bar] // Not OK: unequal types
<console>:11: error: Cannot prove that foo1.Bar =:= foo2.Bar.
implicitly[foo1.Bar =:= foo2.Bar]
ในมุมมองของฉันข้างต้นน่าจะเพียงพอที่จะตอบคำถาม "Scala เป็นภาษาที่พิมพ์ขึ้นเองหรือไม่" ในแง่บวก: เป็นที่ชัดเจนว่าที่นี่เรามีประเภทที่แตกต่างจากค่าซึ่งเป็นคำนำหน้า
อย่างไรก็ตามมักจะถูกคัดค้านว่า Scala ไม่ใช่ภาษาประเภทที่อ้างอิงแบบ "เต็ม" เนื่องจากไม่มีผลรวมและประเภทผลิตภัณฑ์ตามที่พบใน Agda หรือ Coq หรือ Idris เป็นเนื้อแท้ ฉันคิดว่าสิ่งนี้สะท้อนให้เห็นถึงการตรึงรูปแบบมากกว่าปัจจัยพื้นฐานในระดับหนึ่งอย่างไรก็ตามฉันจะพยายามแสดงให้เห็นว่า Scala ใกล้เคียงกับภาษาอื่น ๆ เหล่านี้มากกว่าที่รับทราบโดยทั่วไป
แม้จะมีคำศัพท์ประเภทผลรวมที่ขึ้นกับ (หรือที่เรียกว่าประเภทซิกม่า) เป็นเพียงคู่ของค่าที่ประเภทของค่าที่สองขึ้นอยู่กับค่าแรก สิ่งนี้แสดงได้โดยตรงใน Scala
scala> trait Sigma {
| val foo: Foo
| val bar: foo.Bar
| }
defined trait Sigma
scala> val sigma = new Sigma {
| val foo = foo1
| val bar = new foo.Bar
| }
sigma: java.lang.Object with Sigma{val bar: this.foo.Bar} = $anon$1@e3fabd8
และในความเป็นจริงนี่เป็นส่วนสำคัญของการเข้ารหัสประเภทวิธีการที่ต้องพึ่งพาซึ่งจำเป็นสำหรับการหลีกหนีจาก 'Bakery of Doom'ใน Scala ก่อน 2.10 (หรือก่อนหน้านี้ผ่านตัวเลือกคอมไพเลอร์ Scala -Ydependent-method type)
ประเภทผลิตภัณฑ์ที่ขึ้นอยู่กับ (หรือที่เรียกว่าประเภท Pi) เป็นฟังก์ชันจากค่าเป็นประเภท สิ่งเหล่านี้เป็นกุญแจสำคัญในการแสดงเวกเตอร์ขนาดคงที่และลูกโปสเตอร์อื่น ๆ สำหรับภาษาโปรแกรมที่พิมพ์ขึ้น เราสามารถเข้ารหัสประเภท Pi ใน Scala โดยใช้การรวมกันของประเภทที่ขึ้นกับพา ธ ประเภทซิงเกิลตันและพารามิเตอร์นัย อันดับแรกเรากำหนดลักษณะที่จะแสดงฟังก์ชันจากค่าของประเภท T ถึงประเภท U
scala> trait Pi[T] { type U }
defined trait Pi
เราสามารถกำหนดวิธีการหลายรูปแบบที่ใช้ประเภทนี้ได้
scala> def depList[T](t: T)(implicit pi: Pi[T]): List[pi.U] = Nil
depList: [T](t: T)(implicit pi: Pi[T])List[pi.U]
(สังเกตการใช้ประเภทที่ขึ้นกับพา ธpi.U
ในประเภทผลลัพธ์List[pi.U]
) เมื่อระบุค่าประเภท T ฟังก์ชันนี้จะส่งกลับรายการค่า (n ว่าง) ของประเภทที่ตรงกับค่า T นั้น ๆ
ทีนี้มากำหนดค่าที่เหมาะสมและพยานโดยปริยายสำหรับความสัมพันธ์เชิงหน้าที่ที่เราต้องการถือ
scala> object Foo
defined module Foo
scala> object Bar
defined module Bar
scala> implicit val fooInt = new Pi[Foo.type] { type U = Int }
fooInt: java.lang.Object with Pi[Foo.type]{type U = Int} = $anon$1@60681a11
scala> implicit val barString = new Pi[Bar.type] { type U = String }
barString: java.lang.Object with Pi[Bar.type]{type U = String} = $anon$1@187602ae
และตอนนี้นี่คือฟังก์ชันการใช้งานประเภท Pi ของเรา
scala> depList(Foo)
res2: List[fooInt.U] = List()
scala> depList(Bar)
res3: List[barString.U] = List()
scala> implicitly[res2.type <:< List[Int]]
res4: <:<[res2.type,List[Int]] = <function1>
scala> implicitly[res2.type <:< List[String]]
<console>:19: error: Cannot prove that res2.type <:< List[String].
implicitly[res2.type <:< List[String]]
^
scala> implicitly[res3.type <:< List[String]]
res6: <:<[res3.type,List[String]] = <function1>
scala> implicitly[res3.type <:< List[Int]]
<console>:19: error: Cannot prove that res3.type <:< List[Int].
implicitly[res3.type <:< List[Int]]
(โปรดทราบว่าที่นี่เราใช้<:<
ตัวดำเนินการพยานประเภทย่อยของ Scala มากกว่า=:=
เพราะres2.type
และres3.type
เป็นประเภทซิงเกิลตันและด้วยเหตุนี้จึงแม่นยำกว่าประเภทที่เรากำลังตรวจสอบใน RHS)
อย่างไรก็ตามในทางปฏิบัติใน Scala เราจะไม่เริ่มต้นด้วยการเข้ารหัสประเภท Sigma และ Pi แล้วดำเนินการต่อจากที่นั่นเหมือนที่เราทำใน Agda หรือ Idris แต่เราจะใช้ประเภทที่ขึ้นกับพา ธ ประเภทซิงเกิลตันและนัยโดยตรง คุณสามารถค้นหาตัวอย่างมากมายของวิธีนี้เล่นออกมาในไม่มีรูปแบบ: ชนิดขนาด , บันทึกขยาย , HLists ครอบคลุม , เศษเหล็กสำเร็จรูปของคุณ , ทั่วไปซิปฯลฯ เป็นต้น
การคัดค้านที่เหลืออยู่เพียงอย่างเดียวที่ฉันเห็นคือในการเข้ารหัสประเภท Pi ข้างต้นเราต้องการให้ประเภทซิงเกิลตันของค่าที่ขึ้นอยู่กับการแสดงออก น่าเสียดายใน Scala สิ่งนี้เป็นไปได้สำหรับค่าของประเภทการอ้างอิงเท่านั้นไม่ใช่สำหรับค่าของประเภทที่ไม่อ้างอิง (esp. เช่น int) นี้เป็นความอัปยศ แต่ไม่ได้เป็นความยากลำบากที่แท้จริง: ตรวจสอบประเภทของ Scala หมายถึงประเภทเดี่ยวของค่าที่ไม่ใช่การอ้างอิงภายในและมีได้รับคู่ของการทดลองในการทำให้พวกเขาแสดงออกโดยตรง ในทางปฏิบัติเราสามารถหลีกเลี่ยงปัญหาที่มีการเข้ารหัสประเภทระดับมาตรฐานเป็นธรรมของจำนวนธรรมชาติ
ไม่ว่าในกรณีใดฉันไม่คิดว่าข้อ จำกัด โดเมนเล็กน้อยนี้สามารถใช้เป็นการคัดค้านสถานะของ Scala ในฐานะภาษาที่พิมพ์ขึ้นได้ ถ้าเป็นเช่นนั้นก็อาจกล่าวได้เช่นเดียวกันสำหรับ Dependent ML (ซึ่งอนุญาตให้ขึ้นกับค่าจำนวนธรรมชาติเท่านั้น) ซึ่งจะเป็นข้อสรุปที่แปลกประหลาด