เลนส์, fclabels, data-accessor - ไลบรารี่ใดสำหรับการเข้าถึงโครงสร้างและการกลายพันธุ์จะดีกว่า


173

มีไลบรารียอดนิยมอย่างน้อยสามแห่งสำหรับการเข้าถึงและจัดการฟิลด์ของระเบียน สิ่งที่ฉันรู้คือ: data-accessor, fclabels และเลนส์

โดยส่วนตัวแล้วฉันเริ่มด้วย data-accessor และตอนนี้ฉันก็ใช้มันอยู่ อย่างไรก็ตามเมื่อเร็ว ๆ นี้ใน haskell-cafe มีความเห็นของ fclabels เป็นที่เหนือกว่า

ดังนั้นฉันสนใจที่จะเปรียบเทียบห้องสมุดทั้งสาม (และอาจมากกว่า)


3
ณ วันนี้lensแพ็คเกจมีฟังก์ชั่นและเอกสารที่สมบูรณ์ที่สุดดังนั้นหากคุณไม่คำนึงถึงความซับซ้อนและการพึ่งพามันเป็นวิธีที่จะไป
แบบแยกส่วน

คำตอบ:


200

มีห้องสมุดอย่างน้อย 4 แห่งที่ฉันรู้เรื่องการจัดเตรียมเลนส์

ความคิดของเลนส์คือมันให้บางสิ่งบางอย่างผิดปกติไป

data Lens a b = Lens (a -> b) (b -> a -> a)

จัดให้มีสองฟังก์ชั่น: ทะเยอทะยานและ Setter

get (Lens g _) = g
put (Lens _ s) = s

ภายใต้กฎหมายสามข้อ:

อย่างแรกคือถ้าคุณใส่อะไรคุณสามารถเอามันออกมาได้

get l (put l b a) = b 

ประการที่สองที่ได้รับแล้วการตั้งค่าจะไม่เปลี่ยนคำตอบ

put l (get l a) a = a

และครั้งที่สามการวางสองครั้งเหมือนกับการวางครั้งเดียวหรือมากกว่าการวางครั้งที่สองจะชนะ

put l b1 (put l b2 a) = put l b1 a

โปรดทราบว่าระบบประเภทไม่เพียงพอที่จะตรวจสอบกฎหมายเหล่านี้ให้คุณดังนั้นคุณต้องให้แน่ใจว่าพวกเขาด้วยตนเองไม่ว่าคุณจะใช้เลนส์แบบใด

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

โดยที่ในใจเราสามารถหันไปใช้การใช้งานที่แตกต่างกัน:

การใช้งาน

fclabels

fclabelsอาจจะเป็นเหตุผลที่ง่ายที่สุดเกี่ยวกับไลบรารี่ของเลนส์เพราะมันa :-> bสามารถแปลโดยตรงไปยังประเภทของเลนส์ข้างต้น มันให้อินสแตนซ์ของหมวดหมู่(:->)ซึ่งมีประโยชน์เพราะมันช่วยให้คุณแต่งเลนส์ได้ นอกจากนี้ยังมีPointประเภทที่ไม่มีกฎหมายซึ่งพูดถึงความคิดของเลนส์ที่ใช้ที่นี่และระบบประปาบางอย่างสำหรับการจัดการกับมอร์ฟ

หนึ่งอุปสรรคต่อการนำไปใช้ของfclabelsคือแพคเกจหลักรวมถึงการประปาแม่แบบ - haskell ดังนั้นแพคเกจไม่ Haskell 98 และมันยังต้องมีTypeOperatorsส่วนขยาย(ค่อนข้างไม่ขัดแย้ง)

ข้อมูลการเข้าถึง

[แก้ไข: data-accessorคือไม่ใช้เป็นตัวแทนนี้ data-lensแต่ได้ย้ายไปยังรูปแบบคล้ายกับว่า แม้ว่าฉันจะเก็บความเห็นนี้ไว้]

ข้อมูลการเข้าถึงค่อนข้างนิยมมากขึ้นกว่าfclabelsในส่วนหนึ่งเพราะมันเป็น Haskell 98 แต่ทางเลือกของการแสดงภายในทำให้ฉันโยนขึ้นในปากของฉันนิด ๆ หน่อย ๆ

