ฉันใหม่กับทฤษฎีภาษาโปรแกรม ฉันกำลังดูการบรรยายออนไลน์ที่อาจารย์อ้างว่าฟังก์ชั่นที่มีประเภท polymorphic นั้นforall t: Type, t->t
เป็นตัวตน แต่ไม่ได้อธิบายว่าทำไม บางคนสามารถอธิบายให้ฉันทำไม อาจพิสูจน์ข้อเรียกร้องจากหลักการแรก
ฉันใหม่กับทฤษฎีภาษาโปรแกรม ฉันกำลังดูการบรรยายออนไลน์ที่อาจารย์อ้างว่าฟังก์ชั่นที่มีประเภท polymorphic นั้นforall t: Type, t->t
เป็นตัวตน แต่ไม่ได้อธิบายว่าทำไม บางคนสามารถอธิบายให้ฉันทำไม อาจพิสูจน์ข้อเรียกร้องจากหลักการแรก
คำตอบ:
สิ่งแรกที่ควรทราบคือนี่ไม่จำเป็นต้องเป็นเรื่องจริง ยกตัวอย่างเช่นขึ้นอยู่กับภาษาของฟังก์ชั่นที่มีประเภทนั้นนอกเหนือจากฟังก์ชั่นเอกลักษณ์สามารถ: 1) วนซ้ำตลอดไป 2) กลายพันธุ์บางสถานะ 3) ส่งคืนnull
4) ส่งข้อยกเว้น 5) ดำเนินการ I / O บางอย่าง 6) แยกเธรดเพื่อทำอย่างอื่น 7) ทำcall/cc
shenanigans, 8) ใช้บางอย่างเช่น Java's Object.hashCode
, 9) ใช้การสะท้อนกลับเพื่อกำหนดว่าชนิดเป็นจำนวนเต็มและเพิ่มขึ้นถ้าเป็นเช่นนั้น 10) ใช้การสะท้อนเพื่อวิเคราะห์ call stack และ ทำสิ่งที่อยู่บนพื้นฐานของบริบทที่เรียกว่า 11) อาจเป็นสิ่งอื่น ๆ อีกมากมายและแน่นอนรวมกันข้างต้น
ดังนั้นคุณสมบัติที่นำไปสู่สิ่งนี้พาราเมทริกตี้เป็นคุณสมบัติของภาษาโดยรวมและมีความหลากหลายและแข็งแกร่งกว่า สำหรับแคลคูลัสอย่างเป็นทางการจำนวนมากที่ศึกษาในทฤษฎีประเภทไม่มีพฤติกรรมดังกล่าวข้างต้นสามารถเกิดขึ้นได้ ตัวอย่างเช่นสำหรับSystem F / แคลคูลัส polymorphic lambda บริสุทธิ์ที่ซึ่งพาราเมทริกตี้ถูกศึกษาเป็นครั้งแรกไม่มีพฤติกรรมดังกล่าวข้างต้นใด ๆ ที่สามารถเกิดขึ้นได้ มันก็ไม่ได้มีข้อยกเว้นรัฐไม่แน่นอนnull
, call/cc
I / O, การสะท้อนและก็ขอ normalizing ดังนั้นจึงไม่สามารถห่วงตลอดไป ดังที่ Gilles ได้กล่าวไว้ในความคิดเห็นบทความTheorems ฟรี!โดย Phil Wadler เป็นการแนะนำที่ดีในหัวข้อนี้และการอ้างอิงของมันจะไปเพิ่มเติมในทฤษฎีโดยเฉพาะเทคนิคของความสัมพันธ์เชิงตรรกะ ลิงค์นั้นยังแสดงเอกสารอื่น ๆ โดย Wadler ในหัวข้อของพารามิเตอร์
เนื่องจากพาราเมทริกตี้เป็นสมบัติของภาษาเพื่อพิสูจน์ว่ามันต้องมีการพูดภาษาอย่างเป็นทางการก่อนแล้วจึงมีข้อโต้แย้งที่ค่อนข้างซับซ้อน อาร์กิวเมนต์ที่ไม่เป็นทางการสำหรับกรณีนี้สมมติว่าเราอยู่ในแคลคูลัส polymorphic แลมบ์ดานั่นคือเนื่องจากเราไม่รู้ว่าt
เราไม่สามารถดำเนินการใด ๆ กับอินพุต (เช่นเราไม่สามารถเพิ่มได้เพราะเราไม่รู้ว่ามันคืออะไร ตัวเลข) หรือสร้างค่าของประเภทนั้น (สำหรับทุกอย่างที่เรารู้ว่าt
= Void
ซึ่งเป็นประเภทที่ไม่มีค่าเลย) วิธีเดียวในการสร้างมูลค่าประเภทt
คือการคืนค่าที่มอบให้เรา ไม่มีพฤติกรรมอื่น ๆ ที่เป็นไปได้ วิธีหนึ่งที่จะเห็นว่าคือการใช้การทำให้เป็นมาตรฐานที่แข็งแกร่งและแสดงให้เห็นว่ามีรูปแบบปกติเพียงคำเดียวของประเภทนี้
หลักฐานการอ้างสิทธิ์นั้นค่อนข้างซับซ้อน แต่ถ้านั่นคือสิ่งที่คุณต้องการจริงๆคุณสามารถตรวจสอบเอกสารต้นฉบับของ Reynoldsในหัวข้อ
ความคิดหลักคือมันเก็บไว้สำหรับฟังก์ชันpolamorphicที่ร่างกายของฟังก์ชัน polymorphic จะเหมือนกันสำหรับ monomorphic instantiations ทั้งหมดของฟังก์ชัน ในระบบดังกล่าวจะไม่มีข้อสันนิษฐานเกี่ยวกับชนิดของพารามิเตอร์ประเภท polymorphic และถ้าค่าเดียวในขอบเขตมีประเภททั่วไปไม่มีอะไรเกี่ยวข้องกับมัน แต่คืนค่ามันหรือส่งผ่านไปยังฟังก์ชันอื่นที่คุณ ' คุณได้กำหนดไว้แล้วว่าจะไม่ทำอะไรเลยนอกจากส่งคืนหรือส่งต่อ .. ดังนั้นในที่สุดสิ่งที่คุณทำได้ก็คือฟังก์ชั่นการระบุตัวตนบางส่วนก่อนส่งคืนพารามิเตอร์
ด้วยคำเตือนทั้งหมดที่ดีเร็กกล่าวถึงและเพิกเฉยต่อความขัดแย้งที่เกิดจากการใช้ทฤษฎีเซตขอให้ฉันวาดหลักฐานในจิตวิญญาณของ Reynolds / Wadler
ฟังก์ชั่นของประเภท:
f :: forall t . t -> t
เป็นครอบครัวที่มีฟังก์ชั่นดัชนีโดยประเภทเสื้อ
แนวคิดคือเพื่อกำหนดฟังก์ชัน polymorphic อย่างเป็นทางการเราไม่ควรปฏิบัติกับประเภทเป็นชุดของค่า แต่เป็นความสัมพันธ์ ประเภทพื้นฐานเช่นInt
ความสัมพันธ์ที่ก่อให้เกิดความเสมอภาค - เช่นสองInt
ค่ามีความเกี่ยวข้องหากพวกเขามีความเท่าเทียมกัน ฟังก์ชั่นที่เกี่ยวข้องถ้าพวกเขาแมปค่าที่เกี่ยวข้องกับค่าที่เกี่ยวข้อง กรณีที่น่าสนใจคือฟังก์ชั่น polymorphic พวกเขาแมปประเภทที่เกี่ยวข้องกับค่าที่เกี่ยวข้อง
ในกรณีของเราเราต้องการสร้างความสัมพันธ์ระหว่างฟังก์ชัน polymorphic และgประเภทสองชนิด:
forall t . t -> t
สมมติว่าประเภทที่เกี่ยวข้องกับการพิมพ์เสื้อ ฟังก์ชั่นแรกฉแผนที่ชนิดsเป็นค่า - ที่นี่ค่าตัวเองเป็นฟังก์ชั่นฉsประเภทs → s ฟังก์ชั่นที่สองแผนที่ชนิดTไปยังอีกมูลค่ากรัมเสื้อประเภทเสื้อ→ T เราบอกว่าfเกี่ยวข้องกับgหากค่าf sและg tสัมพันธ์กัน เนื่องจากค่าเหล่านี้เป็นฟังก์ชั่นของตัวเองพวกเขาจะเกี่ยวข้องถ้าพวกเขาแมปค่าที่เกี่ยวข้องกับค่าที่เกี่ยวข้อง
ขั้นตอนสำคัญคือการใช้ทฤษฎีบทพาราเมตริกเมทริกของเรย์โนลด์สซึ่งบอกว่าคำใด ๆ มีความสัมพันธ์กับตัวเอง ในกรณีของเราฟังก์ชั่นf
นั้นเกี่ยวข้องกับตัวมันเอง ในคำอื่น ๆ ถ้าs
มีความเกี่ยวข้องกับt
, ยังเกี่ยวข้องกับฉเสื้อ
ตอนนี้เราสามารถเลือกความสัมพันธ์ระหว่างสองประเภทและใช้ทฤษฎีบทนี้ได้ ลองเลือกประเภทแรกเป็นประเภทหน่วย()
ซึ่งมีเพียงค่าเดียวหรือที่เรียก()
อีกอย่างว่า เราจะเก็บประเภทที่สองt
ตามอำเภอใจ แต่ไม่ว่างเปล่า ลองเลือกความสัมพันธ์ระหว่าง()
และt
เป็นเพียงคู่เดียว((), c)
โดยที่c
ค่าของประเภทt
(ความสัมพันธ์เป็นเพียงส่วนหนึ่งของผลคูณของชุดคาร์ทีเซียน) ทฤษฎีบทพาราเมทริกตี้บอกเราว่าจะต้องเกี่ยวข้องกับฉเสื้อ พวกเขาจะต้องแมปค่าที่เกี่ยวข้องกับค่าที่เกี่ยวข้อง ฟังก์ชั่นแรก f (ไม่ได้มีทางเลือกมากก็ต้องแมมูลค่าเพียงกลับไป ดังนั้นฟังก์ชันที่สอง f tต้องแม็พกับ(เฉพาะค่าที่เกี่ยวข้องกับ) ตั้งแต่เป็นพลสมบูรณ์เราสรุปว่าฉTคือฉัน d tและตั้งแต่เป็นพลสมบูรณ์คือ()
()
c
c
()
c
t
f
id
แก้ไข: ความคิดเห็นข้างต้นได้ให้ชิ้นส่วนที่ขาดหายไป บางคนกำลังเล่นกับภาษาที่สมบูรณ์น้อยกว่าที่ตั้งใจ ฉันไม่สนใจภาษาเหล่านี้อย่างชัดเจน ภาษาที่ไม่สมบูรณ์แบบที่ใช้งานได้จริง ๆ เป็นเรื่องยากที่จะออกแบบ ส่วนที่เหลือทั้งหมดนี้ขยายตัวในสิ่งที่เกิดขึ้นพยายามใช้ทฤษฎีบทเหล่านี้เป็นภาษาเต็ม
เท็จ!
function f(a): forall t: Type, t->t
function g(a): forall t: Type, t->t
return (a is g) ? f : a
return a is f ? g : a
ที่is
ผู้ประกอบการเปรียบเทียบสองตัวแปรสำหรับข้อมูลอ้างอิง นั่นคือพวกเขามีค่าเดียวกัน ไม่ใช่ค่าที่เทียบเท่าค่าเดียวกัน ฟังก์ชั่นf
และg
เทียบเท่าโดยคำจำกัดความบางอย่าง แต่ไม่เหมือนกัน
ถ้าฟังก์ชั่นนี้ผ่านตัวมันจะส่งคืนสิ่งอื่น; มิฉะนั้นจะส่งคืนอินพุตของมัน สิ่งอื่นมีชนิดเดียวกันกับตัวเองดังนั้นจึงสามารถทดแทนได้ ในคำอื่น ๆf
ไม่ได้เป็นตัวตนเพราะf(f)
ผลตอบแทนในขณะที่ตัวตนที่จะกลับมาg
f
สำหรับทฤษฎีบทที่จะถือมันจะต้องมีความสามารถในการลดไร้สาระ
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(1000, <0, a>)[1]¹
หากคุณยินดีที่จะสมมติว่าคุณสามารถอนุมานการอนุมานประเภทได้ง่ายขึ้นมาก
หากเราพยายาม จำกัด โดเมนจนกว่าทฤษฎีบทจะจบลงเราก็ต้อง จำกัด ขอบเขตให้สุดขั้ว
raise
exit
ตอนนี้เรากำลังเริ่มถูก จำกัดnil
ไม่มี นี่เริ่มเป็นปัญหาแล้ว เราได้จัดการกับ 1 / 0. 1 หลายวิธีแล้วการดำรงอยู่ของทั้งสองข้อ จำกัด ที่ผ่านมาทำให้ภาษาพิการ ในขณะที่ทัวริงยังคงสมบูรณ์วิธีเดียวที่จะทำให้วัตถุประสงค์ทั่วไปเป็นไปได้คือการจำลองแพลตฟอร์มภายในที่ตีความภาษาด้วยข้อกำหนดที่หลวมกว่า
¹หากคุณคิดว่าคอมไพเลอร์สามารถอนุมานได้ให้ลองอันนี้
function fermat(z) : int -> int
function pow(x, p)
return p = 0 ? 1 : x * pow(x, p - 1)
function f2(x, y, z) : int, int, int -> <int, int>
left = pow(x, 5) + pow(y, 5)
right = pow(z, 5)
return left = right
? <x, y>
: pow(x, 5) < right
? f2(x + 1, y, z)
: pow(y, 5) < right
? f2(2, y + 1, z)
: f2(2, 2, z + 1)
return f2(2, 2, z)
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(fermat(3)[0], <0, a>)[1]
²หลักฐานที่คอมไพเลอร์ไม่สามารถทำได้ขึ้นอยู่กับการทำให้ไม่เห็น เราสามารถใช้หลายไลบรารีเพื่อให้แน่ใจว่าคอมไพเลอร์ไม่สามารถดูลูปได้ในครั้งเดียว นอกจากนี้เราสามารถสร้างบางสิ่งบางอย่างที่โปรแกรมจะทำงาน แต่ไม่สามารถรวบรวมได้เพราะคอมไพเลอร์ไม่สามารถทำการเหนี่ยวนำในหน่วยความจำที่มีอยู่
³ใครบางคนคิดว่าคุณสามารถมีผลตอบแทนเป็นศูนย์ได้โดยไม่ต้องมีประเภทสามัญโดยพลการกลับมาเป็นศูนย์ สิ่งนี้จ่ายค่าปรับที่น่ารังเกียจซึ่งฉันไม่เห็นว่ามีประสิทธิภาพในภาษาที่สามารถจ่ายได้
function f(a, b, c): t: Type: t[],int,int->t
return a[b/c]
จะต้องไม่รวบรวม ปัญหาพื้นฐานคือการจัดทำดัชนีอาร์เรย์รันไทม์ไม่ทำงานอีกต่อไป
foil
ปริมาณคืออะไร) สิ่งนี้ไม่เป็นประโยชน์เลย