อะไรคือความแตกต่างระหว่าง . (จุด) และ $ (เครื่องหมายดอลลาร์)?


709

อะไรคือความแตกต่างระหว่างจุด(.)และเครื่องหมายดอลลาร์($)?

ดังที่ฉันเข้าใจแล้วพวกเขาทั้งคู่เป็นน้ำตาลประโยคโดยไม่จำเป็นต้องใช้วงเล็บ

คำตอบ:


1226

$ผู้ประกอบการคือการหลีกเลี่ยงวงเล็บ สิ่งใดที่ปรากฏขึ้นหลังจากนั้นจะมีความสำคัญมากกว่าสิ่งใด ๆ

ตัวอย่างเช่นสมมติว่าคุณมีบรรทัดที่อ่านว่า:

putStrLn (show (1 + 1))

หากคุณต้องการกำจัดวงเล็บเหล่านั้นบรรทัดใด ๆ ต่อไปนี้จะทำสิ่งเดียวกัน:

putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

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

กลับไปที่ตัวอย่างเดิม:

putStrLn (show (1 + 1))
  1. (1 + 1)ไม่มีอินพุตและดังนั้นจึงไม่สามารถใช้กับ.ผู้ให้บริการได้
  2. showสามารถใช้และส่งกลับIntString
  3. putStrLnสามารถใช้และกลับStringIO ()

คุณสามารถเชื่อมโยงshowกับputStrLnสิ่งนี้:

(putStrLn . show) (1 + 1)

หากวงเล็บนั้นมากเกินไปสำหรับความชอบของคุณให้กำจัดพวกมันด้วย$โอเปอเรเตอร์:

putStrLn . show $ 1 + 1

54
ที่จริงแล้วตั้งแต่ + เป็นฟังก์ชั่นด้วยคุณไม่สามารถทำให้มันเป็นคำนำหน้าแล้วเขียนมันด้วยเช่น `putStrLn แสดง (+) 1 1 `ไม่ใช่ว่ามันชัดเจนกว่า แต่ฉันหมายความว่า ... คุณทำได้ใช่มั้ย
CodexArcanum

4
@CodexArcanum ในตัวอย่างนี้สิ่งที่ต้องการputStrLn . show . (+1) $ 1จะเทียบเท่า คุณถูกต้องในตัวดำเนินการมัด (ทั้งหมด?) ส่วนใหญ่นั้นเป็นฟังก์ชั่น
Michael Steele

79
map ($3)ผมสงสัยว่าทำไมไม่มีใครเคยกล่าวถึงการใช้งานเช่น ฉันหมายถึงฉันส่วนใหญ่ใช้$เพื่อหลีกเลี่ยงวงเล็บเช่นกัน แต่มันก็ไม่เหมือนที่พวกเขามีสำหรับ
ลูกบาศก์

43
map ($3)Num a => [(a->b)] -> [b]เป็นหน้าที่ของประเภท มันใช้รายการของฟังก์ชั่นรับตัวเลข, ใช้ 3 กับพวกเขาทั้งหมดและรวบรวมผลลัพธ์
Cubic

21
คุณต้องระวังเมื่อใช้ $ กับตัวดำเนินการอื่น "x + f (y + z)" ไม่เหมือนกับ "x + f $ y + z" เพราะจริง ๆ แล้วหมายถึง "(x + f) (y + z)" (เช่นผลรวมของ x และ f คือ ถือว่าเป็นฟังก์ชั่น)
พอลจอห์นสัน

186

พวกเขามีประเภทที่แตกต่างกันและคำจำกัดความที่แตกต่างกัน:

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

infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x

($)มีวัตถุประสงค์เพื่อแทนที่แอปพลิเคชันฟังก์ชั่นปกติ แต่มีความสำคัญที่แตกต่างกันเพื่อช่วยหลีกเลี่ยงวงเล็บ (.)มีไว้สำหรับการรวมฟังก์ชันสองฟังก์ชันเข้าด้วยกันเพื่อสร้างฟังก์ชันใหม่

ในบางกรณีพวกเขาสามารถใช้แทนกันได้ แต่นี่ไม่เป็นความจริงโดยทั่วไป ตัวอย่างทั่วไปที่พวกเขาอยู่คือ:

f $ g $ h $ x

==>

f . g . h $ x

ในคำอื่น ๆ ในห่วงโซ่ของ$s ทั้งหมด แต่สุดท้ายจะถูกแทนที่ด้วย.


1
ถ้าxเป็นฟังก์ชั่นล่ะ? คุณสามารถใช้.เป็นตัวสุดท้ายได้หรือไม่?
richizy