ประเภทที่Tใช้เพื่อแสดงถึงเลนส์ถูกกำหนดภายในเป็น

newtype T r a = Cons { decons :: a -> r -> (a, r) }

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

ที่กล่าวว่า Henning ได้รวมเทมเพลต-haskell plumbing เพื่อสร้าง accessors ให้คุณโดยอัตโนมัติในแพ็คเกจ' data-accessor-template ' แยกต่างหาก

มันมีข้อดีของแพคเกจชุดใหญ่ที่จ้างมาแล้วเป็น Haskell 98 และให้Categoryอินสแตนซ์ที่สำคัญทั้งหมดดังนั้นหากคุณไม่ใส่ใจกับวิธีการทำไส้กรอกแพ็คเกจนี้เป็นตัวเลือกที่สมเหตุสมผล .

เลนส์

ถัดไปมีเลนส์แพคเกจซึ่งตั้งข้อสังเกตว่าเลนส์ที่สามารถให้ homomorphism รัฐ monad ระหว่างสอง monads รัฐโดย definining เลนส์โดยตรงเป็น homomorphisms monad ดังกล่าว

หากจริง ๆ แล้วใส่ใจที่จะจัดทำเลนส์สำหรับประเภทของเลนส์พวกเขาจะมีประเภทที่ 2:

newtype Lens s t = Lens (forall a. State t a -> State s a)

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

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

นอกจากนี้เงื่อนไขด้านข้างที่ระบุไว้ในตอนเริ่มไม่มีการแสดงออกที่ดีในแบบฟอร์มนี้ เช่นเดียวกับ 'fclabels' สิ่งนี้ให้วิธีการ template-haskell สำหรับสร้างเลนส์โดยอัตโนมัติสำหรับประเภทบันทึกโดยตรงในแพ็คเกจหลัก

เนื่องจากการขาดCategoryอินสแตนซ์การเข้ารหัสแบบบาร็อคและความต้องการของเทมเพลต - ฮาเซลในแพ็คเกจหลักจึงเป็นการใช้งานที่ฉันโปรดปรานน้อยที่สุด

ข้อมูลเลนส์

[แก้ไข: ตั้งแต่ 1.8.0, สิ่งเหล่านี้ได้ย้ายจากแพ็คเกจ comonad-transformers ไปยัง data-lens]

data-lensแพ็คเกจของฉันมีเลนส์ในแง่ของร้านค้า comonad

newtype Lens a b = Lens (a -> Store b a)

ที่ไหน

data Store b a = Store (b -> a) b

ขยายนี้เทียบเท่ากับ

newtype Lens a b = Lens (a -> (b, b -> a))

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

นอกจากนี้ยังมีเหตุผลทางทฤษฎีที่ดีสำหรับการเป็นตัวแทนนี้เพราะส่วนย่อยของค่า 'เลนส์' ที่ตอบสนองกฎหมาย 3 ข้อที่ระบุไว้ในตอนต้นของการตอบสนองนี้เป็นเลนส์ที่แม่นยำซึ่งฟังก์ชั่นห่อเป็น 'comonad coalgebra' สำหรับ comonad . วิธีนี้แปลงกฎขน 3 แบบสำหรับเลนส์lลงไปเทียบเท่า 2 จุดที่ไม่มีค่าเทียบเท่า:

extract . l = id
duplicate . l = fmap l . l

วิธีการนี้เป็นครั้งแรกที่ตั้งข้อสังเกตและอธิบายไว้ในรัสเซลของคอนเนอร์FunctorคือการLensเป็นApplicativeคือการBiplate: แนะนำ multiplateและถูกblogged เกี่ยวกับขึ้นอยู่กับ preprintโดยเจเรมีชะนี

นอกจากนี้ยังรวมถึงจำนวนของ combinators Data.Mapสำหรับการทำงานกับเลนส์อย่างเคร่งครัดและบางเลนส์สต็อกสำหรับภาชนะบรรจุเช่น

