ทำไมฟังก์ชั่นที่มี polymorphic type `forall t: Type, t-> t` ต้องเป็นฟังก์ชันเฉพาะตัว?


18

ฉันใหม่กับทฤษฎีภาษาโปรแกรม ฉันกำลังดูการบรรยายออนไลน์ที่อาจารย์อ้างว่าฟังก์ชั่นที่มีประเภท polymorphic นั้นforall t: Type, t->tเป็นตัวตน แต่ไม่ได้อธิบายว่าทำไม บางคนสามารถอธิบายให้ฉันทำไม อาจพิสูจน์ข้อเรียกร้องจากหลักการแรก


3
ฉันคิดว่าคำถามนี้จะต้องซ้ำกัน แต่ฉันหามันไม่เจอ cs.stackexchange.com/questions/341/…เป็นวิธีการติดตาม การอ้างอิงมาตรฐานคือทฤษฎีบทฟรี! โดย Phil Wadler
Gilles 'หยุดความชั่วร้าย'

1
ลองสร้างฟังก์ชั่นทั่วไปกับประเภทนี้ที่ทำอะไรอย่างอื่น คุณจะพบว่าไม่มี
Bergi

@Bergi ใช่ฉันไม่พบตัวอย่างตัวนับ แต่ก็ยังไม่แน่ใจว่าจะพิสูจน์ได้อย่างไร
abhishek

แต่ข้อสังเกตของคุณคืออะไรเมื่อคุณพยายามค้นหา ทำไมความพยายามของคุณถึงไม่ทำงาน?
Bergi

@Gilles บางทีคุณอาจจำcs.stackexchange.com/q/19430/14663 ได้ไหม?
Bergi

คำตอบ:


32

สิ่งแรกที่ควรทราบคือนี่ไม่จำเป็นต้องเป็นเรื่องจริง ยกตัวอย่างเช่นขึ้นอยู่กับภาษาของฟังก์ชั่นที่มีประเภทนั้นนอกเหนือจากฟังก์ชั่นเอกลักษณ์สามารถ: 1) วนซ้ำตลอดไป 2) กลายพันธุ์บางสถานะ 3) ส่งคืนnull4) ส่งข้อยกเว้น 5) ดำเนินการ I / O บางอย่าง 6) แยกเธรดเพื่อทำอย่างอื่น 7) ทำcall/ccshenanigans, 8) ใช้บางอย่างเช่น Java's Object.hashCode, 9) ใช้การสะท้อนกลับเพื่อกำหนดว่าชนิดเป็นจำนวนเต็มและเพิ่มขึ้นถ้าเป็นเช่นนั้น 10) ใช้การสะท้อนเพื่อวิเคราะห์ call stack และ ทำสิ่งที่อยู่บนพื้นฐานของบริบทที่เรียกว่า 11) อาจเป็นสิ่งอื่น ๆ อีกมากมายและแน่นอนรวมกันข้างต้น

ดังนั้นคุณสมบัติที่นำไปสู่สิ่งนี้พาราเมทริกตี้เป็นคุณสมบัติของภาษาโดยรวมและมีความหลากหลายและแข็งแกร่งกว่า สำหรับแคลคูลัสอย่างเป็นทางการจำนวนมากที่ศึกษาในทฤษฎีประเภทไม่มีพฤติกรรมดังกล่าวข้างต้นสามารถเกิดขึ้นได้ ตัวอย่างเช่นสำหรับSystem F / แคลคูลัส polymorphic lambda บริสุทธิ์ที่ซึ่งพาราเมทริกตี้ถูกศึกษาเป็นครั้งแรกไม่มีพฤติกรรมดังกล่าวข้างต้นใด ๆ ที่สามารถเกิดขึ้นได้ มันก็ไม่ได้มีข้อยกเว้นรัฐไม่แน่นอนnull, call/ccI / O, การสะท้อนและก็ขอ normalizing ดังนั้นจึงไม่สามารถห่วงตลอดไป ดังที่ Gilles ได้กล่าวไว้ในความคิดเห็นบทความTheorems ฟรี!โดย Phil Wadler เป็นการแนะนำที่ดีในหัวข้อนี้และการอ้างอิงของมันจะไปเพิ่มเติมในทฤษฎีโดยเฉพาะเทคนิคของความสัมพันธ์เชิงตรรกะ ลิงค์นั้นยังแสดงเอกสารอื่น ๆ โดย Wadler ในหัวข้อของพารามิเตอร์

