Haskell: <*> ออกเสียงอย่างไร? [ปิด]


109

คุณออกเสียงฟังก์ชันเหล่านี้ในประเภทการใช้งานอย่างไร:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(นั่นคือถ้าพวกเขาไม่ใช่ตัวดำเนินการพวกเขาจะเรียกว่าอะไร?)

โปรดทราบว่าหากคุณสามารถเปลี่ยนชื่อpureเป็นสิ่งที่เป็นมิตรกับนักคณิตศาสตร์ที่ไม่ใช่นักคณิตศาสตร์ได้คุณจะเรียกมันว่าอะไร?


6
@ เจคูเปอร์ ... คุณจะได้ยินว่าเราออกเสียงอย่างไร? :) คุณอาจต้องการโพสต์คำขอบน meta.stackoverflow.com สำหรับคุณสมบัติการบันทึกเสียงและการเล่น :)
Kiril

8
มันออกเสียงว่า "Good grief, they was really run out of Operator, they don't?" นอกจากนี้ยังเป็นชื่อที่ดีสำหรับอาจจะpure makeApplicative
ชัค

@ ลิริกเอาล่ะฉันเดาว่าฉันหมายถึง "whaddya เรียกสิ่งนี้ว่า" :) @ ชัคโพสต์pureคำแนะนำของคุณเพื่อเป็นคำตอบและฉันจะโหวตให้คุณ
J Cooper

6
(<*>) คือ Control.Applicative เวอร์ชันของ "ap" ของ Control.Monad ดังนั้น "ap" จึงน่าจะเป็นชื่อที่เหมาะสมที่สุด
Edward KMETT

11
ฉันจะเรียกมันว่าไซคลอปส์ แต่นั่นเป็นแค่ฉัน
RCIX

คำตอบ:


245

ขออภัยฉันไม่รู้คณิตศาสตร์ของตัวเองจริงๆจึงอยากรู้ว่าจะออกเสียงฟังก์ชันในประเภทการใช้งานอย่างไร

ฉันคิดว่าการรู้คณิตศาสตร์ของคุณไม่เกี่ยวข้องกับที่นี่ อย่างที่คุณทราบกันดีอยู่แล้วว่า 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 liftM22 ในชื่อหมายถึงความจริงที่ว่ามันทำงานกับฟังก์ชันของอาร์กิวเมนต์สองตัว มีฟังก์ชันที่คล้ายกันสำหรับฟังก์ชันอาร์กิวเมนต์ 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และเราไม่สามารถก่อให้เกิดค่าชนิดพลดังนั้นเราจะแก้ไขประเภทของการเป็นmfEmptyf ()

เราไม่ต้องการบังคับ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สู่การคำนวณที่มีอยู่

ทั้งสามเป็นหัวใจสำคัญเพียงแค่แอปพลิเคชั่นฟังก์ชั่นปกติที่เพิ่มขึ้นเล็กน้อย


6
@ Colin Cochrane: แน่ใจหรือว่าไม่ได้สะกดคำว่า "ยืดยาว" ที่นั่น? :) แต่เดี๋ยวก่อนฉันจะเอา! ฉันมักจะรู้สึกอย่างนั้นApplicativeและรูปแบบสำนวนที่ใช้งานได้นั้นไม่ได้รับความรักเพียงพอดังนั้นฉันจึงไม่สามารถต้านทานโอกาสที่จะยกย่องคุณธรรมของมันได้เล็กน้อยเพื่ออธิบายว่าฉัน (ไม่) ออกเสียง(<*>)อย่างไร
CA McCann

+1! โปรดอย่าลังเลที่จะยืดยาวในทำนองเดียวกันที่stackoverflow.com/questions/2104446/…
Greg Bacon

6
Haskell จะมีน้ำตาลไวยากรณ์สำหรับApplicative's! สิ่งที่ต้องการ[| f a b c d |](ตามที่แนะนำโดยเอกสารต้นฉบับ) จากนั้นเราก็ไม่จำเป็นต้องใช้ตัว<*>รวมกันและคุณจะอ้างถึงนิพจน์ดังกล่าวเป็นตัวอย่างของ "แอปพลิเคชันฟังก์ชันในบริบททางเพศ"
Tom Crockett

1
@FredOverflow: Monadไม่มีฉันหมายถึง หรือFunctorหรือMonoidสิ่งอื่นใดที่มีคำที่เป็นที่ยอมรับซึ่งเกี่ยวข้องกับคำคุณศัพท์น้อยกว่าสามคำ "การใช้งาน" เป็นเพียงชื่อที่ไม่น่าสนใจ แต่มีเหตุผลในการอธิบายชื่อที่ตบเข้ากับสิ่งที่ค่อนข้างจำเป็น
CA McCann

