ฉันพยายามและล้มเหลวในการ grok ฟังก์ชั่นจากtraverse Data.Traversableฉันไม่สามารถมองเห็นจุดของมัน เนื่องจากฉันมาจากภูมิหลังที่จำเป็นใครก็ได้ช่วยอธิบายให้ฉันฟังในแง่ของการวนซ้ำที่จำเป็นได้ไหม รหัสหลอกจะได้รับการชื่นชมมาก ขอบคุณ.
ฉันพยายามและล้มเหลวในการ grok ฟังก์ชั่นจากtraverse Data.Traversableฉันไม่สามารถมองเห็นจุดของมัน เนื่องจากฉันมาจากภูมิหลังที่จำเป็นใครก็ได้ช่วยอธิบายให้ฉันฟังในแง่ของการวนซ้ำที่จำเป็นได้ไหม รหัสหลอกจะได้รับการชื่นชมมาก ขอบคุณ.
คำตอบ:
traverseเหมือนกับfmapยกเว้นว่าจะให้คุณเรียกใช้เอฟเฟกต์ในขณะที่คุณกำลังสร้างโครงสร้างข้อมูลใหม่
ดูตัวอย่างจากData.Traversableเอกสารประกอบ
data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
Functorตัวอย่างของการTreeจะเป็น:
instance Functor Tree where
fmap f Empty = Empty
fmap f (Leaf x) = Leaf (f x)
fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)
มันสร้างต้นไม้ทั้งหมดขึ้นใหม่โดยใช้fกับทุกค่า
instance Traversable Tree where
traverse f Empty = pure Empty
traverse f (Leaf x) = Leaf <$> f x
traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r
Traversableเช่นเป็นเกือบเดียวกันยกเว้นการก่อสร้างจะเรียกว่าในรูปแบบ applicative ซึ่งหมายความว่าเราสามารถมีเอฟเฟกต์ (ด้านข้าง) ได้ในขณะที่สร้างต้นไม้ขึ้นมาใหม่ การใช้งานเกือบจะเหมือนกับ monads ยกเว้นว่าผลกระทบไม่สามารถขึ้นอยู่กับผลลัพธ์ก่อนหน้านี้ ในตัวอย่างนี้หมายความว่าคุณไม่สามารถทำสิ่งที่แตกต่างไปจากสาขาทางขวาของโหนดได้ขึ้นอยู่กับผลลัพธ์ของการสร้างสาขาทางซ้ายขึ้นมาใหม่เช่น
สำหรับเหตุผลทางประวัติศาสตร์Traversableชั้นนอกจากนี้ยังมีรุ่นเอกของที่เรียกว่าtraverse mapMสำหรับ intents และวัตถุประสงค์mapMเป็นเช่นเดียวกับtraverse- มันมีอยู่เป็นวิธีการแยกจากกันเพราะApplicativeเพียง Monadแต่ต่อมากลายเป็นซุปเปอร์คลาสของ
หากคุณจะใช้สิ่งนี้ในภาษาที่ไม่บริสุทธิ์fmapก็จะเหมือนกับว่าtraverseไม่มีวิธีใดในการป้องกันผลข้างเคียง คุณไม่สามารถใช้งานแบบวนซ้ำได้เนื่องจากคุณต้องสำรวจโครงสร้างข้อมูลแบบวนซ้ำ นี่คือตัวอย่างเล็ก ๆ ที่ฉันจะทำใน Javascript:
Node.prototype.traverse = function (f) {
return new Node(this.l.traverse(f), f(this.k), this.r.traverse(f));
}
การใช้งานแบบนี้จะ จำกัด เอฟเฟกต์ที่ภาษาอนุญาต หากคุณต้องการความไม่เป็นตัวกำหนด (ซึ่งเป็นตัวอย่างรายการของรูปแบบการใช้งาน) และภาษาของคุณไม่มีในตัวคุณก็โชคไม่ดี
Functorส่วนที่ไม่ใช่พาราเมตริก ค่าสถานะในStateความล้มเหลวในMaybeและEitherจำนวนองค์ประกอบใน[]และแน่นอนผลข้างเคียงภายนอกโดยพลการในIO. ฉันไม่สนใจว่ามันเป็นคำทั่วไป (เช่นMonoidฟังก์ชันที่ใช้ "ว่าง" และ "ต่อท้าย" แนวคิดนี้จะกว้างกว่าคำที่แนะนำในตอนแรก) แต่เป็นเรื่องธรรมดาและตอบสนองวัตถุประสงค์ได้ดีพอ
apผลลัพธ์ก่อนหน้านี้ ฉันได้ทบทวนคำพูดนั้นใหม่ตามนั้น
traverseเปลี่ยนสิ่งที่อยู่ภายในให้Traversableกลายเป็นTraversableสิ่งของ "ภายใน" Applicativeและได้รับฟังก์ชันที่ทำให้Applicativeสิ่งต่างๆ
ใช้ Let 's Maybeเป็นและรายชื่อเป็นApplicative Traversableอันดับแรกเราต้องมีฟังก์ชันการเปลี่ยนแปลง:
half x = if even x then Just (x `div` 2) else Nothing
ดังนั้นถ้าจำนวนคือแม้เราได้รับครึ่งหนึ่งของมัน (ภายในJust) Nothingอื่นที่เราได้รับ หากทุกอย่างเป็นไปอย่าง "เรียบร้อย" จะมีลักษณะดังนี้:
traverse half [2,4..10]
--Just [1,2,3,4,5]
แต่...
traverse half [1..10]
-- Nothing
เหตุผลก็คือ<*>ฟังก์ชันถูกใช้เพื่อสร้างผลลัพธ์และเมื่อหนึ่งในอาร์กิวเมนต์คือNothingเราจะNothingกลับมา
ตัวอย่างอื่น:
rep x = replicate x x
ฟังก์ชั่นนี้จะสร้างรายชื่อของความยาวxที่มีเนื้อหาxเช่น=rep 3 [3,3,3]เป็นผลมาจากtraverse rep [1..3]อะไร?
เราได้รับผลบางส่วน[1], [2,2]และการใช้[3,3,3] repตอนนี้ความหมายของรายการในฐานะApplicativesเป็น "ใช้เวลารวมกันทุกคน" เช่นเป็น(+) <$> [10,20] <*> [3,4][13,14,23,24]
"รวมกันทั้งหมด" ของ[1]และเป็นครั้งที่สอง[2,2] [1,2]ทั้งหมดรวมกันของทั้งสองครั้ง[1,2]และเป็นครั้งที่หก[3,3,3] [1,2,3]ดังนั้นเราจึงมี:
traverse rep [1..3]
--[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
fac n = length $ traverse rep [1..n]
liftA2 (,) traverse
ฉันคิดว่ามันง่ายที่สุดในการทำความเข้าใจในแง่ของการsequenceAเป็นtraverseสามารถกำหนดดังต่อไปนี้
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
sequenceA จัดลำดับองค์ประกอบของโครงสร้างจากซ้ายไปขวาโดยส่งคืนโครงสร้างที่มีรูปร่างเดียวกันซึ่งมีผลลัพธ์
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
sequenceA = traverse id
คุณยังสามารถคิดว่าsequenceAเป็นการย้อนกลับลำดับของ functors สองตัวเช่นเปลี่ยนจากรายการการกระทำไปเป็นการกระทำที่ส่งคืนรายการผลลัพธ์
ดังนั้นtraverseต้องใช้โครงสร้างบางอย่างและใช้fเพื่อเปลี่ยนทุกองค์ประกอบในโครงสร้างให้เป็นแอปพลิเคชันบางอย่างจากนั้นจะจัดลำดับผลของแอปพลิเคชันเหล่านั้นจากซ้ายไปขวาโดยส่งคืนโครงสร้างที่มีรูปร่างเดียวกันซึ่งมีผลลัพธ์
คุณยังสามารถเปรียบเทียบกับFoldableซึ่งกำหนดฟังก์ชันที่เกี่ยวข้องtraverse_ได้
traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
ดังนั้นคุณจะเห็นว่าความแตกต่างที่สำคัญระหว่างFoldableและTraversableคืออย่างหลังช่วยให้คุณสามารถรักษารูปร่างของโครงสร้างได้ในขณะที่รูปแบบเดิมต้องการให้คุณพับผลลัพธ์เป็นค่าอื่น ๆ
ตัวอย่างง่ายๆของการใช้งานคือการใช้รายการเป็นโครงสร้างที่สามารถข้ามผ่านได้และIOเป็นการนำไปใช้:
λ> import Data.Traversable
λ> let qs = ["name", "quest", "favorite color"]
λ> traverse (\thing -> putStrLn ("What is your " ++ thing ++ "?") *> getLine) qs
What is your name?
Sir Lancelot
What is your quest?
to seek the holy grail
What is your favorite color?
blue
["Sir Lancelot","to seek the holy grail","blue"]
แม้ว่าตัวอย่างนี้จะค่อนข้างไม่น่าตื่นเต้น แต่สิ่งต่าง ๆ ก็น่าสนใจยิ่งขึ้นเมื่อtraverseใช้กับคอนเทนเนอร์ประเภทอื่นหรือใช้แอปพลิเคชันอื่น ๆ
sequenceA . fmapสำหรับรายการเทียบเท่ากับsequence . mapมันไม่ใช่เหรอ?
IO ประเภทนี้เพื่อแสดงผลข้างเคียง (2) IOเป็นโมนาดซึ่งกลายเป็นสิ่งที่สะดวกมาก Monads ไม่ได้เชื่อมโยงกับผลข้างเคียงเป็นหลัก นอกจากนี้ควรสังเกตว่ามีความหมายของ "ผล" ซึ่งกว้างกว่า "ผลข้างเคียง" ในความหมายปกติซึ่งรวมถึงการคำนวณที่บริสุทธิ์ ในประเด็นสุดท้ายนี้โปรดดูด้วยว่า“ มีผล” หมายความว่าอย่างไร
มันเหมือนกับfmapยกเว้นว่าคุณสามารถเรียกใช้เอฟเฟกต์ภายในฟังก์ชัน mapper ซึ่งจะเปลี่ยนประเภทผลลัพธ์ด้วย
[1, 2, 3]ลองนึกภาพรายการของจำนวนเต็มตัวแทนรหัสผู้ใช้ในฐานข้อมูล: หากคุณต้องการfmapใช้ ID ผู้ใช้เหล่านี้เป็นชื่อผู้ใช้คุณไม่สามารถใช้แบบดั้งเดิมfmapได้เนื่องจากภายในฟังก์ชันคุณต้องเข้าถึงฐานข้อมูลเพื่ออ่านชื่อผู้ใช้ (ซึ่งต้องใช้เอฟเฟกต์ - ในกรณีนี้โดยใช้IOmonad)
ลายเซ็นของtraverseคือ:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
ด้วยtraverseคุณสามารถสร้างเอฟเฟกต์ได้ดังนั้นโค้ดของคุณสำหรับการแมป ID ผู้ใช้กับชื่อผู้ใช้จะมีลักษณะดังนี้
mapUserIDsToUsernames :: (Num -> IO String) -> [Num] -> IO [String]
mapUserIDsToUsernames fn ids = traverse fn ids
นอกจากนี้ยังมีฟังก์ชันที่เรียกว่าmapM:
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
การใช้งานใด ๆmapMสามารถแทนที่traverseได้ แต่ไม่สามารถใช้วิธีอื่นได้ mapMใช้ได้กับ monads เท่านั้นในขณะที่traverseเป็นแบบทั่วไป
หากคุณเพียงแค่ต้องการบรรลุเอฟเฟกต์และไม่ส่งคืนค่าที่เป็นประโยชน์ใด ๆฟังก์ชันเหล่านี้มีtraverse_และmapM_เวอร์ชันซึ่งทั้งสองอย่างนี้จะไม่สนใจค่าส่งคืนจากฟังก์ชันและเร็วกว่าเล็กน้อย
traverse คือลูป การใช้งานขึ้นอยู่กับโครงสร้างข้อมูลที่จะส่งผ่าน ที่อาจจะมีรายการต้นไม้Maybe, Seq(อิทธิพลต่อ) หรือสิ่งที่มีวิธีการทั่วไปของการถูกเดินทางข้ามผ่านสิ่งที่ต้องการสำหรับวงหรือฟังก์ชันเวียน อาร์เรย์จะมี for-loop, list a while-loop, tree ทั้งสิ่งที่เรียกซ้ำหรือการรวมกันของ stack กับ while-loop แต่ในภาษาที่ใช้งานได้คุณไม่จำเป็นต้องใช้คำสั่งลูปที่ยุ่งยากเหล่านี้: คุณรวมส่วนภายในของลูป (ในรูปของฟังก์ชัน) เข้ากับโครงสร้างข้อมูลในลักษณะที่ตรงกว่าและมีรายละเอียดน้อยกว่า
กับ Traversableคลาสประเภทนี้คุณอาจเขียนอัลกอริทึมของคุณได้อย่างอิสระและหลากหลายมากขึ้น แต่ประสบการณ์ของฉันบอกว่าTraversableโดยปกติแล้วจะใช้เพียงแค่กาวอัลกอริทึมกับโครงสร้างข้อมูลที่มีอยู่ มันค่อนข้างดีที่ไม่จำเป็นต้องเขียนฟังก์ชันที่คล้ายกันสำหรับประเภทข้อมูลที่แตกต่างกันที่มีคุณสมบัติเช่นกัน