เนื่องจากพาราเมทริกตี้เป็นสมบัติของภาษาเพื่อพิสูจน์ว่ามันต้องมีการพูดภาษาอย่างเป็นทางการก่อนแล้วจึงมีข้อโต้แย้งที่ค่อนข้างซับซ้อน อาร์กิวเมนต์ที่ไม่เป็นทางการสำหรับกรณีนี้สมมติว่าเราอยู่ในแคลคูลัส polymorphic แลมบ์ดานั่นคือเนื่องจากเราไม่รู้ว่าtเราไม่สามารถดำเนินการใด ๆ กับอินพุต (เช่นเราไม่สามารถเพิ่มได้เพราะเราไม่รู้ว่ามันคืออะไร ตัวเลข) หรือสร้างค่าของประเภทนั้น (สำหรับทุกอย่างที่เรารู้ว่าt= Voidซึ่งเป็นประเภทที่ไม่มีค่าเลย) วิธีเดียวในการสร้างมูลค่าประเภทtคือการคืนค่าที่มอบให้เรา ไม่มีพฤติกรรมอื่น ๆ ที่เป็นไปได้ วิธีหนึ่งที่จะเห็นว่าคือการใช้การทำให้เป็นมาตรฐานที่แข็งแกร่งและแสดงให้เห็นว่ามีรูปแบบปกติเพียงคำเดียวของประเภทนี้


1
System F หลีกเลี่ยงลูปไม่สิ้นสุดที่ระบบประเภทไม่สามารถตรวจพบได้อย่างไร ที่จัดเป็นแก้ไม่ได้ในกรณีทั่วไป
Joshua

2
@ โจชัว - หลักฐานที่เป็นไปไม่ได้มาตรฐานสำหรับปัญหาการหยุดชะงักเริ่มต้นด้วยการสันนิษฐานว่ามีวงวนไม่สิ้นสุดในตอนแรก ดังนั้นขอให้ตั้งคำถามว่าทำไมระบบ F ไม่มีลูปไม่สิ้นสุดคือการใช้เหตุผลแบบวน ในวงกว้างระบบ F ไม่เกือบจะสมบูรณ์แบบดังนั้นฉันสงสัยว่ามันเป็นไปตามสมมติฐานของการพิสูจน์นั้น มันอ่อนแอง่ายพอที่คอมพิวเตอร์จะพิสูจน์ได้ว่าโปรแกรมทั้งหมดของโปรแกรมนั้นสิ้นสุดลง (ไม่มีการเรียกซ้ำไม่มีในขณะที่ลูปเท่านั้นที่อ่อนแอมากสำหรับลูปและอื่น ๆ )
Jonathan Cast

@ โจชัว: มันแก้ไม่ได้ในกรณีทั่วไปซึ่งไม่ได้ป้องกันการแก้ไขในหลายกรณีพิเศษ โดยเฉพาะอย่างยิ่งทุกโปรแกรมที่เกิดขึ้นเป็นเทอม F ที่เป็นระบบได้รับการพิสูจน์แล้วว่าหยุดชะงัก: มีข้อพิสูจน์ที่เหมือนกันข้อหนึ่งที่ใช้งานได้สำหรับโปรแกรมเหล่านี้ทั้งหมด เห็นได้ชัดว่านี้หมายถึงมีอื่น ๆโปรแกรมที่ไม่สามารถพิมพ์ลงในระบบ F ...
cody

15

หลักฐานการอ้างสิทธิ์นั้นค่อนข้างซับซ้อน แต่ถ้านั่นคือสิ่งที่คุณต้องการจริงๆคุณสามารถตรวจสอบเอกสารต้นฉบับของ Reynoldsในหัวข้อ

