ทำไมถึงมีฟังก์ชั่น `map` มากมายสำหรับประเภทต่าง ๆ ใน F #


9

ฉันกำลังเรียนรู้ F # ฉันเริ่ม FP กับ Haskell และฉันอยากรู้เรื่องนี้

เนื่องจาก F # เป็นภาษา. NET ดูเหมือนว่าจะเหมาะสมกว่าสำหรับฉันที่จะประกาศอินเทอร์เฟซเช่นMappableเดียวกับFunctorคลาสประเภทHaskell

ป้อนคำอธิบายรูปภาพที่นี่

แต่เช่นเดียวกับภาพข้างบนฟังก์ชั่น F # จะถูกแยกและใช้งานด้วยตัวมันเอง วัตถุประสงค์การออกแบบของการออกแบบดังกล่าวคืออะไร? สำหรับฉันการแนะนำMappable.mapและใช้สิ่งนี้กับข้อมูลแต่ละประเภทจะสะดวกกว่า


คำถามนี้ไม่ได้อยู่ในนั้น ไม่ใช่ปัญหาการเขียนโปรแกรม ฉันขอแนะนำให้คุณถามใน F # Slack หรือฟอรัมสนทนาอื่น ๆ
Bent Tranberg

5
@BentTranberg กรุณาอ่านอย่างทั่วถึงThe community is here to help you with specific coding, algorithm, or language problems.รวมไปถึงคำถามการออกแบบภาษาตราบใดที่การปฏิบัติตามเงื่อนไขอื่น ๆ สมบูรณ์
kaefer

3
เรื่องสั้นสั้น ๆ F # ไม่มีคลาสประเภทดังนั้นจึงจำเป็นต้องปรับใช้ใหม่mapและฟังก์ชั่นการสั่งซื้อที่สูงขึ้นทั่วไปสำหรับคอลเลกชันแต่ละประเภท อินเทอร์เฟซจะช่วยได้เพียงเล็กน้อยเนื่องจากมันยังคงต้องการคอลเลกชันแต่ละประเภทเพื่อให้มีการใช้งานแยกกัน
dumetrulo

คำตอบ:


20

ใช่คำถามตรงไปตรงมาบนพื้นผิว แต่ถ้าคุณใช้เวลาในการคิดจนจบคุณจะเข้าสู่ความลึกของทฤษฎีประเภทที่ไม่สามารถวัดได้ และทฤษฎีประเภทก็จ้องมองคุณเช่นกัน

ขั้นแรกคุณต้องเข้าใจอย่างถูกต้องแล้วว่า F # ไม่มีคลาสประเภทและนั่นคือสาเหตุ Mappableแต่คุณเสนออินเตอร์เฟซ ตกลงมาดูกันดีกว่า

สมมติว่าเราสามารถประกาศส่วนต่อประสานดังกล่าวได้ คุณลองจินตนาการดูว่าลายเซ็นของมันจะเป็นอย่างไร?

type Mappable =
    abstract member map : ('a -> 'b) -> 'f<'a> -> 'f<'b>

ไหนfเป็นประเภทการใช้อินเตอร์เฟซ โอ้เดี๋ยวก่อน! F # ไม่มีสิ่งนั้น! นี่fคือตัวแปรชนิดที่สูงกว่าและ F # ไม่มีความเมตตาที่สูงขึ้นเลย ไม่มีวิธีที่จะประกาศฟังก์ชั่นf : 'm<'a> -> 'm<'b>หรืออะไรทำนองนั้น

แต่โอเคสมมติว่าเราได้ข้ามอุปสรรค์นั้นด้วย และตอนนี้เรามีอินเตอร์เฟซMappableที่สามารถดำเนินการโดยList, Array, Seqและอ่างล้างจาน แต่เดี๋ยวก่อน! ตอนนี้เรามีวิธีการแทนฟังก์ชั่นและวิธีการเขียนไม่ดี! ดูที่การเพิ่ม 42 ให้กับองค์ประกอบทั้งหมดของรายการซ้อนกัน:

// Good ol' functions:
add42 nestedList = nestedList |> List.map (List.map ((+) 42))

// Using an interface:
add42 nestedList = nestedList.map (fun l -> l.map ((+) 42))

ดู: ตอนนี้เราต้องใช้แลมบ์ดาแสดงออก! ไม่มีทางที่จะผ่านสิ่งนี้ได้.mapการใช้งานไปยังฟังก์ชั่นอื่นเป็นค่า จุดจบของ "ฟังก์ชั่นเป็นค่า" อย่างมีประสิทธิภาพ (และใช่ฉันรู้ว่าการใช้แลมบ์ดาไม่ได้ดูแย่มากในตัวอย่างนี้ แต่เชื่อฉันสิมันน่าเกลียดมาก)

แต่เดี๋ยวก่อนเรายังไม่เสร็จ ตอนนี้เป็นการเรียกเมธอดการอนุมานชนิดไม่ทำงาน! เนื่องจากลายเซ็นชนิดของวิธีการ. NET ขึ้นอยู่กับชนิดของวัตถุจึงไม่มีวิธีใดที่คอมไพเลอร์จะอนุมานทั้งคู่ นี่เป็นปัญหาที่พบบ่อยมากสำหรับมือใหม่ที่เข้ามาทำงานร่วมกันกับไลบรารี NET และการรักษาเพียงอย่างเดียวคือการให้ลายเซ็นประเภท:

add42 (nestedList : #Mappable) = nestedList.map (fun l -> l.map ((+) 42))

โอ้ แต่นี่ยังไม่เพียงพอ! ถึงแม้ว่าผมจะได้จัดให้มีลายเซ็นสำหรับตัวเองผมไม่ได้ให้ลายเซ็นสำหรับพารามิเตอร์แลมบ์ดาฯnestedList lลายเซ็นดังกล่าวควรเป็นอย่างไร คุณจะบอกว่ามันควรจะเป็นfun (l: #Mappable) -> ...อย่างไร โอ้และตอนนี้เราก็ต้องจัดอันดับประเภท -N ตามที่คุณเห็น#Mappableมันเป็นทางลัดสำหรับ "ประเภทใดก็ได้'aเช่นนั้น'a :> Mappable" - นั่นคือการแสดงออกแลมบ์ดาซึ่งเป็นเรื่องทั่วไป

หรืออีกวิธีหนึ่งเราสามารถกลับไปสู่ความใจดีที่สูงขึ้นและประกาศประเภทที่nestedListแม่นยำยิ่งขึ้น:

add42 (nestedList : 'f<'a<'b>> where 'f :> Mappable, 'a :> Mappable) = ...

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

add42 nestedList = nestedList.map (.map ((+) 42))

ประเภทของสิ่งที่.mapจะเป็นอย่างไร มันจะต้องเป็นประเภทที่มีข้อ จำกัดเหมือนใน Haskell!

.map : Mappable 'f => ('a -> 'b) -> 'f<'a> -> 'f<'b>

ว้าวโอเค การใส่ความจริงที่. NET ไม่อนุญาตให้มีประเภทดังกล่าวมีอยู่จริงเราเพิ่งกลับมาเรียนประเภท!

แต่มีเหตุผลที่ F # ไม่มีคลาสประเภทในตอนแรก หลายแง่มุมของเหตุผลนั้นได้อธิบายไว้ข้างต้น แต่วิธีที่กระชับกว่าคือ: ความเรียบง่ายความเรียบง่าย

คุณเห็นไหมว่านี่เป็นลูกไหมพรม เมื่อคุณมีคลาสประเภทคุณจะต้องมีข้อ จำกัด ความมีน้ำใจที่สูงขึ้นอันดับ -N (หรืออย่างน้อยอันดับ 2) และก่อนที่คุณจะรู้คุณจะต้องขอประเภทที่ไม่น่าจดจำฟังก์ชันประเภท GADT และทั้งหมด ส่วนที่เหลือของมัน

แต่ Haskell จ่ายราคาให้กับสินค้าทุกอย่าง ปรากฎว่าไม่มีวิธีที่ดีในการอนุมานทุกสิ่งนั้น ประเภทที่เรียงลำดับสูงกว่าทำงานได้ แต่ข้อ จำกัด ไม่ได้เป็นเช่นนั้นแล้ว อันดับ -N - อย่าแม้แต่จะฝันถึงมัน และแม้ว่าจะได้ผลก็ตามคุณจะได้รับข้อผิดพลาดประเภทที่คุณต้องมีปริญญาเอกเพื่อให้เข้าใจ และนั่นคือสาเหตุที่ Haskell คุณได้รับการสนับสนุนอย่างอ่อนโยนให้ใส่ลายเซ็นพิมพ์ลงบนทุกสิ่ง ไม่ใช่ทุกอย่างทุกอย่างแต่จริง ๆ แล้วเกือบทุกอย่าง และที่คุณไม่ได้ใส่ลายเซ็นประเภท (เช่นภายในletและwhere ) - เซอร์ไพร์สเซอร์ไพร์สสถานที่เหล่านั้นถูกทำให้เป็นโมฆะจริง ๆ

ใน F # ในทางกลับกันลายเซ็นของพิมพ์นั้นหายากส่วนใหญ่จะใช้สำหรับเอกสารหรือสำหรับ. NET interop นอกเหนือจากสองกรณีนี้คุณสามารถเขียนโปรแกรมขนาดใหญ่ที่ซับซ้อนทั้งหมดใน F # และไม่ใช้ลายเซ็นประเภทหนึ่งครั้ง การอนุมานประเภททำงานได้ดีเพราะไม่มีอะไรซับซ้อนเกินไปหรือคลุมเครือให้จัดการ

และนี่คือข้อได้เปรียบที่ยิ่งใหญ่ของ F # มากกว่า Haskell ใช่ Haskell ช่วยให้คุณแสดงสิ่งที่ซับซ้อนเป็นพิเศษอย่างแม่นยำนั่นเป็นเรื่องดี แต่ F # ช่วยให้คุณล้างแค้นได้ดีเกือบเหมือน Python หรือ Ruby และยังมีคอมไพเลอร์ที่คอยจับคุณถ้าคุณสะดุด

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