ดังนั้นเลนส์ในdata-lensรูปแบบ a Category(ซึ่งแตกต่างจากlensesแพ็คเกจ) คือ Haskell 98 (ซึ่งแตกต่างจากfclabels/ lenses), มีเหตุผล (ไม่เหมือนกับปลายด้านหลังdata-accessor) และให้การใช้งานที่มีประสิทธิภาพมากขึ้นเล็กน้อยdata-lens-fdมอบฟังก์ชันการทำงานกับ MonadState สำหรับผู้ที่เต็มใจ ของ Haskell 98 และเครื่องจักรแม่แบบ Haskell data-lens-templateอยู่ในขณะนี้ผ่านทาง

อัพเดท 6/28/2012: กลยุทธ์การติดตั้งเลนส์อื่น ๆ

เลนส์มอร์ฟ

มีการเข้ารหัสเลนส์อีกสองค่าที่ควรพิจารณา วิธีแรกให้วิธีการทางทฤษฎีที่ดีในการดูเลนส์เป็นวิธีการแบ่งโครงสร้างเป็นค่าของฟิลด์และ 'ทุกอย่างอื่น'

ป.ร. ให้ไว้ประเภทสำหรับสัณฐาน

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

สมาชิกที่ถูกต้องพึงพอใจhither . yon = idและyon . hither = id

เราสามารถเป็นตัวแทนของเลนส์ด้วย:

data Lens a b = forall c. Lens (Iso a (b,c))

สิ่งเหล่านี้มีประโยชน์หลักเป็นวิธีคิดเกี่ยวกับความหมายของเลนส์และเราสามารถใช้เป็นเครื่องมือในการอธิบายเหตุผลเลนส์อื่น ๆ

เลนส์แวนแลนลาโฮเฟน

เราสามารถสร้างโมเดลของเลนส์เพื่อที่พวกเขาจะสามารถประกอบ(.)และidแม้ไม่มีCategoryตัวอย่างโดยใช้

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

เป็นประเภทสำหรับเลนส์ของเรา

จากนั้นการกำหนดเลนส์นั้นง่ายเหมือน:

_2 f (a,b) = (,) a <$> f b

และคุณสามารถตรวจสอบได้ด้วยตัวคุณเองว่าองค์ประกอบฟังก์ชั่นคือองค์ประกอบเลนส์

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

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

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

Lensฉันที่กำหนดไว้ข้างต้นเป็นจริง_2LensFamily

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

ฉันได้เขียนห้องสมุดที่มีเลนส์, ตระกูลเลนส์และภาพรวมอื่น ๆ รวมถึง getters, setters, folds และ traversals มันมีอยู่ในการแฮ็กเป็น lensแพคเกจ

ข้อดีอีกอย่างของวิธีนี้คือผู้ดูแลห้องสมุดสามารถสร้างเลนส์ในสไตล์นี้ในห้องสมุดของคุณได้โดยไม่ต้องพึ่งพาห้องสมุดเลนส์ใด ๆ เลยเพียงแค่จัดหาฟังก์ชั่นที่มีประเภทFunctor f => (b -> f b) -> a -> f aสำหรับประเภท 'a' และ 'b' สิ่งนี้ช่วยลดค่าใช้จ่ายในการนำไปใช้อย่างมาก

เนื่องจากคุณไม่จำเป็นต้องใช้แพคเกจเพื่อกำหนดเลนส์ใหม่มันต้องใช้ความกดดันอย่างมากจากความกังวลก่อนหน้านี้ของฉันเกี่ยวกับการรักษาไลบรารี่ Haskell 98


28
ฉันชอบ fclabels สำหรับวิธีการมองโลกในแง่ดี:->
Tener

3
บทความInessential Guide to data-accessorและInessential Guide to fclabelsอาจจะเป็นสิ่งสำคัญ
hvr

10
Haskell 1998 นั้นสำคัญหรือไม่? เพราะมันทำให้การพัฒนาคอมไพเลอร์ง่ายขึ้น? และเราไม่ควรเปลี่ยนมาพูดถึง Haskell 2010 แทนใช่มั้ย
yairchu

55
ไม่นะ! ฉันเป็นผู้แต่งดั้งเดิมจากdata-accessorนั้นก็ส่งต่อให้ Henning และหยุดให้ความสนใจ การa -> r -> (a,r)เป็นตัวแทนทำให้ฉันรู้สึกไม่สบายใจและการใช้งานดั้งเดิมของฉันก็เหมือนกับLensประเภทของคุณ Heeennnninngg !!
luqui

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