ความคิดหลักคือมันเก็บไว้สำหรับฟังก์ชันpolamorphicที่ร่างกายของฟังก์ชัน polymorphic จะเหมือนกันสำหรับ monomorphic instantiations ทั้งหมดของฟังก์ชัน ในระบบดังกล่าวจะไม่มีข้อสันนิษฐานเกี่ยวกับชนิดของพารามิเตอร์ประเภท polymorphic และถ้าค่าเดียวในขอบเขตมีประเภททั่วไปไม่มีอะไรเกี่ยวข้องกับมัน แต่คืนค่ามันหรือส่งผ่านไปยังฟังก์ชันอื่นที่คุณ ' คุณได้กำหนดไว้แล้วว่าจะไม่ทำอะไรเลยนอกจากส่งคืนหรือส่งต่อ .. ดังนั้นในที่สุดสิ่งที่คุณทำได้ก็คือฟังก์ชั่นการระบุตัวตนบางส่วนก่อนส่งคืนพารามิเตอร์


8

ด้วยคำเตือนทั้งหมดที่ดีเร็กกล่าวถึงและเพิกเฉยต่อความขัดแย้งที่เกิดจากการใช้ทฤษฎีเซตขอให้ฉันวาดหลักฐานในจิตวิญญาณของ 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สัมพันธ์กัน เนื่องจากค่าเหล่านี้เป็นฟังก์ชั่นของตัวเองพวกเขาจะเกี่ยวข้องถ้าพวกเขาแมปค่าที่เกี่ยวข้องกับค่าที่เกี่ยวข้องsเสื้อssssเสื้อก.เสื้อเสื้อเสื้อก.sก.เสื้อ

ขั้นตอนสำคัญคือการใช้ทฤษฎีบทพาราเมตริกเมทริกของเรย์โนลด์สซึ่งบอกว่าคำใด ๆ มีความสัมพันธ์กับตัวเอง ในกรณีของเราฟังก์ชั่นfนั้นเกี่ยวข้องกับตัวมันเอง ในคำอื่น ๆ ถ้าsมีความเกี่ยวข้องกับt, ยังเกี่ยวข้องกับเสื้อsเสื้อ

ตอนนี้เราสามารถเลือกความสัมพันธ์ระหว่างสองประเภทและใช้ทฤษฎีบทนี้ได้ ลองเลือกประเภทแรกเป็นประเภทหน่วย()ซึ่งมีเพียงค่าเดียวหรือที่เรียก()อีกอย่างว่า เราจะเก็บประเภทที่สองtตามอำเภอใจ แต่ไม่ว่างเปล่า ลองเลือกความสัมพันธ์ระหว่าง()และtเป็นเพียงคู่เดียว((), c)โดยที่cค่าของประเภทt(ความสัมพันธ์เป็นเพียงส่วนหนึ่งของผลคูณของชุดคาร์ทีเซียน) ทฤษฎีบทพาราเมทริกตี้บอกเราว่าจะต้องเกี่ยวข้องกับเสื้อ พวกเขาจะต้องแมปค่าที่เกี่ยวข้องกับค่าที่เกี่ยวข้อง ฟังก์ชั่นแรก f (()เสื้อไม่ได้มีทางเลือกมากก็ต้องแมมูลค่าเพียงกลับไป ดังนั้นฟังก์ชันที่สอง f tต้องแม็พกับ(เฉพาะค่าที่เกี่ยวข้องกับ) ตั้งแต่เป็นพลสมบูรณ์เราสรุปว่าTคือฉัน d tและตั้งแต่เป็นพลสมบูรณ์คือ()()()เสื้อcc()cเสื้อผมdเสื้อtfid

คุณสามารถค้นหารายละเอียดเพิ่มเติมในบล็อกของฉัน


-2

แก้ไข: ความคิดเห็นข้างต้นได้ให้ชิ้นส่วนที่ขาดหายไป บางคนกำลังเล่นกับภาษาที่สมบูรณ์น้อยกว่าที่ตั้งใจ ฉันไม่สนใจภาษาเหล่านี้อย่างชัดเจน ภาษาที่ไม่สมบูรณ์แบบที่ใช้งานได้จริง ๆ เป็นเรื่องยากที่จะออกแบบ ส่วนที่เหลือทั้งหมดนี้ขยายตัวในสิ่งที่เกิดขึ้นพยายามใช้ทฤษฎีบทเหล่านี้เป็นภาษาเต็ม

เท็จ!

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)ผลตอบแทนในขณะที่ตัวตนที่จะกลับมาgf