1
@pelotom: ดู [ stackoverflow.com/questions/12014524/…ที่คนใจดีแสดงให้ฉันเห็นสองวิธีในการรับสัญกรณ์เกือบนั้น
AndrewC

21

เนื่องจากฉันไม่มีความทะเยอทะยานในการปรับปรุงคำตอบทางเทคนิคของ CA McCannฉันจะจัดการกับคำตอบที่นุ่มนวลกว่านี้:

ถ้าคุณสามารถเปลี่ยนชื่อpureเป็นสิ่งที่เป็นมิตรกับ podunks อย่างฉันได้คุณจะเรียกมันว่าอะไร?

เพื่อเป็นอีกทางเลือกหนึ่งโดยเฉพาะอย่างยิ่งเมื่อไม่มีการสิ้นสุดของการร้องไห้ที่เต็มไปด้วยความโกรธและการทรยศต่อMonadเวอร์ชันที่เรียกว่า " return" ฉันขอเสนอชื่ออื่นซึ่งแนะนำการทำงานของมันในลักษณะที่สามารถตอบสนองความจำเป็นที่สุดของโปรแกรมเมอร์ที่จำเป็น หวังว่าทุกคนจะสามารถบ่นได้เหมือนกันเกี่ยวกับ: inject.

รับค่า "ฉีด" ลงในFunctor, Applicative, Monadหรือสิ่งที่มีคุณ ฉันโหวตให้ " inject" และฉันอนุมัติข้อความนี้


4
ฉันมักจะเอนเอียงไปทางบางอย่างเช่น "หน่วย" หรือ "ลิฟท์" แต่คำเหล่านี้มีความหมายอื่นมากเกินไปใน Haskell injectเป็นชื่อที่ยอดเยี่ยมและน่าจะดีกว่าของฉันแม้ว่าจะเป็นโน้ตด้านข้างเล็กน้อย แต่ฉันคิดว่า - Smalltalk และ Ruby สำหรับวิธีการพับซ้ายของบางประเภท ฉันไม่เคยเข้าใจการเลือกชื่อนั้นเลยแม้ว่า ...
CA McCann

3
นี่เป็นกระทู้เก่ามาก แต่ฉันคิดว่าinjectใน Ruby & Smalltalk ถูกใช้เพราะมันเหมือนกับว่าคุณกำลัง "ฉีด" ตัวดำเนินการระหว่างแต่ละองค์ประกอบในรายการ อย่างน้อยนั่นคือสิ่งที่ฉันคิดมาตลอด
Jonathan Sterling

1
หากต้องการดึงด้ายด้านข้างเก่าขึ้นมาอีกครั้ง : คุณไม่ได้ฉีดยาตัวดำเนินการคุณกำลังเปลี่ยน (กำจัด) ตัวสร้างที่มีอยู่แล้ว (ดูรอบวิธีอื่น ๆ ที่คุณกำลังฉีดข้อมูลเก่าเป็นชนิดใหม่.) foldrสำหรับรายการการกำจัดเป็นเพียง (คุณแทนที่(:)และ[]โดยที่(:)รับ 2 args และ[]เป็นค่าคงที่ดังนั้นfoldr (+) 0 (1:2:3:[])1+2+3+0) บนBoolมันเป็นเพียงif- then- else(ค่าคงที่สองตัวเลือกหนึ่งตัว) และMaybeเรียกว่าmaybe... Haskell ไม่มีชื่อ / ฟังก์ชันเดียวสำหรับสิ่งนี้เนื่องจากทั้งหมดมีประเภทที่แตกต่างกัน (โดยทั่วไปการกำจัดเป็นเพียงการเรียกซ้ำ / การเหนี่ยวนำ)
ไม่มีใคร

@CAMcCann Smalltalk ได้ชื่อนั้นมาจากเพลง Arlo Guthrieเกี่ยวกับร่างสำหรับสงครามเวียดนามซึ่งชายหนุ่มผู้โชคร้ายถูกรวบรวมคัดเลือกบางครั้งถูกปฏิเสธและอื่น ๆ ถูกฉีดยา
Tom Anderson

7

โดยสรุป:

  • <*>คุณสามารถเรียกมันใช้ ดังนั้นMaybe f <*> Maybe aสามารถออกเสียงเป็นใช้Maybe fMaybe aมากกว่า

  • คุณสามารถเปลี่ยนชื่อpureไปofเช่นห้องสมุด JavaScript หลายคนทำ ใน JS คุณสามารถสร้างด้วยMaybeMaybe.of(a)

นอกจากนี้วิกิพีเดียของ Haskell ยังมีหน้าเกี่ยวกับการออกเสียงของตัวดำเนินการภาษาที่นี่


3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

ที่มา: Haskell Programming จาก First Principlesโดย Chris Allen และ Julie Moronuki


ชื่อเหล่านั้นยังไม่ถูกต้องเท่าที่ฉันสามารถบอกได้
dfeuer

@dfeuer รอ Haskellers รุ่นต่อไปที่ใช้หนังสือเล่มนั้นเป็นสื่อการเรียนรู้หลัก
dmvianna

1
มันอาจเกิดขึ้น แม้ว่าชื่อจะแย่มากเนื่องจากไม่มีส่วนเกี่ยวข้องกับความหมาย
dfeuer

1
@dfeuer ฉันไม่เห็นทางออกที่ดีเลย "ap" / "apply" นั้นคลุมเครือเหมือนกับ "tie fighter" ทุกอย่างเป็นแอปพลิเคชั่นฟังก์ชั่น อย่างไรก็ตามชื่อที่ออกมาจากสีน้ำเงินอาจได้รับความหมายจากการใช้งาน “ แอปเปิ้ล” เป็นตัวอย่างที่ดี อย่างไรก็ตามการกลับมาของ Monad นั้นเป็นการใช้งานที่บริสุทธิ์ ไม่มีสิ่งประดิษฐ์ที่นี่
dmvianna
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.