Haskell: Typeclass กับการส่งผ่านฟังก์ชัน


16

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

class Eq a where 
  (==)                  :: a -> a -> Bool

และใช้มันในฟังก์ชั่นอื่น ๆ เพื่อระบุอาร์กิวเมนต์ชนิดต้องเป็นตัวอย่างของEq:

elem                    :: (Eq a) => a -> [a] -> Bool

เราไม่สามารถกำหนดelemฟังก์ชั่นของเราโดยไม่ต้องใช้ตัวพิมพ์ดีดและส่งผ่านอาร์กิวเมนต์ฟังก์ชันที่ทำงานได้หรือไม่


2
ที่เรียกว่าพจนานุกรมผ่าน คุณสามารถคิดข้อ จำกัด typeclass เป็นข้อโต้แย้งโดยนัย
Poscat

2
คุณสามารถทำเช่นนั้นได้ แต่เห็นได้ชัดว่าสะดวกกว่าที่ไม่ต้องผ่านฟังก์ชั่นและให้ใช้ "มาตรฐาน" อย่างใดอย่างหนึ่งตามประเภท
Robin Zigmond

2
คุณสามารถทำให้มันเป็นอย่างนั้นได้ แต่ฉันขอยืนยันว่ามีข้อได้เปรียบที่สำคัญอย่างน้อยหนึ่งข้อ: ความสามารถในการเขียนฟังก์ชัน polymorphic ที่ทำงานกับประเภทใดก็ได้ที่ใช้ "อินเตอร์เฟส" หรือชุดคุณลักษณะเฉพาะ ฉันคิดว่าข้อ จำกัด ของ typeclass แสดงออกอย่างชัดเจนในวิธีที่ผ่านการขัดแย้งของฟังก์ชั่นพิเศษไม่ได้ โดยเฉพาะอย่างยิ่งเพราะ "กฎหมาย" (โดยนัยเศร้า) ซึ่งมีประเภทของวิชาเรียนจำนวนมากที่จะต้องพึงพอใจ Monad mจำกัด กล่าวว่าฉันมากกว่าที่ผ่านอาร์กิวเมนต์ของฟังก์ชันที่เพิ่มขึ้นของประเภทและa -> m a m a -> (a -> m b) -> m b
Robin Zigmond


1
TypeApplicationsขยายช่วยให้คุณทำอาร์กิวเมนต์นัยอย่างชัดเจน (==) @Int 3 5เปรียบเทียบ3และ5เจาะจงเป็นIntค่า คุณสามารถคิดว่า@Intเป็นกุญแจสำคัญในพจนานุกรมของฟังก์ชั่นความเท่าเทียมกันเฉพาะประเภทมากกว่าIntฟังก์ชั่นการเปรียบเทียบเฉพาะตัวเอง
chepner

คำตอบ:


19

ใช่. สิ่งนี้เรียกว่า "รูปแบบการผ่านของพจนานุกรม" บางครั้งเมื่อฉันทำสิ่งที่ยากลำบากโดยเฉพาะฉันต้องแยกประเภทของคลาสและเปลี่ยนเป็นพจนานุกรมเนื่องจากการส่งผ่านพจนานุกรมมีประสิทธิภาพมากกว่า1แต่บ่อยครั้งที่ค่อนข้างยุ่งยาก บางครั้งฉันใช้รูปแบบการส่งผ่านพจนานุกรมในภาษาที่ไม่ใช่ Haskell เพื่อจำลองแบบจำลอง (แต่ได้เรียนรู้ว่าโดยปกติแล้วจะไม่เป็นความคิดที่ดีเท่าที่ฟัง)

