สับสนเกี่ยวกับฟังก์ชันเป็นอินสแตนซ์ของ Functor ใน haskell

ประเภทของ fmap ใน Functor คือ:

fmap :: Functor f => (a -> b) -> f a -> f b

ดูเหมือนว่า ขั้นแรกให้ใช้ฟังก์ชัน (a -› b) กับพารามิเตอร์ของ f a เพื่อสร้างผลลัพธ์ประเภท b จากนั้นใช้ f กับมัน และผลลัพธ์คือ f b

ใช้ Maybe a เช่น:

 fmap show (Just 1)
 result is : Just "1"

เช่นเดียวกับการพูดว่า:

Just (show 1)

แต่เมื่อใช้ (->) เป็น Functor (ใน Control.Monad.Instances)

import Control.Monad.Instances
(fmap show Just) 1
result is : "Just 1"

นั่นคือ Just จะถูกนำไปใช้ก่อน จากนั้น show จะถูกนำไปใช้ ในอีกตัวอย่างหนึ่ง ผลลัพธ์จะเหมือนกัน:

 fmap (*3) (+100) 1
 result is 303

ทำไมไม่ *3 ก่อนแล้วจึง +100?


person 诺 铁    schedule 24.04.2012    source แหล่งที่มา


คำตอบ (5)


ประเภทของ fmap ใน Functor คือ:

fmap :: Functor f => (a -> b) -> f a -> f b

ดูเหมือนว่า ขั้นแรกให้ใช้ฟังก์ชัน (a -> b) กับพารามิเตอร์ของ f a เพื่อสร้างผลลัพธ์ประเภท b จากนั้นใช้ f กับมัน และผลลัพธ์คือ f b

นั่นคือประเภทของ fmap แต่การตีความความหมายประเภทนั้นของคุณนั้นผิด

ดูเหมือนคุณจะถือว่า f a มีพารามิเตอร์ตัวเดียว และพารามิเตอร์นั้นมีประเภท a

พิจารณา xs :: [a]:

  • บางที xs = []
  • บางที xs = [x1]
  • บางที xs = [x1, x2]
  • ...

ประเภท f a เป็นฟังก์ชัน f ที่มีพารามิเตอร์ประเภทเดียว a แต่ ค่า ประเภท f a ไม่จำเป็นต้องอยู่ในรูปแบบ F x ดังที่คุณเห็นจากกรณีแรกและกรณีที่สามด้านบน

ตอนนี้ให้พิจารณา fmap f xs:

  • บางที fmap f xs = []
  • บางที fmap f xs = [f x1]
  • บางที fmap f xs = [f x1, f x2]
  • ...

เราไม่จำเป็นต้องใช้ f เลย (กรณีแรก)! หรือเราอาจใช้มากกว่าหนึ่งครั้ง (กรณีที่สาม)

สิ่งที่เราทำคือแทนที่สิ่งที่เป็นประเภท a ด้วยสิ่งที่เป็นประเภท b แต่เราปล่อยให้โครงสร้างที่ใหญ่ขึ้นไม่เสียหาย --- ไม่มีการเพิ่มองค์ประกอบใหม่ ไม่มีการลบองค์ประกอบ ลำดับขององค์ประกอบยังคงไม่เปลี่ยนแปลง


ทีนี้ลองคิดถึงฟังก์ชัน (c ->) กัน (โปรดจำไว้ว่า functor รับพารามิเตอร์ประเภทเดียวเท่านั้น ดังนั้นอินพุตของ (->) จึงได้รับการแก้ไข)

c -> a มี a ด้วยหรือเปล่า มันอาจจะไม่มี a เลย แต่ก็สามารถทำให้เกิดเวทย์มนตร์ได้เมื่อเราให้ c แต่ผลลัพธ์จาก fmap มีประเภท c -> b: เราจะต้องระบุ b จากนั้นเมื่อเรานำเสนอด้วย c

ดังนั้นเราจึงสามารถพูดได้ว่า fmap f x = \y -> f (x y)

ในกรณีนี้ เรากำลังใช้ f ตามความต้องการ --- ทุกครั้งที่มีการใช้ฟังก์ชันที่เราส่งคืน f ก็จะถูกนำไปใช้เช่นกัน

person dave4420    schedule 24.04.2012
comment
ใช่แล้ว คำตอบของคุณเยี่ยมมาก! ฉันทำผิดพลาดครั้งใหญ่ ขอบคุณมาก. - person 诺 铁; 24.04.2012
comment
ฉันสับสนพารามิเตอร์ประเภทกับพารามิเตอร์ที่เป็นรูปธรรม - person 诺 铁; 24.04.2012

fmap อินสแตนซ์สำหรับ (->) r (เช่น ฟังก์ชัน) เป็นเพียงการจัดองค์ประกอบเท่านั้น จากแหล่งที่มา:

instance Functor ((->) r) where
    fmap = (.)

