Sự khác biệt giữa. (dấu chấm) và $ (ký hiệu đô la)?


709

Sự khác biệt giữa dấu chấm (.)và ký hiệu đô la là ($)gì?

Theo tôi hiểu, cả hai đều là đường cú pháp vì không cần sử dụng dấu ngoặc đơn.

Câu trả lời:


1226

Các $nhà điều hành là để tránh ngoặc đơn. Bất cứ điều gì xuất hiện sau nó sẽ được ưu tiên hơn bất cứ điều gì đến trước.

Ví dụ: giả sử bạn có một dòng ghi:

putStrLn (show (1 + 1))

Nếu bạn muốn loại bỏ các dấu ngoặc đơn đó, bất kỳ dòng nào sau đây cũng sẽ làm điều tương tự:

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

Mục đích chính của .toán tử không phải là để tránh dấu ngoặc đơn, mà là các hàm chuỗi. Nó cho phép bạn buộc đầu ra của bất cứ thứ gì xuất hiện ở bên phải với đầu vào của bất cứ thứ gì xuất hiện ở bên trái. Điều này thường cũng dẫn đến ít dấu ngoặc đơn hơn, nhưng hoạt động khác nhau.

Quay trở lại ví dụ tương tự:

putStrLn (show (1 + 1))
  1. (1 + 1)không có đầu vào và do đó không thể được sử dụng với .toán tử.
  2. showcó thể lấy Intvà trả lại a String.
  3. putStrLncó thể lấy một Stringvà trả lại một IO ().

Bạn có thể xâu chuỗi showđể putStrLnthích điều này:

(putStrLn . show) (1 + 1)

Nếu đó là quá nhiều dấu ngoặc đơn theo ý thích của bạn, hãy loại bỏ chúng với $toán tử:

putStrLn . show $ 1 + 1

54
Trên thực tế, vì + cũng là một hàm, bạn không thể làm cho nó có tiền tố sau đó cũng soạn nó, như `putStrLn. chỉ . (+) 1 1 `Không phải nó rõ ràng hơn, nhưng ý tôi là ... bạn có thể, phải không?
CodexArcanum

4
@CodexArcanum Trong ví dụ này, một cái gì đó tương tự putStrLn . show . (+1) $ 1sẽ tương đương. Bạn đúng trong hầu hết các toán tử infix (tất cả?) Là các hàm.
Michael Steele

79
Tôi tự hỏi tại sao không ai từng đề cập đến sử dụng như thế nào map ($3). Ý tôi là, tôi chủ yếu sử dụng $để tránh dấu ngoặc đơn, nhưng nó không giống như tất cả những gì họ đang ở đó.
Khối

43
map ($3)là một chức năng của loại Num a => [(a->b)] -> [b]. Nó lấy một danh sách các hàm lấy một số, áp dụng 3 cho tất cả chúng và thu thập kết quả.
Khối

21
Bạn phải cẩn thận khi sử dụng $ với các toán tử khác. "x + f (y + z)" không giống với "x + f $ y + z" bởi vì cái sau thực sự có nghĩa là "(x + f) (y + z)" (tức là tổng của x và f là được coi là một chức năng).
Paul Johnson

186

Chúng có các loại khác nhau và định nghĩa khác nhau:

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

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

($)được dự định để thay thế ứng dụng chức năng bình thường nhưng ở một ưu tiên khác để giúp tránh dấu ngoặc đơn. (.)là để kết hợp hai chức năng với nhau để tạo ra một chức năng mới.

Trong một số trường hợp chúng có thể hoán đổi cho nhau, nhưng nói chung điều này không đúng. Ví dụ điển hình nơi họ là:

f $ g $ h $ x

==>

f . g . h $ x

Nói cách khác trong một chuỗi $s, tất cả trừ cái cuối cùng có thể được thay thế bằng.


1
Điều gì nếu xlà một chức năng? Sau đó bạn có thể sử dụng .như là cuối cùng?
richizy

3
@richizy nếu bạn thực sự áp dụng xtrong bối cảnh này, thì có - nhưng sau đó "cuối cùng" sẽ áp dụng cho một cái gì đó khác x. Nếu bạn không áp dụng x, thì nó không khác gì xlà một giá trị.
GS - Xin lỗi đến

124

Cũng lưu ý rằng đó ($)chức năng nhận dạng chuyên biệt cho các loại chức năng . Hàm nhận dạng trông như thế này:

id :: a -> a
id x = x

Trong khi ($)trông như thế này:

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