แน่นอนเมื่อใดก็ตามที่มีความแตกต่างในพลังการแสดงออกมีการแลกเปลี่ยน ในขณะที่คุณสามารถใช้ API ที่กำหนดได้หลายวิธีหากมีการเขียนโดยใช้ DPS API จะได้รับข้อมูลเพิ่มเติมหากคุณไม่สามารถ วิธีหนึ่งที่แสดงให้เห็นในทางปฏิบัตินั้นData.Setขึ้นอยู่กับความจริงที่ว่ามีเพียงหนึ่งOrdพจนานุกรมต่อหนึ่งประเภท Setร้านค้าองค์ประกอบเรียงตามOrdและถ้าคุณสร้างชุดกับหนึ่งในพจนานุกรมแล้วแทรกองค์ประกอบการใช้ที่แตกต่างกันอย่างใดอย่างหนึ่งตามที่จะเป็นไปได้ด้วย DPS คุณสามารถทำลายSet's คงที่และทำให้เกิดการผิดพลาด ปัญหาที่ไม่ซ้ำกันนี้สามารถบรรเทาได้ด้วยphantom existentialพิมพ์เพื่อทำเครื่องหมายพจนานุกรม แต่ด้วยความซับซ้อนที่น่ารำคาญใน API นอกจากนี้ยังแสดงให้เห็นในแบบเดียวกับTypeableAPI

ความเป็นเอกลักษณ์ไม่ได้เกิดขึ้นบ่อยนัก สิ่งที่ดีในการพิมพ์รหัสสำหรับคุณ ตัวอย่างเช่น,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

ซึ่งใช้ "โปรเซสเซอร์" สองตัวซึ่งรับอินพุตและอาจให้เอาต์พุตและเชื่อมต่อเข้าด้วยกันNothingโดยไม่ต้องเขียนจะต้องเขียนใน DPS ดังนี้:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

เราจำเป็นต้องสะกดประเภทที่เราใช้อีกครั้งแม้ว่าเราจะสะกดมันออกมาแล้วในประเภทของลายเซ็นและถึงแม้จะซ้ำซ้อนเพราะคอมไพเลอร์รู้ทุกประเภทแล้ว เนื่องจากมีเพียงวิธีเดียวในการสร้างSemigroupประเภทที่กำหนดคอมไพเลอร์จึงสามารถทำได้เพื่อคุณ สิ่งนี้มีเอฟเฟกต์ประเภท "ดอกเบี้ยทบต้น" เมื่อคุณเริ่มกำหนดอินสแตนซ์พารามิเตอร์จำนวนมากและใช้โครงสร้างประเภทของคุณในการคำนวณสำหรับคุณเช่นเดียวกับในData.Functor.*combinators และนี่จะใช้เพื่อผลที่ยอดเยี่ยมโดยderiving viaที่คุณสามารถรับ โครงสร้างพีชคณิต "มาตรฐาน" ของประเภทที่เขียนเพื่อคุณ

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

-

1 U nless คุณใช้reflectionในกรณีที่พวกเขากลายเป็นเทียบเท่าในอำนาจ - แต่reflectionยังสามารถที่จะยุ่งยากในการใช้งาน



ฉันสนใจ Fundeps ที่แสดงออกผ่าน DPS มาก คุณรู้จักแหล่งข้อมูลที่แนะนำในเรื่องนี้หรือไม่? อย่างไรก็ตามคำอธิบายที่เข้าใจได้มาก
บ๊อบ

@ บ๊อบไม่ใช่คนฉับพลัน แต่มันเป็นการสำรวจที่น่าสนใจ อาจถามคำถามใหม่เกี่ยวกับมัน?
luqui

5

ใช่. (เรียกว่าการผ่านพจนานุกรม) นั้นโดยทั่วไปแล้วสิ่งที่คอมไพเลอร์ทำกับประเภทของคลาสนั้น สำหรับฟังก์ชันนั้นทำตามตัวอักษรมันจะออกมาเป็นแบบนี้:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

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

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

โทรอยู่ในขณะนี้เทียบเท่ากับelemBy2 (x ==) xselem x xs

... โอ้เดี๋ยวก่อน anyนั่นเป็นเพียง (และในความเป็นจริงในไลบรารีมาตรฐาน,elem = any . (==) .)


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