Dot Operator ใน Haskell: ต้องการคำอธิบายเพิ่มเติม


87

ฉันพยายามทำความเข้าใจว่าตัวดำเนินการ dot ทำอะไรในรหัส Haskell นี้:

sumEuler = sum . (map euler) . mkList

ซอร์สโค้ดทั้งหมดอยู่ด้านล่าง

ความเข้าใจของฉัน

ตัวดำเนินการ dot กำลังรับฟังก์ชันทั้งสองsumและผลลัพธ์ของmap eulerและผลลัพธ์ของmkListเป็นอินพุต

แต่sumฟังก์ชันไม่ใช่อาร์กิวเมนต์ของฟังก์ชันใช่ไหม? เกิดอะไรขึ้นที่นี่?

นอกจากนี้สิ่งที่กำลัง(map euler)ทำอยู่?

รหัส

mkList :: Int -> [Int]
mkList n = [1..n-1]

euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))

sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList

คำตอบ:


139

พูดง่ายๆ.คือองค์ประกอบของฟังก์ชันเช่นเดียวกับในวิชาคณิตศาสตร์:

f (g x) = (f . g) x

ในกรณีของคุณคุณกำลังสร้างฟังก์ชันใหม่sumEulerซึ่งสามารถกำหนดได้เช่นนี้:

sumEuler x = sum (map euler (mkList x))

สไตล์ในตัวอย่างของคุณเรียกว่าสไตล์ "ไม่มีจุด" - อาร์กิวเมนต์ของฟังก์ชันจะถูกละเว้น สิ่งนี้ทำให้โค้ดชัดเจนขึ้นในหลาย ๆ กรณี (อาจเป็นเรื่องยากที่จะส่งเสียงครวญครางในครั้งแรกที่คุณเห็น แต่คุณจะชินกับมันหลังจากนั้นไม่นานมันเป็นสำนวนของ Haskell ทั่วไป)

หากคุณยังสับสนอยู่อาจช่วยให้เชื่อมโยง .กับท่อ UNIX ได้ หากf's เอาท์พุทจะกลายเป็นgของการป้อนข้อมูลที่มีการส่งออกจะกลายเป็นของการป้อนข้อมูลที่คุณต้องการเขียนว่าในบรรทัดคำสั่งเช่นh f < x | g | hใน Haskell, .ทำงานเหมือนยูนิกซ์|แต่ "ถอยหลัง" h . g . f $ x- ฉันพบว่าสัญกรณ์นี้มีประโยชน์มากเมื่อพูดกำลังประมวลผลรายการ แทนที่จะใช้โครงสร้างที่ดูเทอะทะmap (\x -> x * 2 + 10) [1..10]คุณสามารถเขียน(+10) . (*2) <$> [1..10]ได้ (และถ้าคุณต้องการใช้เฉพาะฟังก์ชันนั้นกับค่าเดียวมัน(+10) . (*2) $ 10สอดคล้องกัน!)

Haskell wiki มีบทความดีๆพร้อมรายละเอียดเพิ่มเติม: http://www.haskell.org/haskellwiki/Pointfree


1
เล่นลิ้นเล็กน้อย: ข้อมูลโค้ดแรกไม่ใช่ Haskell ที่ถูกต้อง
SwiftsNamesake

2
@SwiftsNamesake สำหรับพวกเราที่ไม่คล่องใน Haskell คุณแค่หมายความว่าเครื่องหมายเท่ากับตัวเดียวไม่มีความหมายที่นี่? (ตัวอย่างข้อมูลควรได้รับการจัดรูปแบบ " f (g x)= (f . g) x"?) หรืออย่างอื่น?
user234461

1
@ user234461 เป๊ะใช่เลย คุณต้องการ==แทนถ้าคุณต้องการ Haskell มาตรฐานที่ถูกต้อง
SwiftsNamesake

ตัวอย่างเล็ก ๆ น้อย ๆ ที่ด้านบนเป็นเพียงทอง เช่นเดียวกับคำตอบอื่น ๆ ที่นี่ถูกต้อง แต่ตัวอย่างข้อมูลนั้นคลิกตรงโดยสังหรณ์ใจในหัวของฉันซึ่งทำให้ไม่จำเป็นต้องอ่านคำตอบที่เหลือของคุณ
Tarick Welling

24

. ตัวดำเนินการเขียนฟังก์ชัน ตัวอย่างเช่น,

a . b

ที่ไหนและBมีฟังก์ชั่นใหม่เป็นฟังก์ชั่นที่วิ่งขข้อโต้แย้งของมันแล้วในผลลัพธ์เหล่านั้น รหัสของคุณ

sumEuler = sum . (map euler) . mkList

เหมือนกับ:

sumEuler myArgument = sum (map euler (mkList myArgument))

