อินสแตนซ์ Orphaned ใน Haskell


87

เมื่อรวบรวมแอปพลิเคชัน Haskell ของฉันด้วย-Wallตัวเลือก GHC จะบ่นเกี่ยวกับอินสแตนซ์ที่ไม่มีเจ้าของตัวอย่างเช่น:

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result

ชั้นประเภทToSElemจะไม่ระเบิดก็กำหนดโดยHStringTemplate

ตอนนี้ฉันรู้วิธีแก้ไขแล้ว (ย้ายการประกาศอินสแตนซ์ไปไว้ในโมดูลที่มีการประกาศผลลัพธ์) และฉันรู้ว่าทำไม GHC จึงต้องการหลีกเลี่ยงอินสแตนซ์ที่ไม่มีเจ้าของ แต่ฉันก็ยังเชื่อว่าวิธีของฉันดีกว่า ฉันไม่สนใจว่าคอมไพเลอร์จะไม่สะดวก - แทนที่จะเป็นฉัน

เหตุผลที่ฉันต้องการประกาศไฟล์ ToSElemอินสแตนซ์ในโมดูลผู้เผยแพร่เนื่องจากเป็นโมดูลผู้เผยแพร่ที่ขึ้นอยู่กับ HStringTemplate ไม่ใช่โมดูลอื่น ๆ ฉันพยายามแยกความกังวลและหลีกเลี่ยงไม่ให้ทุกโมดูลขึ้นอยู่กับ HStringTemplate

ฉันคิดว่าข้อดีอย่างหนึ่งของคลาสประเภทของ Haskell เมื่อเปรียบเทียบกับอินเทอร์เฟซของ Java คือเป็นแบบเปิดมากกว่าปิดดังนั้นจึงไม่จำเป็นต้องประกาศอินสแตนซ์ในตำแหน่งเดียวกับประเภทข้อมูล คำแนะนำของ GHC ดูเหมือนจะเพิกเฉยต่อสิ่งนี้

ดังนั้นสิ่งที่ฉันกำลังมองหาคือการตรวจสอบความถูกต้องว่าความคิดของฉันเป็นสิ่งที่ดีและฉันจะมีเหตุผลที่จะเพิกเฉย / ระงับคำเตือนนี้หรือการโต้แย้งที่น่าเชื่อกว่าในการทำสิ่งต่างๆในแบบของฉัน


การอภิปรายในคำตอบและความคิดเห็นแสดงให้เห็นว่ามีความแตกต่างอย่างมากระหว่างการกำหนดอินสแตนซ์เด็กกำพร้าในไฟล์ปฏิบัติการขณะที่คุณกำลังทำกับในไลบรารีที่เปิดเผยต่อผู้อื่น คำถามที่ได้รับความนิยมอย่างมากนี้แสดงให้เห็นว่าอินสแตนซ์เด็กกำพร้าสร้างความสับสนให้กับผู้ใช้ปลายทางของไลบรารีที่กำหนดได้อย่างไร
Christian Conkle

คำตอบ:


94

ฉันเข้าใจว่าทำไมคุณถึงต้องการทำสิ่งนี้ แต่น่าเสียดายที่อาจเป็นเพียงภาพลวงตาที่คลาส Haskell ดูเหมือนจะ "เปิด" ในแบบที่คุณพูด หลายคนรู้สึกว่าความเป็นไปได้ในการทำเช่นนี้เป็นข้อบกพร่องในข้อกำหนดของ Haskell ด้วยเหตุผลที่ฉันจะอธิบายด้านล่าง อย่างไรก็ตามหากมันไม่เหมาะสมกับอินสแตนซ์จริงๆคุณจำเป็นต้องประกาศในโมดูลที่มีการประกาศคลาสหรือในโมดูลที่มีการประกาศประเภทนั่นอาจเป็นสัญญาณว่าคุณควรใช้newtypeกระดาษห่อหุ้มหรืออื่น ๆ รอบประเภทของคุณ

