มีห้องสมุดอย่างน้อย 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
ฉันที่กำหนดไว้ข้างต้นเป็นจริง_2
LensFamily
_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
lens
แพ็คเกจมีฟังก์ชั่นและเอกสารที่สมบูรณ์ที่สุดดังนั้นหากคุณไม่คำนึงถึงความซับซ้อนและการพึ่งพามันเป็นวิธีที่จะไป