แต่หวังว่าจะอ่านง่ายขึ้น เหตุผลที่มี parens อยู่รอบ ๆmap eulerเพราะมันทำให้ชัดเจนขึ้นว่ามีการประกอบ 3 ฟังก์ชัน: sum , map eulerและmkList - map eulerเป็นฟังก์ชันเดียว


24

sumเป็นฟังก์ชั่นในโหมโรง Haskell sumEulerไม่อาร์กิวเมนต์ มันมีประเภท

Num a => [a] -> a

ตัวดำเนินการองค์ประกอบของฟังก์ชัน. มีชนิด

(b -> c) -> (a -> b) -> a -> c

ดังนั้นเราจึงมี

           euler           ::  Int -> Int
       map                 :: (a   -> b  ) -> [a  ] -> [b  ]
      (map euler)          ::                 [Int] -> [Int]
                    mkList ::          Int -> [Int]
      (map euler) . mkList ::          Int ->          [Int]
sum                        :: Num a =>                 [a  ] -> a
sum . (map euler) . mkList ::          Int ->                   Int

โปรดทราบว่าIntเป็นตัวอย่างของNumคลาสประเภทนี้


11

. ตัวดำเนินการใช้สำหรับองค์ประกอบของฟังก์ชัน เช่นเดียวกับคณิตศาสตร์ถ้าคุณต้องใช้ฟังก์ชัน f (x) และ g (x) f g กลายเป็น f (g (x))

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

คืออะไรคือมันรับฟังก์ชั่นที่มีอาร์กิวเมนต์พูดสองอาร์กิวเมนต์มันใช้อาร์กิวเมนต์ euler (map euler) ใช่ไหม? และผลลัพธ์คือฟังก์ชันใหม่ซึ่งใช้อาร์กิวเมนต์เดียวเท่านั้น

ผลรวม (แผนที่ euler) mkList นั้นเป็นวิธีที่ยอดเยี่ยมในการรวบรวมทุกสิ่งเข้าด้วยกัน ฉันต้องบอกว่า Haskell ของฉันค่อนข้างสนิม แต่บางทีคุณอาจจะรวมฟังก์ชั่นสุดท้ายนั้นเข้าด้วยกัน?


6

Dot Operator ใน Haskell

ฉันพยายามทำความเข้าใจว่าตัวดำเนินการ dot ทำอะไรในรหัส Haskell นี้:

sumEuler = sum . (map euler) . mkList

คำตอบสั้น ๆ

รหัสเทียบเท่าที่ไม่มีจุดนั่นเป็นเพียง

sumEuler = \x -> sum ((map euler) (mkList x))

หรือไม่มีแลมด้า

sumEuler x = sum ((map euler) (mkList x))

เนื่องจากจุด (.) แสดงถึงองค์ประกอบของฟังก์ชัน

คำตอบที่ยาวขึ้น

ก่อนอื่นมาทำให้การใช้งานบางส่วนง่ายขึ้นeulerเพื่อmap:

map_euler = map euler
sumEuler = sum . map_euler . mkList

ตอนนี้เรามีจุด จุดเหล่านี้บ่งชี้อะไร

จากแหล่งที่มา :

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

ดังนั้นจึง(.)เป็นผู้ประกอบการเขียน

เขียน

ในทางคณิตศาสตร์เราอาจเขียนองค์ประกอบของฟังก์ชัน f (x) และ g (x) นั่นคือ f (g (x)) เป็น

(f ∘ก.) (x)

ซึ่งสามารถอ่านได้ว่า "f ประกอบด้วย g"

ดังนั้นใน Haskell สามารถเขียน f ∘ g หรือ f ประกอบด้วย g ได้:

f . g

การจัดองค์ประกอบเป็นแบบเชื่อมโยงซึ่งหมายความว่า f (g (h (x))) ซึ่งเขียนด้วยตัวดำเนินการองค์ประกอบสามารถเว้นวงเล็บไว้โดยไม่มีความคลุมเครือ

นั่นคือเนื่องจาก (f ∘ g) ∘ h เทียบเท่ากับ f ∘ (g ∘ h) เราจึงสามารถเขียน f ∘ g ∘ h ได้

วนกลับ

วนกลับไปที่การทำให้เข้าใจง่ายก่อนหน้านี้สิ่งนี้:

sumEuler = sum . map_euler . mkList

หมายความว่าsumEulerเป็นองค์ประกอบที่ไม่ได้ใช้ของฟังก์ชันเหล่านั้น:

sumEuler = \x -> sum (map_euler (mkList x))

4

ตัวดำเนินการจุดใช้ฟังก์ชันทางด้านซ้าย ( sum) กับเอาต์พุตของฟังก์ชันทางด้านขวา ในกรณีของคุณคุณจะผูกมัดฟังก์ชั่นหลายแห่งด้วยกัน - คุณผ่านผลจากการmkListไปแล้วผ่านผลจากการที่ (map euler) ไซต์นี้มีข้อมูลเบื้องต้นที่ดีเกี่ยวกับแนวคิดหลายประการsum

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.