3
@richizy ถ้าคุณกำลังใช้จริงxในบริบทนี้แล้วใช่ - แต่แล้ว "สุดท้าย" xใครจะได้รับการนำไปใช้อย่างอื่นที่ไม่ใช่ หากคุณไม่ได้สมัครxก็ไม่ต่างกับxคุณค่า
GS - ขอโทษโมนิก้า

124

นอกจากนี้ยังทราบว่า($)เป็นฟังก์ชั่นเอกลักษณ์เฉพาะประเภทฟังก์ชั่น ฟังก์ชันตัวตนมีลักษณะดังนี้:

id :: a -> a
id x = x

ในขณะที่($)มีลักษณะเช่นนี้:

($) :: (a -> b) -> (a -> b)
($) = id

โปรดทราบว่าฉันตั้งใจเพิ่มวงเล็บพิเศษในลายเซ็นประเภท

การใช้($)สามารถกำจัดได้โดยเพิ่มวงเล็บ (เว้นแต่ผู้ใช้จะใช้ในส่วน) เช่นจะกลายเป็นf $ g xf (g x)

การใช้(.)มักจะยากที่จะเปลี่ยนเล็กน้อย พวกเขามักจะต้องการแลมบ์ดาหรือแนะนำพารามิเตอร์ฟังก์ชันที่ชัดเจน ตัวอย่างเช่น:

f = g . h

กลายเป็น

f x = (g . h) x

กลายเป็น

f x = g (h x)

หวังว่านี่จะช่วยได้!


"โปรดทราบว่าฉันได้เพิ่มวงเล็บพิเศษในลายเซ็นประเภทโดยเจตนา" ฉันสับสน ... ทำไมคุณต้องทำอย่างนี้?
Mateen Ulhaq

3
@MateenUlhaq ประเภทของ ($) คือ (a -> b) -> a -> b ซึ่งเหมือนกับ (a -> b) -> (a -> b) แต่วงเล็บพิเศษจะเพิ่มที่นี่ ความชัดเจน
ฤดี

2
ฉันคิดว่า ฉันคิดว่ามันเป็นฟังก์ชั่นของสองข้อโต้แย้ง ... แต่เนื่องจากการปิดกั้นมันจึงเท่ากับฟังก์ชันที่ส่งคืนฟังก์ชัน
Mateen Ulhaq

78

($) อนุญาตให้ฟังก์ชั่นถูกล่ามโซ่ไว้ด้วยกันโดยไม่ต้องเพิ่มวงเล็บเพื่อควบคุมลำดับการประเมิน:

Prelude> head (tail "asdf")
's'

Prelude> head $ tail "asdf"
's'

ผู้ประกอบการเขียน(.)สร้างฟังก์ชั่นใหม่โดยไม่ต้องระบุข้อโต้แย้ง:

Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'

Prelude> let second = head . tail
Prelude> second "asdf"
's'

ตัวอย่างด้านบนเป็นตัวอย่างเนื้อหา แต่ไม่ได้แสดงความสะดวกสบายในการใช้องค์ประกอบ นี่คือการเปรียบเทียบอื่น:

Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"

หากเราใช้ครั้งที่สามเพียงครั้งเดียวเราสามารถหลีกเลี่ยงการตั้งชื่อโดยใช้แลมบ์ดา:

Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"

ในที่สุดองค์ประกอบก็ช่วยให้เราหลีกเลี่ยงแลมบ์ดาได้:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"

3
หาก stackoverflow มีฟังก์ชันการรวมกันฉันต้องการคำตอบที่รวมสองคำอธิบายก่อนหน้านี้กับตัวอย่างในคำตอบนี้
Chris.Q

59

เวอร์ชั่นสั้นและหวาน:

  • ($) เรียกใช้ฟังก์ชันซึ่งเป็นอาร์กิวเมนต์ซ้ายมือของค่าซึ่งเป็นอาร์กิวเมนต์ขวา
  • (.) ประกอบด้วยฟังก์ชันซึ่งเป็นอาร์กิวเมนต์ซ้ายมือบนฟังก์ชันซึ่งเป็นอาร์กิวเมนต์ขวา

29

แอปพลิเคชันหนึ่งที่มีประโยชน์และใช้เวลาพอสมควรในการหาคำอธิบายสั้น ๆจากการเรียนรู้เกี่ยวกับฮาสเคล : ตั้งแต่:

f $ x = f x