ในตัวอย่างของคุณ เราสามารถแทนที่ fmap ด้วย (.) และทำการแปลงบางอย่างได้

fmap (*3) (+100) 1 => 
(.) (*3) (+100) 1  =>
(*3) . (+100) $ 1  => -- put (.) infix
(*3) (1 + 100)     => -- apply (+100)
(1 + 100) * 3         -- apply (*3)

นั่นคือ fmap สำหรับฟังก์ชันจะเขียนจากขวาไปซ้าย (เหมือนกับ (.) ทุกประการ ซึ่งสมเหตุสมผลเพราะเป็น (.))

หากต้องการดูอีกวิธีหนึ่ง (สำหรับการยืนยัน (สองครั้ง)!) เราสามารถใช้ลายเซ็นประเภท:

-- general fmap
fmap :: Functor f => (a -> b) -> f a -> f b

-- specialised to the function functor (I've removed the last pair of brackets)
fmap :: (a -> b) -> (r -> a) -> r -> b 

ดังนั้นก่อนอื่นค่าของประเภท r (อาร์กิวเมนต์ที่สาม) จะต้องถูกแปลงเป็นค่าประเภท a (โดยฟังก์ชัน r -> a) เพื่อให้ฟังก์ชัน a -> b สามารถเปลี่ยนให้เป็นค่าประเภท b (ผลลัพธ์)

person huon    schedule 24.04.2012
comment
ใช่ fmap :: (a -> b) -> (r -> a) -> r -> b สิ่งนี้อธิบายได้มากขอบคุณ - person 诺 铁; 24.04.2012
comment
ลิงก์เสีย ขณะนี้เป็น hackage.haskell .org/package/base-4.12.0.0/docs/src/ - person jinbeom hong; 06.11.2019

จำเป็นต้อง ที่จะต้องกำหนดวิธีนั้นเพื่อทำให้ประเภทต่างๆ ออกมาดี ตามที่คุณชี้ให้เห็น ประเภทของ fmap คือ:

fmap :: Functor f => (a -> b) -> f a -> f b

ลองพิจารณากรณีที่ฟังก์ชัน f คือ ((->) c)

(หมายเหตุ: จริงๆ แล้วเราต้องการเขียนเป็น (c ->) นั่นคือฟังก์ชันจาก c แต่ Haskell ไม่อนุญาตให้เราทำสิ่งนี้)

จากนั้น f a ก็คือ ((->) c a) ซึ่งเทียบเท่ากับ (c -> a) และในทำนองเดียวกันสำหรับ f b ดังนั้นเราจึงได้:

fmap :: (a -> b) -> (c -> a) -> (c -> b)

กล่าวคือ เราจำเป็นต้องใช้สองฟังก์ชัน:

  • f :: a -> b
  • g :: c -> a

และสร้างฟังก์ชันใหม่

  • h :: c -> b

แต่มีทางเดียวเท่านั้นที่จะทำเช่นนั้นได้ คุณต้องใช้ g ก่อนเพื่อให้ได้บางอย่างประเภท a จากนั้นจึงใช้ f เพื่อให้ได้บางอย่างประเภท b ซึ่งหมายความว่าคุณ มี ที่จะกำหนด

instance Functor ((->) c) where
    fmap f g = \x -> f (g x)

หรือพูดให้กระชับกว่านั้นคือ

instance Functor ((->) c) where
    fmap = (.)
person Chris Taylor    schedule 24.04.2012

fmap สำหรับ (->) ถูกกำหนดไว้เช่น fmap = (.) ดังนั้น (fmap f g) x คือ (f . g) x คือ f (g x) ในกรณีของคุณ (*3) ((+100) 1) ซึ่งเท่ากับ 3 * (100 + 1) ซึ่งส่งผลให้เป็น 303

person phipsgabler    schedule 24.04.2012

ในการสร้างประเภทฟังก์ชัน คุณต้องมีพารามิเตอร์ 2 ชนิดสำหรับ (->) นั่นคือประเภทอาร์กิวเมนต์อินพุตเดี่ยวและประเภทส่งคืน

Functor สามารถรับพารามิเตอร์ได้เพียง 1 ประเภทเท่านั้น ดังนั้นคุณต้องตอกย้ำประเภทอาร์กิวเมนต์อินพุต (เนื่องจากเป็นประเภทแรกจากซ้ายไปขวา) ซึ่งทำให้ประเภทการส่งคืนของฟังก์ชันเป็นพารามิเตอร์ประเภทของ Functor

ดังนั้นสำหรับฟังก์ชัน (Functor) a->b คุณต้องให้ fmap ฟังก์ชัน ff ประเภท b->xxx นอกเหนือจาก a->xxx เพื่อให้ทำงานได้ และนั่นหมายความว่าฟังก์ชัน ff สามารถใช้ได้หลังจาก a->b เท่านั้น คือการสมัคร

person lingxuan deng    schedule 06.01.2018