ฉันพยายามและล้มเหลวในการ 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
ได้เนื่องจากภายในฟังก์ชันคุณต้องเข้าถึงฐานข้อมูลเพื่ออ่านชื่อผู้ใช้ (ซึ่งต้องใช้เอฟเฟกต์ - ในกรณีนี้โดยใช้IO
monad)
ลายเซ็นของ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
โดยปกติแล้วจะใช้เพียงแค่กาวอัลกอริทึมกับโครงสร้างข้อมูลที่มีอยู่ มันค่อนข้างดีที่ไม่จำเป็นต้องเขียนฟังก์ชันที่คล้ายกันสำหรับประเภทข้อมูลที่แตกต่างกันที่มีคุณสมบัติเช่นกัน