สำหรับทฤษฎีบทที่จะถือมันจะต้องมีความสามารถในการลดไร้สาระ

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]¹

หากคุณยินดีที่จะสมมติว่าคุณสามารถอนุมานการอนุมานประเภทได้ง่ายขึ้นมาก

หากเราพยายาม จำกัด โดเมนจนกว่าทฤษฎีบทจะจบลงเราก็ต้อง จำกัด ขอบเขตให้สุดขั้ว

  • ฟังก์ชั่นบริสุทธิ์ (ไม่มีสถานะที่ไม่แน่นอนไม่มี IO) ตกลงฉันสามารถอยู่กับที่ หลายครั้งที่เราต้องการเรียกใช้การพิสูจน์มากกว่าฟังก์ชั่น
  • ไลบรารีมาตรฐานว่างเปล่า Meh
  • ไม่มีและไม่มีการ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]

จะต้องไม่รวบรวม ปัญหาพื้นฐานคือการจัดทำดัชนีอาร์เรย์รันไทม์ไม่ทำงานอีกต่อไป


@Bergi: ฉันสร้างตัวอย่าง
Joshua

1
โปรดสละเวลาสักครู่เพื่อไตร่ตรองความแตกต่างระหว่างคำตอบของคุณกับอีกสองข้อ ประโยคเปิดของดีเร็กคือ“ สิ่งแรกที่ควรทราบคือไม่จำเป็นต้องเป็นเรื่องจริง” จากนั้นเขาก็อธิบายว่าคุณสมบัติของภาษาใดทำให้เป็นจริง jmite ยังอธิบายสิ่งที่ทำให้เป็นจริง ในทางตรงกันข้ามคำตอบของคุณให้ตัวอย่างในคำที่ไม่ระบุ (และภาษาที่ไม่ธรรมดา) โดยไม่มีคำอธิบาย (ตัวบอกfoilปริมาณคืออะไร) สิ่งนี้ไม่เป็นประโยชน์เลย
Gilles 'ดังนั้นหยุดความชั่วร้าย'

1
@DW: หาก a คือ f ดังนั้นประเภทของ a คือประเภทของ f ซึ่งเป็นชนิดของ g และดังนั้นจึงตรวจสอบ typecheck หากคอมไพเลอร์ตัวจริงเตะมันออกมาฉันจะใช้ตัวส่งนักวิ่งที่ภาษาจริงมีอยู่เสมอสำหรับระบบแบบสแตติกที่ทำให้มันผิดและมันจะไม่ล้มเหลวที่รันไทม์
Joshua

2
นั่นไม่ใช่วิธีที่ตัวพิมพ์ดีดแบบคงที่ทำงาน ไม่ได้ตรวจสอบว่าประเภทนั้นตรงกับอินพุตเฉพาะหนึ่งรายการ มีกฎประเภทที่เฉพาะเจาะจงซึ่งมีวัตถุประสงค์เพื่อให้แน่ใจว่าฟังก์ชั่นจะพิมพ์ในอินพุตที่เป็นไปได้ทั้งหมด หากคุณต้องการใช้งาน typecast โซลูชันนี้น่าสนใจน้อยกว่ามาก แน่นอนว่าถ้าคุณข้ามระบบประเภทแล้วประเภทของฟังก์ชั่นรับประกันอะไร - ไม่แปลกใจเลย!
DW

1
@DW: คุณพลาดจุด มีข้อมูลเพียงพอสำหรับตัวตรวจสอบชนิดคงที่เพื่อพิสูจน์ว่ารหัสนั้นเป็นประเภทที่ปลอดภัยหากมีปัญญาในการค้นหา
Joshua
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.