Lưu ý rằng tôi đã cố ý thêm dấu ngoặc đơn trong chữ ký loại.

Việc sử dụng ($)thường có thể được loại bỏ bằng cách thêm dấu ngoặc đơn (trừ khi toán tử được sử dụng trong một phần). Vd: f $ g xtrở thành f (g x).

Sử dụng (.)thường khó hơn một chút để thay thế; họ thường cần một lambda hoặc giới thiệu một tham số chức năng rõ ràng. Ví dụ:

f = g . h

trở thành

f x = (g . h) x

trở thành

f x = g (h x)

Hi vọng điêu nay co ich!


"Lưu ý rằng tôi đã cố ý thêm dấu ngoặc đơn trong chữ ký loại." Tôi bối rối ... tại sao bạn làm điều này?
Mateen Ulhaq

3
@MateenUlhaq Loại ($) là (a -> b) -> a -> b, giống như (a -> b) -> (a -> b), nhưng các dấu ngoặc đơn bổ sung làm ở đây thêm một số trong trẻo.
Rudi

2
Ồ, tôi cho là vậy. Tôi đã nghĩ về nó như là một hàm của hai đối số ... nhưng vì curry, nó chính xác tương đương với một hàm trả về một hàm.
Mateen Ulhaq

78

($) cho phép các hàm được nối với nhau mà không cần thêm dấu ngoặc đơn để kiểm soát thứ tự đánh giá:

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

Prelude> head $ tail "asdf"
's'

Toán tử soạn thảo (.)tạo một hàm mới mà không chỉ định các đối số:

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

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

Ví dụ trên có thể minh họa, nhưng không thực sự cho thấy sự tiện lợi của việc sử dụng bố cục. Đây là một tương tự khác:

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

Nếu chúng ta chỉ sử dụng lần thứ ba một lần, chúng ta có thể tránh đặt tên bằng cách sử dụng lambda:

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

Cuối cùng, thành phần cho phép chúng ta tránh lambda:

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

3
Nếu stackoverflow có chức năng kết hợp, tôi thích câu trả lời kết hợp hai giải thích trước với ví dụ trong câu trả lời này.
Chris.Q

59

Phiên bản ngắn và ngọt ngào:

  • ($) gọi hàm là đối số bên trái của nó trên giá trị là đối số bên phải của nó.
  • (.) cấu thành hàm là đối số bên trái của nó trên hàm là đối số bên phải của nó.

29

Một ứng dụng hữu ích và khiến tôi mất một thời gian để tìm ra từ mô tả rất ngắn khi tìm hiểu về bạn một haskell : Kể từ:

f $ x = f x

và dấu ngoặc đơn phía bên phải của một biểu thức có chứa toán tử infix chuyển đổi nó thành một hàm tiền tố, người ta có thể viết ($ 3) (4+)tương tự (++", world") "hello".

Tại sao mọi người sẽ làm điều này? Đối với danh sách các chức năng, ví dụ. Cả hai:

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

và:

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

ngắn hơn map (\x -> x ++ ", world") ...hoặc map (\f -> f 3) .... Rõ ràng, các biến thể sau sẽ dễ đọc hơn đối với hầu hết mọi người.


14
btw, tôi khuyên bạn không nên sử dụng $3mà không có không gian. Nếu Mẫu Haskell được bật, điều này sẽ được phân tích cú pháp dưới dạng mối nối, trong khi $ 3luôn có nghĩa là những gì bạn đã nói. Nhìn chung, dường như có một xu hướng trong Haskell là "đánh cắp" các cú pháp bằng cách nhấn mạnh rằng các toán tử nhất định có khoảng trắng xung quanh chúng được xử lý như vậy.
GS - Xin lỗi đến Monica

1
Đã cho tôi một thời gian để tìm ra cách ngoặc đang làm việc: en.wikibooks.org/wiki/Haskell/...
Casebash

18

Haskell: sự khác biệt giữa .(dấu chấm) và $(ký hiệu đô la)

Sự khác biệt giữa dấu chấm (.)và ký hiệu đô la là ($)gì? Theo tôi hiểu, cả hai đều là đường cú pháp vì không cần sử dụng dấu ngoặc đơn.

Chúng không phải là cú pháp đường vì không cần sử dụng dấu ngoặc đơn - chúng là các hàm, - được trộn, do đó chúng ta có thể gọi chúng là toán tử.

Soạn, (.)và khi nào sử dụng nó.

(.)là hàm soạn thảo. Vì thế

result = (f . g) x

