ทำไม (หรือทำไมไม่) เป็นประเภทที่มีอยู่ถือว่าการปฏิบัติที่ไม่ดีในการเขียนโปรแกรมการทำงาน?


43

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

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

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับประเภทการดำรงอยู่ที่นี่ ("ถ้าคุณกล้า .. ")


8
@RobertHarvey ฉันพบโพสต์บล็อกที่ทำให้ฉันคิดเกี่ยวกับคำถามนี้เป็นครั้งแรก: Haskell Antipattern: อัตถิภาวนิยม Typeclass นอกจากนี้กระดาษ Joey Adams กล่าวถึง poblems บางอย่างที่มีอยู่ในส่วน 3.1 หากคุณมีข้อโต้แย้งที่ขัดแย้งกันโปรดแบ่งปันพวกเขา
Petr Pudlák

5
@atupapa.com
CA McCann

10
หากคุณต้องการทราบว่าทำไมผู้เขียนโพสต์บล็อกบางคนแสดงความคิดเห็นดังนั้นบุคคลที่คุณควรถามคือผู้เขียน
Eric Lippert

6
@Poleole นั่นเป็นเรื่องของความเห็น การใช้ Haskell มานานหลายปีฉันไม่สามารถจินตนาการถึงการใช้ภาษาที่ใช้งานได้ซึ่งไม่มีระบบการพิมพ์ที่แข็งแกร่ง
Petr Pudlák

4
@Polemmy: มนต์ของ Haskell คือ "ถ้ามันคอมไพล์มันใช้งานได้" Erlangs มนต์คือ "ปล่อยให้มันพัง" การพิมพ์แบบไดนามิกนั้นดีเหมือนกาว แต่โดยส่วนตัวแล้วฉันจะไม่สร้างสิ่งต่าง ๆ โดยใช้แค่กาว
Den

คำตอบ:


8

ประเภทที่มีอยู่จะไม่ถือว่าเป็นการปฏิบัติที่ไม่ดีในการเขียนโปรแกรมการทำงาน ฉันคิดว่าสิ่งที่ทำให้คุณสะดุดคือหนึ่งในการใช้งานที่มีการอ้างถึงมากที่สุดสำหรับอัตถิภาวนิยมคือAntipattern typeclassซึ่งผู้คนจำนวนมากเชื่อว่าเป็นการฝึกฝนที่ไม่ดี

รูปแบบนี้มักจะถูกตัดออกเป็นคำตอบสำหรับคำถามว่าจะมีรายการองค์ประกอบที่พิมพ์ต่างกันได้อย่างไรซึ่งทั้งหมดนั้นใช้ typeclass เดียวกัน ตัวอย่างเช่นคุณอาจต้องการมีรายการค่าที่มีShowอินสแตนซ์:

{-# LANGUAGE ExistentialTypes #-}

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
    area (AnyShape x) = area x

example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]

ปัญหาเกี่ยวกับรหัสเช่นนี้คือ:

  1. การดำเนินการที่เป็นประโยชน์เพียงอย่างเดียวที่คุณสามารถทำได้AnyShapeคือการได้รับพื้นที่ของมัน
  2. คุณยังต้องใช้ตัวAnyShapeสร้างเพื่อนำชนิดรูปร่างหนึ่งชนิดมาเป็นAnyShapeประเภท

ดังนั้นเมื่อปรากฎว่าโค้ดส่วนนั้นไม่ได้สิ่งที่คุณสั้นกว่านี้มา:

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]

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


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

วิธีการทางคณิตศาสตร์ที่มากขึ้นของการวางสิ่งนี้ชี้ให้เห็นว่าAnyShapeประเภทจากด้านบนและDoubleมีisomorphic - มีลักษณะเป็น "การแปลงแบบไม่สูญเสีย" ระหว่างพวกเขา (ดีประหยัดสำหรับความแม่นยำจุดลอยตัว):

forward :: AnyShape -> Double
forward = area

backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))

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


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

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


นั่นไม่ใช่มอร์ฟิซึ่มส์
Ryan Reich

2

ฉันไม่คุ้นเคยกับ Haskell ดังนั้นฉันจะพยายามตอบคำถามทั่วไปในฐานะผู้พัฒนา C #

หลังจากอ่านหนังสือแล้วปรากฎว่า:

  1. Java wildcard คล้ายกับชนิดที่มีอยู่:

    ความแตกต่างระหว่างประเภทการดำรงอยู่ของสกาล่าและสัญลักษณ์ตัวแทนของ Java ตามตัวอย่าง

  2. อักขระตัวแทนไม่ได้ใช้งานใน C # อย่างสมบูรณ์: สนับสนุนความแปรปรวนทั่วไป แต่ความแปรปรวนของไซต์การโทรไม่ได้:

    C # Generics: สัญลักษณ์แทน

  3. คุณอาจไม่จำเป็นต้องใช้คุณสมบัตินี้ทุกวัน แต่เมื่อคุณจะรู้สึกว่ามัน (เช่นต้องแนะนำประเภทพิเศษเพื่อให้สิ่งต่าง ๆ ใช้งานได้):

    สัญลักษณ์แทนในข้อ จำกัด ทั่วไป C #

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

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