สาเหตุที่ต้องหลีกเลี่ยงอินสแตนซ์ orphan ที่ลึกกว่าความสะดวกของคอมไพเลอร์ หัวข้อนี้ค่อนข้างขัดแย้งดังที่คุณเห็นจากคำตอบอื่น ๆ เพื่อความสมดุลของการอภิปรายฉันจะอธิบายมุมมองที่ว่าเราไม่ควรเคยเขียนอินสแตนซ์เด็กกำพร้าซึ่งฉันคิดว่าเป็นความคิดเห็นส่วนใหญ่ในหมู่ Haskellers ที่มีประสบการณ์ ความคิดเห็นของฉันอยู่ตรงกลางซึ่งฉันจะอธิบายในตอนท้าย

ปัญหาเกิดจากข้อเท็จจริงที่ว่าเมื่อมีการประกาศอินสแตนซ์มากกว่าหนึ่งรายการสำหรับคลาสและประเภทเดียวกันไม่มีกลไกใดในมาตรฐาน Haskell ที่จะระบุว่าจะใช้แบบใด คอมไพเลอร์ปฏิเสธโปรแกรม

เอฟเฟกต์ที่ง่ายที่สุดคือคุณสามารถมีโปรแกรมที่ทำงานได้อย่างสมบูรณ์ซึ่งจะหยุดการรวบรวมทันทีเนื่องจากการเปลี่ยนแปลงที่คนอื่นทำให้การพึ่งพาโมดูลของคุณห่างไกล

ที่แย่กว่านั้นคือเป็นไปได้ที่โปรแกรมที่ใช้งานจะเริ่มหยุดทำงานขณะรันไทม์เนื่องจากมีการเปลี่ยนแปลงที่อยู่ไกลออกไป คุณสามารถใช้วิธีการที่คุณสมมติว่ามาจากการประกาศอินสแตนซ์บางอย่างและอาจถูกแทนที่ด้วยอินสแตนซ์อื่นที่แตกต่างกันมากพอที่จะทำให้โปรแกรมของคุณเริ่มหยุดทำงานอย่างอธิบายไม่ได้

ผู้ที่ต้องการรับประกันว่าปัญหาเหล่านี้จะไม่เกิดขึ้นกับพวกเขาจะต้องปฏิบัติตามกฎที่ว่าหากใครก็ตามที่เคยประกาศอินสแตนซ์ของคลาสบางประเภทมาแล้วจะต้องไม่ประกาศอินสแตนซ์อื่นอีกในโปรแกรมใด ๆ ที่เขียนขึ้น โดยใครก็ได้ แน่นอนว่ามีวิธีแก้ปัญหาในการใช้newtypeเพื่อประกาศอินสแตนซ์ใหม่ แต่อย่างน้อยก็มักจะเกิดความไม่สะดวกเล็กน้อยและบางครั้งก็เป็นเรื่องสำคัญ ดังนั้นในแง่นี้ผู้ที่เขียนอินสแตนซ์เด็กกำพร้าโดยเจตนาจึงค่อนข้างไม่สุภาพ

แล้วปัญหานี้ควรทำอย่างไร? ค่ายต่อต้านเด็กกำพร้ากล่าวว่าคำเตือน GHC เป็นข้อบกพร่องต้องเป็นข้อผิดพลาดที่ปฏิเสธความพยายามใด ๆ ในการประกาศอินสแตนซ์เด็กกำพร้า ในระหว่างนี้เราต้องฝึกวินัยในตนเองและหลีกเลี่ยงสิ่งเหล่านี้โดยเสียค่าใช้จ่ายทั้งหมด

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

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

ข้อสรุปของฉันจากทั้งหมดนี้ก็คืออย่างน้อยในขณะนี้ฉันขอแนะนำอย่างยิ่งให้คุณหลีกเลี่ยงการประกาศอินสแตนซ์เด็กกำพร้าใด ๆ โดยคำนึงถึงผู้อื่นหากไม่มีเหตุผลอื่น ใช้ไฟล์newtype.


4
โดยเฉพาะอย่างยิ่งปัญหานี้จะเพิ่มมากขึ้นเรื่อย ๆ กับการเติบโตของห้องสมุด ด้วยไลบรารีมากกว่า 2200 ไลบรารีบน Haskell และโมดูลแต่ละโมดูลนับ 10 นับพันความเสี่ยงในการหยิบอินสแตนซ์เพิ่มขึ้นอย่างมาก
Don Stewart