cũng giống như việc xây dựng một hàm truyền kết quả của đối số được truyền gvào f.

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

Sử dụng (.)khi bạn không có sẵn các đối số để chuyển đến các hàm bạn muốn soạn.

Phải kết hợp đúng ($), và khi nào sử dụng nó

($)là một hàm áp dụng kết hợp phải với độ ưu tiên ràng buộc thấp. Vì vậy, nó chỉ đơn thuần là tính toán những thứ bên phải của nó đầu tiên. Như vậy

result = f $ g x

cũng giống như điều này, theo thủ tục (điều quan trọng vì Haskell được đánh giá một cách lười biếng, nó sẽ bắt đầu đánh giá fđầu tiên):

h = f
g_x = g x
result = h g_x

hoặc chính xác hơn:

result = f (g x)

Sử dụng ($)khi bạn có tất cả các biến để đánh giá trước khi bạn áp dụng hàm trước cho kết quả.

Chúng ta có thể thấy điều này bằng cách đọc nguồn cho từng chức năng.

Đọc nguồn

Đây là nguồn cho (.):

-- | 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)

Và đây là nguồn cho ($):

-- | 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

Phần kết luận

Sử dụng thành phần khi bạn không cần phải đánh giá ngay chức năng. Có lẽ bạn muốn truyền hàm kết quả từ thành phần này sang hàm khác.

Sử dụng ứng dụng khi bạn đang cung cấp tất cả các đối số để đánh giá đầy đủ.

Vì vậy, đối với ví dụ của chúng tôi, nó sẽ được ưu tiên về mặt ngữ nghĩa để làm

f $ g x

khi chúng ta có x(hay đúng hơn là gcác đối số) và thực hiện:

f . g

khi chúng ta không


12

... hoặc bạn có thể tránh .và các $công trình xây dựng bằng cách sử dụng đường ống :

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

Đó là sau khi bạn đã thêm chức năng trợ giúp:

(|>) x y = y x

2
Có, |> là toán tử đường ống F #.
dùng1721780

6
Một điều cần lưu ý ở đây là $toán tử của Haskell thực sự hoạt động giống F # <|hơn so với thực tế |>, thông thường trong haskell bạn sẽ viết hàm trên như thế này: third xs = head $ tail $ tail $ xshoặc thậm chí có thể như thế third = head . tail . tail, trong cú pháp kiểu F # sẽ giống như thế này:let third = List.head << List.tail << List.tail
Cà phê điện

1
Tại sao thêm chức năng trợ giúp để làm cho Haskell trông giống F #? -1
vikingsteve

9
Việc lật $đã có sẵn và được gọi là & hackage.haskell.org/package/base-4.8.0.0/docs/ Kẻ
vỗ

11

Một cách tuyệt vời để tìm hiểu thêm về bất cứ điều gì (bất kỳ chức năng) là nhớ rằng tất cả mọi thứ là một chức năng! Câu thần chú chung đó có ích, nhưng trong các trường hợp cụ thể như toán tử, sẽ giúp ghi nhớ mẹo nhỏ này:

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

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

Chỉ cần nhớ sử dụng :ttự do, và bọc các nhà khai thác của bạn ()!


11

Quy tắc của tôi rất đơn giản (tôi cũng mới bắt đầu):

  • không sử dụng .nếu bạn muốn truyền tham số (gọi hàm) và
  • không sử dụng $nếu chưa có tham số nào (soạn một hàm)

Đó là

show $ head [1, 2]

nhưng không bao giờ:

show . head [1, 2]

3
Heuristic tốt, nhưng có thể sử dụng nhiều ví dụ hơn
Zoey Hewll 22/2/2017

0

Tôi nghĩ một ví dụ ngắn về nơi bạn sẽ sử dụng .$sẽ không giúp làm rõ mọi thứ.

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

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

Lưu ý rằng đó times6là một chức năng được tạo ra từ thành phần chức năng.


0

Tất cả các câu trả lời khác là khá tốt. Nhưng có một chi tiết khả năng sử dụng quan trọng về cách ghc đối xử với $, rằng trình kiểm tra loại ghc cho phép instatiarion với các loại xếp hạng / định lượng cao hơn. Nếu bạn nhìn vào loại $ idví dụ, bạn sẽ thấy nó sẽ có một hàm có đối số chính là hàm đa hình. Những điều nhỏ nhặt như thế không có được sự linh hoạt tương tự với một toán tử khó chịu tương đương. (Điều này thực sự khiến tôi tự hỏi liệu $! Có xứng đáng được đối xử như vậy hay không)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.