และวงเล็บด้านขวามือของนิพจน์ที่มีโอเปอเรเตอร์ infix แปลงเป็นฟังก์ชันคำนำหน้าเราสามารถเขียน($ 3) (4+)คล้ายกัน(++", world") "hello"ได้

ทำไมทุกคนจะทำเช่นนี้? สำหรับรายการฟังก์ชั่นเช่น ทั้งสอง:

map (++", world") ["hello","goodbye"]`

และ:

map ($ 3) [(4+),(3*)]

จะสั้นกว่าหรือmap (\x -> x ++ ", world") ... map (\f -> f 3) ...เห็นได้ชัดว่ารุ่นหลังจะสามารถอ่านได้มากขึ้นสำหรับคนส่วนใหญ่


14
btw ฉันอยากจะแนะนำให้ใช้$3โดยไม่มีที่ว่าง หากเปิดใช้งานเทมเพลตแฮสเคลล์สิ่งนี้จะถูกแยกวิเคราะห์เป็นรอยต่อในขณะที่$ 3หมายถึงสิ่งที่คุณพูดเสมอ โดยทั่วไปดูเหมือนว่าจะมีแนวโน้มใน Haskell ที่จะ "ขโมย" บิตของไวยากรณ์โดยยืนยันว่าผู้ประกอบการบางรายมีช่องว่างรอบตัวพวกเขาที่จะได้รับการปฏิบัติเช่นนี้
GS - ขอโทษที่โมนิก้า

1
เอาฉันสักครู่เพื่อดูว่าวงเล็บทำงานอย่างไร: en.wikibooks.org/wiki/Haskell/ …
Casebash

18

Haskell: ความแตกต่างระหว่าง.(dot) และ$(เครื่องหมายดอลลาร์)

อะไรคือความแตกต่างระหว่างจุด(.)และเครื่องหมายดอลลาร์($)? ดังที่ฉันเข้าใจแล้วพวกเขาทั้งคู่เป็นน้ำตาลประโยคโดยไม่จำเป็นต้องใช้วงเล็บ

พวกเขาไม่ได้เป็นน้ำตาล syntactic เพราะไม่จำเป็นต้องใช้วงเล็บ - พวกเขามีฟังก์ชั่น - ผสมดังนั้นเราจึงอาจเรียกพวกเขาว่าผู้ประกอบการ

เขียน(.)และใช้เมื่อใด

(.)เป็นฟังก์ชั่นการเขียน ดังนั้น

result = (f . g) x

เป็นเช่นเดียวกับการสร้างฟังก์ชั่นที่ส่งผ่านผลของการโต้แย้งของตนผ่านไปเพื่อgf

h = \x -> f (g x)
result = h x

ใช้(.)เมื่อคุณไม่มีข้อโต้แย้งเพื่อส่งผ่านไปยังฟังก์ชันที่คุณต้องการเขียน

ใช้การเชื่อมโยงที่ถูกต้อง($)และเมื่อใดที่จะใช้

($)เป็นฟังก์ชั่นการใช้งานที่สัมพันธ์กันทางขวาโดยมีลำดับความสำคัญต่ำ ดังนั้นมันจึงคำนวณสิ่งต่าง ๆ ทางด้านขวาของมันก่อน ดังนั้น,

result = f $ g x

เหมือนกับขั้นตอนนี้ (ซึ่งมีความสำคัญเนื่องจาก Haskell ประเมินอย่างขี้เกียจมันจะเริ่มประเมินfก่อน):

h = f
g_x = g x
result = h g_x

หรือสั้นกระชับ:

result = f (g x)

ใช้($)เมื่อคุณมีตัวแปรทั้งหมดเพื่อประเมินผลก่อนที่คุณจะใช้ฟังก์ชันก่อนหน้านี้กับผลลัพธ์

เราสามารถเห็นสิ่งนี้ได้โดยการอ่านซอร์สสำหรับแต่ละฟังก์ชั่น

อ่านแหล่งที่มา

นี่คือแหล่งที่มาของ(.):

-- | Function composition.
{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

และนี่คือที่มาสำหรับ($):

-- | Application operator.  This operator is redundant, since ordinary
-- application @(f x)@ means the same as @(f '$' x)@. However, '$' has
-- low, right-associative binding precedence, so it sometimes allows
-- parentheses to be omitted; for example:
--
-- >     f $ g $ h x  =  f (g (h x))
--
-- It is also useful in higher-order situations, such as @'map' ('$' 0) xs@,
-- or @'Data.List.zipWith' ('$') fs xs@.
{-# INLINE ($) #-}
($)                     :: (a -> b) -> a -> b
f $ x                   =  f x

ข้อสรุป

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

ใช้แอปพลิเคชันเมื่อคุณระบุอาร์กิวเมนต์ทั้งหมดเพื่อประเมินผลเต็มรูปแบบ

สำหรับตัวอย่างของเรามันน่าจะเหมาะสมกว่าที่จะทำ

f $ g x

เมื่อเรามีx(หรือมากกว่าgอาร์กิวเมนต์ของ) และทำ:

f . g

เมื่อเราทำไม่ได้


12

... หรือคุณสามารถหลีกเลี่ยง.และ$สร้างโดยใช้pipelining :

third xs = xs |> tail |> tail |> head

หลังจากที่คุณเพิ่มเข้าไปในฟังก์ชั่นตัวช่วย:

(|>) x y = y x

2
ใช่ |> เป็นผู้ดำเนินการไพพ์ไลน์ F #
user1721780

6
สิ่งหนึ่งที่ควรทราบที่นี่คือ$ตัวดำเนินการของ Haskell ใช้งานได้เหมือน F # <|มากกว่า|>ปกติโดยทั่วไปใน Haskell คุณจะต้องเขียนฟังก์ชันข้างต้นดังนี้: third xs = head $ tail $ tail $ xsหรือบางทีอย่างthird = head . tail . tailนั้นซึ่งในไวยากรณ์ F # -style จะเป็นดังนี้:let third = List.head << List.tail << List.tail
กาแฟไฟฟ้า

1
ทำไมต้องเพิ่มฟังก์ชั่นตัวช่วยเพื่อทำให้ Haskell มีลักษณะเหมือน F # -1
vikingsteve

9
การพลิก$มีอยู่แล้วและเรียกว่า& hackage.haskell.org/package/base-4.8.0.0/docs/…
pat

11

วิธีที่ยอดเยี่ยมในการเรียนรู้เพิ่มเติมเกี่ยวกับสิ่งใด (ฟังก์ชั่นใด ๆ ) คือการจำไว้ว่าทุกสิ่งเป็นฟังก์ชั่น! มนต์ทั่วไปนั้นช่วยได้ แต่ในบางกรณีเช่นตัวดำเนินการมันช่วยให้จำเคล็ดลับเล็กน้อยนี้ได้:

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

และ

:t ($)
($) :: (a -> b) -> a -> b

เพียงจำไว้ว่าให้ใช้:tอย่างอิสระและห่อผู้ให้บริการของคุณไว้()!


11

กฎของฉันก็เรียบง่าย (ฉันก็เริ่มด้วย):

  • ห้ามใช้.หากคุณต้องการส่งผ่านพารามิเตอร์ (เรียกใช้ฟังก์ชัน) และ
  • อย่าใช้$ถ้าไม่มีพารามิเตอร์ (เขียนฟังก์ชั่น)

นั่นคือ

show $ head [1, 2]

แต่ไม่เคย:

show . head [1, 2]

3
การแก้ปัญหาที่ดี แต่สามารถใช้ตัวอย่างเพิ่มเติมได้
Zoey Hewll

0

ฉันคิดว่าเป็นตัวอย่างสั้น ๆ ว่าคุณจะใช้ที่ใด.และ$จะไม่ช่วยชี้แจงสิ่งต่าง ๆ

double x = x * 2
triple x = x * 3
times6 = double . triple

:i times6
times6 :: Num c => c -> c

โปรดทราบว่าtimes6เป็นฟังก์ชั่นที่สร้างขึ้นจากองค์ประกอบของฟังก์ชั่น


0

คำตอบอื่น ๆ ทั้งหมดค่อนข้างดี แต่มีรายละเอียดการใช้งานที่สำคัญเกี่ยวกับวิธีที่ ghc ปฏิบัติต่อ $ ว่าตัวตรวจสอบประเภท ghc อนุญาตให้ติดตั้ง instatiarion ที่มีประเภทอันดับ / ปริมาณที่สูงขึ้น ถ้าคุณดูที่ประเภทของ $ idตัวอย่างคุณจะพบว่ามันจะใช้ฟังก์ชั่นที่มีอาร์กิวเมนต์เป็นฟังก์ชัน polymorphic สิ่งเล็ก ๆ น้อย ๆ เช่นนั้นไม่ได้รับความยืดหยุ่นเท่ากันกับผู้ปฏิบัติงานอารมณ์เสียที่เทียบเท่า (อันนี้ทำให้ฉันสงสัยว่า $! สมควรได้รับการรักษาแบบเดียวกันหรือไม่)

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