16
Re: "ฉันคิดว่าวิธีแก้ปัญหาที่เหมาะสมคือการเพิ่มส่วนขยายให้กับกลไกการนำเข้าของ Haskell ที่จะควบคุมการนำเข้าอินสแตนซ์" ในกรณีที่ความคิดนี้สนใจใคร ๆ ก็ควรดูตัวอย่างภาษาสกาล่า มันมีคุณสมบัติเช่นนี้มากสำหรับการควบคุมขอบเขตของ 'นัย' ซึ่งสามารถใช้งานได้เหมือนกับอินสแตนซ์ประเภทคลาส
Matt

5
ซอฟต์แวร์ของฉันเป็นแอปพลิเคชันแทนที่จะเป็นไลบรารีดังนั้นความเป็นไปได้ที่จะทำให้เกิดปัญหากับนักพัฒนารายอื่นจึงค่อนข้างเป็นศูนย์ คุณสามารถพิจารณาโมดูลผู้เผยแพร่แอปพลิเคชันและส่วนที่เหลือของโมดูลเป็นไลบรารี แต่ถ้าฉันจะแจกจ่ายไลบรารีก็จะไม่มีผู้เผยแพร่และอินสแตนซ์ที่ไม่มีเจ้าของ แต่ถ้าฉันย้ายอินสแตนซ์ไปยังโมดูลอื่น ๆ ไลบรารีจะจัดส่งโดยไม่จำเป็นต้องพึ่งพา HStringTemplate ดังนั้นในกรณีนี้ฉันคิดว่าเด็กกำพร้าก็โอเค แต่ฉันจะปฏิบัติตามคำแนะนำของคุณหากฉันพบปัญหาเดียวกันในบริบทที่แตกต่างกัน
Dan Dyer

1
ดูเหมือนจะเป็นแนวทางที่สมเหตุสมผล สิ่งเดียวที่ต้องระวังคือหากผู้สร้างโมดูลที่คุณนำเข้าเพิ่มอินสแตนซ์นี้ในเวอร์ชันที่ใหม่กว่า หากอินสแตนซ์นั้นเหมือนกับของคุณคุณจะต้องลบการประกาศอินสแตนซ์ของคุณเอง หากอินสแตนซ์นั้นแตกต่างจากของคุณคุณจะต้องใส่ newtype wrapper รอบ ๆ ประเภทของคุณซึ่งอาจเป็นการปรับโครงสร้างโค้ดของคุณอย่างมีนัยสำคัญ
Yitz

@Matt: น่าแปลกใจที่ Scala ได้รับสิ่งนี้โดยที่ Haskell ไม่ได้! (ยกเว้นแน่นอนว่า Scala ไม่มีไวยากรณ์ชั้นหนึ่งสำหรับเครื่องจักรประเภทคลาสซึ่งแย่กว่านั้น ... )
Erik Kaplun

44

หยุดเตือนนี้เลย!

คุณอยู่ใน บริษัท ที่ดี Conal ทำใน "TypeCompose" "chp-mtl" และ "chp-transformers" ทำ "control-monad-exception-mtl" และ "control-monad-exception-monadsfd" เป็นต้น

btw คุณอาจรู้สิ่งนี้แล้ว แต่สำหรับผู้ที่ไม่ทำและสะดุดคำถามของคุณในการค้นหา:

{-# OPTIONS_GHC -fno-warn-orphans #-}

แก้ไข:

ฉันรับทราบว่าปัญหาที่ Yitz กล่าวถึงในคำตอบของเขาเป็นปัญหาจริง อย่างไรก็ตามฉันเห็นว่าไม่ได้ใช้อินสแตนซ์ที่ไม่มีที่มาเป็นปัญหาเช่นกันและฉันพยายามเลือก "ความชั่วร้ายอย่างน้อยที่สุดทั้งหมด" ซึ่งก็คือการใช้อินสแตนซ์เด็กกำพร้าอย่างรอบคอบ

ฉันใช้เครื่องหมายอัศเจรีย์ในคำตอบสั้น ๆ เท่านั้นเพราะคำถามของคุณแสดงให้เห็นว่าคุณทราบปัญหาดีอยู่แล้ว ไม่งั้นคงไม่ค่อยกระตือรือร้น :)

ความเบี่ยงเบนเล็กน้อย แต่สิ่งที่ฉันเชื่อคือทางออกที่สมบูรณ์แบบในโลกที่สมบูรณ์แบบโดยไม่มีการประนีประนอม:

ฉันเชื่อว่าปัญหาที่ Yitz กล่าวถึง (ไม่รู้ว่าจะเลือกอินสแตนซ์ใด) สามารถแก้ไขได้ในระบบการเขียนโปรแกรมแบบ "องค์รวม" โดยที่:

  • คุณไม่ได้แก้ไขเพียงไฟล์ข้อความดั้งเดิม แต่ได้รับการช่วยเหลือจากสภาพแวดล้อมมากกว่า (เช่นการเติมโค้ดให้เสร็จสิ้นจะแนะนำเฉพาะสิ่งที่เกี่ยวข้องเป็นต้น)
  • ภาษา "ระดับล่าง" ไม่มีการสนับสนุนพิเศษสำหรับคลาสประเภทและตารางฟังก์ชันจะถูกส่งต่อไปอย่างชัดเจน
  • แต่สภาพแวดล้อมการเขียนโปรแกรม "ระดับที่สูงขึ้น" จะแสดงโค้ดในลักษณะเดียวกับวิธีการนำเสนอของ Haskell ในตอนนี้ (โดยปกติคุณจะไม่เห็นตารางฟังก์ชันที่ส่งต่อไป) และเลือกคลาสประเภทที่ชัดเจนให้คุณเมื่อเห็นได้ชัด (สำหรับ ตัวอย่างเช่น Functor ทุกกรณีมีทางเลือกเดียวเท่านั้น) และเมื่อมีหลายตัวอย่าง (การบีบอัดรายการการใช้งานหรือการประยุกต์ใช้รายชื่อผู้สมัครครั้งแรก / ครั้งสุดท้าย / การยกอาจเป็น Monoid) จะช่วยให้คุณสามารถเลือกอินสแตนซ์ที่จะใช้
  • ไม่ว่าในกรณีใดก็ตามแม้ว่าจะมีการเลือกอินสแตนซ์ให้คุณโดยอัตโนมัติ แต่สภาพแวดล้อมก็ช่วยให้คุณเห็นได้อย่างง่ายดายว่าอินสแตนซ์ใดถูกใช้ด้วยอินเทอร์เฟซที่ใช้งานง่าย (ไฮเปอร์ลิงก์หรือโฮเวอร์อินเทอร์เฟซหรือบางอย่าง)

กลับมาจากโลกแฟนตาซี (หรือหวังว่าจะเป็นอนาคต) ตอนนี้: ฉันขอแนะนำให้พยายามหลีกเลี่ยงอินสแตนซ์เด็กกำพร้าในขณะที่ยังคงใช้เมื่อคุณ "ต้องการจริงๆ"


5
ใช่ แต่เนื้อหาที่เกิดขึ้นแต่ละครั้งเป็นความผิดพลาดของคำสั่งบางอย่าง อินสแตนซ์ที่ไม่ดีใน control-monad-except-mtl และ monads-fd สำหรับอย่างใดอย่างหนึ่งในใจ มันจะไม่เป็นการรบกวนน้อยกว่าที่แต่ละโมดูลจะถูกบังคับให้กำหนดประเภทของตัวเองหรือจัดหาเครื่องห่อแบบใหม่ เกือบทุกอินสแตนซ์ของ orphan เป็นเรื่องน่าปวดหัวที่รอให้เกิดขึ้นและหากไม่มีอะไรอีกแล้วคุณจะต้องเฝ้าระวังอย่างต่อเนื่องเพื่อให้แน่ใจว่ามีการนำเข้าหรือไม่ตามความเหมาะสม
Edward KMETT

2
ขอบคุณ. ฉันคิดว่าฉันจะใช้มันในสถานการณ์เฉพาะนี้ แต่ต้องขอบคุณ Yitz ตอนนี้ฉันรู้สึกซาบซึ้งมากขึ้นว่าปัญหาใดที่พวกเขาสามารถทำให้เกิดได้
Dan Dyer

37

