ขออภัยฉันไม่รู้คณิตศาสตร์ของตัวเองจริงๆจึงอยากรู้ว่าจะออกเสียงฟังก์ชันในประเภทการใช้งานอย่างไร
ฉันคิดว่าการรู้คณิตศาสตร์ของคุณไม่เกี่ยวข้องกับที่นี่ อย่างที่คุณทราบกันดีอยู่แล้วว่า Haskell ยืมคำศัพท์บางส่วนจากคณิตศาสตร์เชิงนามธรรมแขนงต่างๆโดยเฉพาะอย่างยิ่งทฤษฎีหมวดหมู่ซึ่งเราได้รับ functors และ monads มาจากไหน การใช้คำศัพท์เหล่านี้ใน Haskell แตกต่างจากคำจำกัดความทางคณิตศาสตร์ที่เป็นทางการ แต่โดยปกติแล้วคำเหล่านี้จะใกล้เคียงกันมากพอที่จะเป็นคำอธิบายที่ดีได้
Applicative
ระดับประเภทนั่งอยู่ที่ไหนสักแห่งระหว่างFunctor
และMonad
ดังนั้นใครจะคาดว่าจะมีพื้นฐานทางคณิตศาสตร์ที่คล้ายกัน เอกสารประกอบสำหรับControl.Applicative
โมดูลเริ่มต้นด้วย:
โมดูลนี้อธิบายโครงสร้างที่อยู่ตรงกลางระหว่าง functor และ monad: ให้นิพจน์และการจัดลำดับที่บริสุทธิ์ แต่ไม่มีการเชื่อมโยง (ในทางเทคนิคแล้ว functor monoidal ที่หละหลวมมาก)
อืม.
class (Functor f) => StrongLaxMonoidalFunctor f where
. . .
ไม่น่าจับใจเท่าที่Monad
ฉันคิด
สิ่งที่กล่าวมาทั้งหมดนี้Applicative
ไม่สอดคล้องกับแนวคิดใด ๆ ที่น่าสนใจโดยเฉพาะทางคณิตศาสตร์ดังนั้นจึงไม่มีคำศัพท์สำเร็จรูปที่จับภาพวิธีการใช้ใน Haskell ตอนนี้ตั้งค่าคณิตศาสตร์ไว้ก่อน
ถ้าเราอยากรู้ว่าเรียกว่าอะไร(<*>)
มันอาจช่วยให้รู้ว่าโดยพื้นฐานแล้วมันหมายถึงอะไร
ดังนั้นสิ่งที่เกิดขึ้นกับApplicative
อยู่แล้วและทำไมไม่ที่เราเรียกว่า?
สิ่งที่Applicative
จำนวนเงินที่ในทางปฏิบัติเป็นวิธีที่จะยกพลFunctor
ฟังก์ชั่นเป็น พิจารณาการรวมกันของMaybe
(เนื้อหาที่ง่ายที่สุดที่ไม่สำคัญที่สุดFunctor
) และBool
(เช่นเดียวกันประเภทข้อมูลที่ง่ายที่สุดที่ไม่สำคัญ)
maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not
ฟังก์ชันนี้fmap
ช่วยให้เรายกระดับnot
จากการทำงานBool
ไปสู่การทำงานMaybe Bool
ได้ แต่ถ้าเราต้องการยก(&&)
ล่ะ?
maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)
นั่นไม่ใช่สิ่งที่เราต้องการเลย ! ในความเป็นจริงมันค่อนข้างไร้ประโยชน์ เราสามารถพยายามที่จะฉลาดและแอบBool
เข้าไปMaybe
ด้านหลัง ...
maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)
... แต่ก็ไม่ดี ประการหนึ่งมันผิด สำหรับสิ่งอื่นก็น่าเกลียด เราสามารถให้พยายาม แต่ปรากฎว่ามีวิธีที่จะยกฟังก์ชั่นของการขัดแย้งหลายในการทำงานบนโดยพลการไม่มี Functor
น่ารำคาญ!
บนมืออื่น ๆ ที่เราสามารถทำมันได้อย่างง่ายดายถ้าเราใช้Maybe
's Monad
เช่น:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
y' <- y
return (x' && y')
ตอนนี้ที่มีจำนวนมากของการทะเลาะเพียงแค่การแปลฟังก์ชั่นที่เรียบง่าย - ซึ่งเป็นเหตุผลที่ให้ฟังก์ชั่นที่จะทำมันโดยอัตโนมัติControl.Monad
liftM2
2 ในชื่อหมายถึงความจริงที่ว่ามันทำงานกับฟังก์ชันของอาร์กิวเมนต์สองตัว มีฟังก์ชันที่คล้ายกันสำหรับฟังก์ชันอาร์กิวเมนต์ 3, 4 และ 5 ฟังก์ชันเหล่านี้ดีกว่าแต่ไม่สมบูรณ์แบบและการระบุจำนวนอาร์กิวเมนต์นั้นน่าเกลียดและเงอะงะ
ซึ่งนำเราไปยังกระดาษที่นำชั้นประเภท applicative ในนั้นผู้เขียนตั้งข้อสังเกตสองประการ:
- การยกฟังก์ชันหลายอาร์กิวเมนต์ให้เป็น a
Functor
เป็นสิ่งที่ควรทำอย่างยิ่ง
- การทำเช่นนั้นไม่จำเป็นต้องใช้ความสามารถทั้งหมดของไฟล์
Monad
แอปพลิเคชันฟังก์ชั่นปกติเขียนโดยการวางเงื่อนไขอย่างง่ายดังนั้นเพื่อให้ "แอปพลิเคชันยก" มีความเรียบง่ายและเป็นธรรมชาติมากที่สุดเอกสารนี้จะแนะนำตัวดำเนินการ infix ให้ยืนอยู่ในแอปพลิเคชันยกเข้าไปในFunctor
คลาสและประเภทเพื่อระบุสิ่งที่จำเป็นสำหรับสิ่งนั้น .
ทั้งหมดนี้นำเราไปสู่ประเด็นต่อไปนี้: (<*>)
แสดงถึงแอปพลิเคชันฟังก์ชัน - เหตุใดจึงออกเสียงแตกต่างจากที่คุณใช้ "ตัวดำเนินการตีข่าว" ในช่องว่าง
แต่ถ้าไม่น่าพอใจมากเราสามารถสังเกตได้ว่าControl.Monad
โมดูลนี้ยังมีฟังก์ชันที่ทำสิ่งเดียวกันสำหรับ monads:
ap :: (Monad m) => m (a -> b) -> m a -> m b
ไหนap
เป็นของหลักสูตรสั้น "ใช้" ตั้งแต่ใดMonad
สามารถApplicative
และap
ความต้องการเฉพาะชุดย่อยของคุณสมบัติที่นำเสนอในภายหลังเราอาจจะสามารถพูดได้ว่าถ้าไม่ดำเนินการก็ควรจะเรียกว่า(<*>)
ap
นอกจากนี้เรายังสามารถเข้าใกล้สิ่งต่างๆจากทิศทางอื่นได้ การFunctor
ดำเนินการยกเรียกว่าfmap
เนื่องจากเป็นลักษณะทั่วไปของการmap
ดำเนินการในรายการ สิ่งที่จัดเรียงของฟังก์ชั่นในรายการจะทำงานเช่น(<*>)
? แน่นอนว่ามีสิ่งที่ap
ทำในรายการ แต่ก็ไม่ได้มีประโยชน์อย่างยิ่งในตัวมันเอง
ในความเป็นจริงอาจมีการตีความที่เป็นธรรมชาติมากกว่าสำหรับรายการ คุณนึกถึงอะไรเมื่อดูลายเซ็นประเภทต่อไปนี้
listApply :: [a -> b] -> [a] -> [b]
มีบางอย่างที่น่าดึงดูดเกี่ยวกับแนวคิดในการจัดเรียงรายการแบบขนานโดยใช้แต่ละฟังก์ชันในส่วนแรกกับองค์ประกอบที่เกี่ยวข้องของวินาที น่าเสียดายสำหรับเพื่อนเก่าของเราMonad
การดำเนินการที่เรียบง่ายนี้ละเมิดกฎหมาย monadหากรายการมีความยาวต่างกัน แต่มันก็ทำให้ดีApplicative
ซึ่งในกรณีนี้(<*>)
จะกลายเป็นวิธีการรวมรุ่นทั่วไปเข้าด้วยกันzipWith
ดังนั้นบางทีเราอาจนึกได้ว่าเรียกมันว่าfzipWith
?
แนวคิดการบีบอัดนี้ทำให้เราเต็มวง จำเรื่องคณิตศาสตร์ก่อนหน้านี้เกี่ยวกับ functors monoidal หรือไม่? ตามชื่อที่แนะนำนี่เป็นวิธีการรวมโครงสร้างของ monoids และ functors ซึ่งทั้งสองอย่างนี้เป็นคลาสประเภท Haskell ที่คุ้นเคย:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monoid a where
mempty :: a
mappend :: a -> a -> a
สิ่งเหล่านี้จะเป็นอย่างไรถ้าคุณใส่ไว้ในกล่องพร้อมกันและเขย่ามันเล็กน้อย? จากนั้นFunctor
เราจะทำให้แนวคิดของโครงสร้างไม่ขึ้นอยู่กับพารามิเตอร์ประเภทและจากนั้นMonoid
เราจะคงรูปแบบโดยรวมของฟังก์ชันไว้:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ?
mfAppend :: f ? -> f ? -> f ?
เราไม่ต้องการที่จะคิดว่ามีวิธีที่จะสร้างอย่างแท้จริง "ว่าง" แล้วFunctor
และเราไม่สามารถก่อให้เกิดค่าชนิดพลดังนั้นเราจะแก้ไขประเภทของการเป็นmfEmpty
f ()
เราไม่ต้องการบังคับmfAppend
ให้ต้องมีพารามิเตอร์ประเภทที่สอดคล้องกันดังนั้นตอนนี้เรามีสิ่งนี้:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f ?
ผลการค้นหาmfAppend
คืออะไร? เรามีสองประเภทโดยพลการที่เราไม่รู้อะไรเลยดังนั้นเราจึงไม่มีตัวเลือกมากมาย สิ่งที่สมเหตุสมผลที่สุดคือเก็บทั้งสองอย่างไว้:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f (a, b)
ณ จุดmfAppend
นี้เห็นได้ชัดว่าเป็นเวอร์ชันทั่วไปของzip
รายการและเราสามารถสร้างใหม่Applicative
ได้อย่างง่ายดาย:
mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)
นอกจากนี้ยังแสดงให้เราเห็นว่าpure
เกี่ยวข้องกับองค์ประกอบเอกลักษณ์ของ a Monoid
ดังนั้นชื่อที่ดีอื่น ๆ ของมันอาจเป็นอะไรก็ได้ที่แนะนำค่าหน่วยการดำเนินการว่างหรืออื่น ๆ
มันยาวมากดังนั้นเพื่อสรุป:
(<*>)
เป็นเพียงแอปพลิเคชันฟังก์ชันที่ได้รับการแก้ไขดังนั้นคุณสามารถอ่านเป็น "ap" หรือ "ใช้" หรืออธิบายได้ทั้งหมดในแบบที่คุณใช้กับแอปพลิเคชันฟังก์ชันปกติ
(<*>)
ยังสรุปคร่าวๆzipWith
ในรายการดังนั้นคุณสามารถอ่านเป็น "zip functors with" ได้เช่นเดียวกับการอ่านfmap
เป็น "map a functor with"
อย่างแรกใกล้เคียงกับเจตนาของApplicative
คลาสประเภท - ตามชื่อ - นั่นคือสิ่งที่ฉันแนะนำ
อันที่จริงฉันสนับสนุนให้ใช้ตัวดำเนินการแอปพลิเคชันที่ยกขึ้นทั้งหมดและไม่ออกเสียง :
(<$>)
ซึ่งยกฟังก์ชันอาร์กิวเมนต์เดียวเป็น Functor
(<*>)
ซึ่งเชื่อมโยงฟังก์ชันหลายอาร์กิวเมนต์ผ่านไฟล์ Applicative
(=<<)
ซึ่งผูกฟังก์ชันที่เข้าMonad
สู่การคำนวณที่มีอยู่
ทั้งสามเป็นหัวใจสำคัญเพียงแค่แอปพลิเคชั่นฟังก์ชั่นปกติที่เพิ่มขึ้นเล็กน้อย