ฉันไม่เข้าใจว่า "การยก" คืออะไร ฉันควรเข้าใจพระก่อนหรือไม่ก่อนที่จะเข้าใจว่า "ลิฟต์" คืออะไร? (ฉันไม่รู้เกี่ยวกับพระอย่างสมบูรณ์เช่นกัน :) หรือใครบางคนสามารถอธิบายให้ฉันด้วยคำง่ายๆ?
ฉันไม่เข้าใจว่า "การยก" คืออะไร ฉันควรเข้าใจพระก่อนหรือไม่ก่อนที่จะเข้าใจว่า "ลิฟต์" คืออะไร? (ฉันไม่รู้เกี่ยวกับพระอย่างสมบูรณ์เช่นกัน :) หรือใครบางคนสามารถอธิบายให้ฉันด้วยคำง่ายๆ?
คำตอบ:
การยกเป็นรูปแบบการออกแบบมากกว่าแนวคิดทางคณิตศาสตร์ (แม้ว่าฉันคาดว่าจะมีใครบางคนอยู่ที่นี่ในตอนนี้จะหักล้างฉันด้วยการแสดงว่าลิฟต์เป็นหมวดหมู่หรือบางอย่าง)
โดยทั่วไปคุณมีชนิดข้อมูลพร้อมพารามิเตอร์ สิ่งที่ต้องการ
data Foo a = Foo { ...stuff here ...}
สมมติว่าคุณพบว่ามีการใช้Foo
ประเภทตัวเลข ( Int
และDouble
อื่น ๆ ) จำนวนมากและคุณยังคงต้องเขียนโค้ดที่ไม่ได้ใส่ตัวเลขเหล่านี้เพิ่มหรือทวีคูณพวกมันแล้วห่อกลับ คุณสามารถลัดวงจรสิ่งนี้ได้โดยการเขียนโค้ดที่ไม่ได้ห่อและพันครั้งเดียว ฟังก์ชั่นนี้เรียกว่า "การยก" แบบดั้งเดิมเพราะมีลักษณะดังนี้:
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
กล่าวอีกนัยหนึ่งคุณมีฟังก์ชั่นที่รับฟังก์ชั่นสองอาร์กิวเมนต์ (เช่น(+)
โอเปอเรเตอร์) และเปลี่ยนเป็นฟังก์ชันเทียบเท่าสำหรับ Foos
ดังนั้นตอนนี้คุณสามารถเขียน
addFoo = liftFoo2 (+)
แก้ไข: ข้อมูลเพิ่มเติม
แน่นอนคุณสามารถมีliftFoo3
, liftFoo4
และอื่น ๆ อย่างไรก็ตามสิ่งนี้มักไม่จำเป็น
เริ่มด้วยการสังเกต
liftFoo1 :: (a -> b) -> Foo a -> Foo b
fmap
แต่นั่นคือสิ่งเดียวกับ ดังนั้นแทนที่จะliftFoo1
เขียน
instance Functor Foo where
fmap f foo = ...
หากคุณต้องการให้ระเบียบสมบูรณ์คุณสามารถพูดได้
liftFoo1 = fmap
หากคุณสามารถFoo
เป็นนักแสดงได้บางทีคุณอาจทำให้เป็นนักแสดงนำได้ ที่จริงแล้วถ้าคุณสามารถเขียนได้liftFoo2
อินสแตนซ์ของการสมัครจะมีลักษณะดังนี้:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
ตัว(<*>)
ดำเนินการสำหรับ Foo มีชนิด
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
มันใช้ฟังก์ชั่นห่อเพื่อค่าห่อ ดังนั้นหากคุณสามารถนำไปใช้งานได้liftFoo2
คุณสามารถเขียนสิ่งนี้ในแง่ของมัน หรือคุณสามารถนำไปใช้โดยตรงและไม่ต้องกังวลกับliftFoo2
เพราะControl.Applicative
โมดูลรวมถึง
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
และในทำนองเดียวกันก็มีliftA
และliftA3
. แต่คุณไม่ได้ใช้บ่อยนักเพราะมีผู้ให้บริการรายอื่น
(<$>) = fmap
ให้คุณเขียน:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
คำนี้myFunction <$> arg1
จะคืนค่าฟังก์ชันใหม่ที่หุ้มด้วย Foo ในทางกลับกันนี้สามารถนำไปใช้กับการโต้แย้งต่อไปโดยใช้(<*>)
และอื่น ๆ ดังนั้นตอนนี้แทนที่จะมีฟังก์ชั่นการยกสำหรับทุก arity คุณแค่มีโซ่ของเดซี่
lift id == id
lift (f . g) == (lift f) . (lift g)
id
และ.
องค์ประกอบลูกศรและลูกศรของบางประเภทตามลำดับ โดยปกติเมื่อพูดถึง Haskell หมวดหมู่ที่มีปัญหาคือ "Hask" ซึ่งลูกศรเป็นฟังก์ชัน Haskell (กล่าวอีกนัยหนึ่งid
และ.
อ้างอิงถึงฟังก์ชัน Haskell ที่คุณรู้จักและชื่นชอบ)
instance Functor Foo
ไม่ได้instance Foo Functor
ใช่มั้ย? ฉันจะแก้ไขด้วยตัวเอง แต่ฉันไม่แน่ใจ 100%
คำอธิบายของ Paul และ yairchu เป็นสิ่งที่ดี
ฉันต้องการเพิ่มว่าฟังก์ชันที่ยกขึ้นสามารถมีจำนวนอาร์กิวเมนต์โดยพลการและพวกเขาไม่จำเป็นต้องเป็นประเภทเดียวกัน ตัวอย่างเช่นคุณสามารถกำหนด liftFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
โดยทั่วไปแล้วการยกฟังก์ชั่นที่รับอาร์กิวเมนต์ 1 จะถูกจับในคลาสประเภทFunctor
และการดำเนินการยกจะเรียกว่าfmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
สังเกตความคล้ายคลึงกันกับliftFoo1
ประเภทของ ในความเป็นจริงถ้าคุณมีliftFoo1
คุณสามารถทำFoo
ตัวอย่างของFunctor
:
instance Functor Foo where
fmap = liftFoo1
นอกจากนี้หลักเกณฑ์ในการยกไปจำนวนข้อของการขัดแย้งที่เรียกว่าสไตล์ applicative อย่าไปสนใจเรื่องนี้จนกว่าคุณจะเข้าใจการยกของฟังก์ชั่นด้วยจำนวนอาร์กิวเมนต์ที่แน่นอน แต่เมื่อคุณเรียนรู้คุณ Haskellมีบทที่ดีในเรื่องนี้ Typeclassopediaเป็นอีกหนึ่งเอกสารที่ดีที่จะอธิบายFunctorและapplicative (เช่นเดียวกับการเรียนประเภทอื่น ๆ เลื่อนลงไปที่บทในเอกสารนั้น)
หวังว่านี่จะช่วยได้!
มาเริ่มด้วยตัวอย่างกัน (เพิ่มพื้นที่สีขาวเพื่อการนำเสนอที่ชัดเจนขึ้น):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
เปลี่ยนฟังก์ชั่นของประเภทธรรมดาเป็นฟังก์ชั่นประเภทเดียวกันที่ห่อหุ้มในApplicative
รายการเช่นรายการIO
ฯลฯ
อีกยกที่พบบ่อยคือจากlift
Control.Monad.Trans
มันเปลี่ยนการกระทำของ monadic หนึ่ง monad เป็นการกระทำของ monad ที่ถูกแปลง
โดยทั่วไปแล้ว "การยก" จะยกฟังก์ชั่น / แอ็คชั่นให้เป็นแบบ "พัน" (ดังนั้นฟังก์ชันดั้งเดิมจะสามารถทำงานได้ "ภายใต้การแรป")
วิธีที่ดีที่สุดในการทำความเข้าใจสิ่งนี้และ monads ฯลฯ และเพื่อทำความเข้าใจว่าทำไมพวกเขาถึงมีประโยชน์อาจเป็นรหัสและใช้ หากมีสิ่งใดที่คุณเข้ารหัสไว้ก่อนหน้านี้ที่คุณสงสัยว่าจะได้รับประโยชน์จากสิ่งนี้ (เช่นนี้จะทำให้รหัสนั้นสั้นลง ฯลฯ ) ให้ลองใช้และคุณจะเข้าใจแนวคิดได้ง่าย
การยกเป็นแนวคิดที่ให้คุณเปลี่ยนฟังก์ชั่นให้เป็นฟังก์ชั่นที่สอดคล้องกันภายในการตั้งค่าอื่น
ตามที่กวดวิชาเงานี้เป็น functor เป็นภาชนะบางคน (เช่นMaybe<a>
, List<a>
หรือTree<a>
ที่สามารถเก็บองค์ประกอบของบางประเภทอื่นa
) ฉันได้ใช้สัญกรณ์ Java generics, <a>
, สำหรับประเภทองค์ประกอบและคิดว่าขององค์ประกอบที่เป็นผลเบอร์รี่บนต้นไม้a
Tree<a>
มีฟังก์ชั่นfmap
ซึ่งจะมีฟังก์ชั่นองค์ประกอบแปลงและภาชนะบรรจุa->b
functor<a>
ซึ่งจะใช้กับองค์ประกอบของภาชนะทุกอย่างมีประสิทธิภาพแปลงมันเป็นa->b
functor<b>
เมื่อเพียงอาร์กิวเมนต์แรกจะถูกส่งให้a->b
, รอfmap
functor<a>
นั่นคือการจัดหาa->b
เพียงอย่างเดียวเปลี่ยนฟังก์ชันระดับองค์ประกอบนี้เป็นฟังก์ชันfunctor<a> -> functor<b>
ที่ทำงานบนคอนเทนเนอร์ สิ่งนี้เรียกว่าการยกของฟังก์ชั่น เนื่องจากตู้คอนเทนเนอร์นั้นเรียกว่าfunctorผู้ Fun แทนที่จะเป็น Monads เป็นสิ่งที่จำเป็นสำหรับการยก พระเป็นประเภทของ "ขนาน" เพื่อยก ทั้งสองต้องพึ่งพาความคิด Functor f<a> -> f<b>
และทำ แตกต่างก็คือการยกใช้a->b
สำหรับการแปลงขณะ Monad a -> f<b>
ต้องใช้ในการกำหนด
r
ประเภทเป็น (ใช้c
เพื่อความหลากหลาย), คือ Functors พวกเขาไม่ได้ "มี" ใด ๆc
's ในตัวอย่างนี้ fmap คือการจัดองค์ประกอบของฟังก์ชั่นการใช้a -> b
ฟังก์ชั่นและr -> a
หนึ่งเพื่อให้คุณใหม่r -> b
ฟังก์ชั่น ยังไม่มีตู้คอนเทนเนอร์นอกจากนี้ถ้าทำได้ฉันจะทำเครื่องหมายลงอีกครั้งสำหรับประโยคสุดท้าย
fmap
มีฟังก์ชั่นและไม่ต้อง "รอ" เพื่ออะไร "ตู้คอนเทนเนอร์" เป็น Functor เป็นจุดยก นอกจากนี้ Monads มีถ้ามีอะไรเป็นความคิดแบบคู่เพื่อยกที่: Monad ช่วยให้คุณใช้สิ่งที่ได้รับการยกบางจำนวนบวกครั้งราวกับว่ามันได้ถูกยกขึ้นเพียงครั้งเดียว - นี้เป็นที่รู้จักกันดีในฐานะแฟบ
To wait
, to expect
, to anticipate
เป็นคำพ้องความหมาย โดยการพูดว่า "ฟังก์ชั่นรอ" ฉันหมายถึง "ฟังก์ชั่นคาดหวัง"
b = 5 : a
และf 0 = 55
f n = g n
ทั้งคู่เกี่ยวข้องกับการปลอมแปลง "คอนเทนเนอร์" นอกจากนี้ความจริงที่ว่ารายการจะถูกเก็บไว้ในหน่วยความจำอย่างสมบูรณ์ในขณะที่ฟังก์ชั่นจะถูกจัดเก็บเป็นการคำนวณ แต่การบันทึกความจำ / รายการ monorphic ที่ไม่ได้จัดเก็บระหว่างการโทรทั้งสองจะทำให้เสียอึออกจากแนวคิดนั้น