อินสแตนซ์ Orphan เป็นสิ่งที่น่ารำคาญ แต่ในความคิดของฉันมันก็จำเป็นในบางครั้ง ฉันมักจะรวมไลบรารีที่ประเภทมาจากไลบรารีหนึ่งและคลาสมาจากไลบรารีอื่น แน่นอนว่าผู้เขียนของไลบรารีเหล่านี้ไม่สามารถคาดหวังได้ว่าจะจัดหาอินสแตนซ์สำหรับทุกประเภทและคลาสที่เป็นไปได้ ดังนั้นฉันต้องจัดหาพวกเขาและพวกเขาก็เป็นเด็กกำพร้า

ความคิดที่ว่าคุณควรรวมประเภทเป็นประเภทใหม่เมื่อคุณต้องการให้อินสแตนซ์เป็นความคิดที่มีประโยชน์ทางทฤษฎี แต่มันก็น่าเบื่อเกินไปในหลาย ๆ สถานการณ์ เป็นแนวคิดที่คนที่ไม่ได้เขียนโค้ด Haskell เพื่อหาเลี้ยงชีพ :)

ดังนั้นจัดเตรียมอินสแตนซ์สำหรับเด็กกำพร้า พวกเขาไม่เป็นอันตราย
หากคุณสามารถขัดข้อง ghc กับอินสแตนซ์ orphan นั่นคือข้อบกพร่องและควรได้รับการรายงานเช่นนี้ (ข้อผิดพลาด ghc มี / มีเกี่ยวกับการตรวจไม่พบหลายอินสแตนซ์ไม่ใช่เรื่องยากที่จะแก้ไข)

แต่โปรดทราบว่าในอนาคตอาจมีคนอื่นเพิ่มอินสแตนซ์ตามที่คุณมีอยู่แล้วและคุณอาจได้รับข้อผิดพลาด (เวลาคอมไพล์)


2
ตัวอย่างที่ดีคือ(Ord k, Arbitrary k, Arbitrary v) ⇒ Arbitrary (Map k v)เมื่อใช้ QuickCheck
Erik Kaplun

17

ในกรณีนี้ฉันคิดว่าการใช้อินสแตนซ์เด็กกำพร้าก็ใช้ได้ หลักการทั่วไปสำหรับฉันคือ - คุณสามารถกำหนดอินสแตนซ์ได้หากคุณ "เป็นเจ้าของ" คลาสประเภทข้อมูลหรือหากคุณ "เป็นเจ้าของ" ประเภทข้อมูล (หรือส่วนประกอบบางอย่างเช่นอินสแตนซ์สำหรับบางที MyData ก็ใช้ได้เช่นกัน อย่างน้อยบางครั้ง) ภายในข้อ จำกัด เหล่านั้นที่คุณตัดสินใจใส่อินสแตนซ์คือธุรกิจของคุณเอง

นอกจากนี้ยังมีข้อยกเว้นอีกประการหนึ่ง - หากคุณไม่ได้เป็นเจ้าของคลาสประเภทข้อมูลหรือประเภทข้อมูล แต่กำลังสร้างไบนารีและไม่ใช่ไลบรารีก็ไม่เป็นไรเช่นกัน


5

(ฉันรู้ว่าฉันไปงานปาร์ตี้สาย แต่นี่อาจเป็นประโยชน์สำหรับคนอื่น ๆ )

คุณสามารถเก็บอินสแตนซ์เด็กกำพร้าไว้ในโมดูลของตนเองได้หากใครนำเข้าโมดูลนั้นโดยเฉพาะเนื่องจากต้องการและสามารถหลีกเลี่ยงการนำเข้าได้หากทำให้เกิดปัญหา


3

ตามบรรทัดเหล่านี้ฉันเข้าใจไลบรารี WRT ตำแหน่งของค่ายอินสแตนซ์ต่อต้าน orphan แต่สำหรับเป้าหมายที่เรียกใช้งานได้อินสแตนซ์ orphan ไม่ควรใช่หรือไม่


3
ในแง่ของการไม่สุภาพต่อผู้อื่นคุณคิดถูกแล้ว แต่คุณกำลังเปิดโอกาสให้ตัวเองเผชิญกับปัญหาในอนาคตหากมีการกำหนดอินสแตนซ์เดียวกันในอนาคตไว้ที่ไหนสักแห่งในห่วงโซ่การพึ่งพาของคุณ ดังนั้นในกรณีนี้คุณจะต้องตัดสินใจว่าคุ้มที่จะเสี่ยงหรือไม่
Yitz

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