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.
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:
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)
không có đầu vào và do đó không thể được sử dụng với .
toán tử.show
có thể lấy Int
và trả lại a String
.putStrLn
có thể lấy một String
và trả lại một IO ()
.Bạn có thể xâu chuỗi show
để putStrLn
thí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
putStrLn . show . (+1) $ 1
sẽ 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.
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 ở đó.
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ả.
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.
x
là một chức năng? Sau đó bạn có thể sử dụng .
như là cuối cùng?
x
trong 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ì x
là một giá trị.
Cũng lưu ý rằng đó ($)
là 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 x
trở 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!
($)
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"
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.
$3
mà 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 $ 3
luô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.
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ử.
(.)
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 g
và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.
($)
, 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.
Đâ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
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à g
các đối số) và thực hiện:
f . g
khi chúng ta không
... 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
$
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 $ xs
hoặ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
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
và
:t ($)
($) :: (a -> b) -> a -> b
Chỉ cần nhớ sử dụng :t
tự do, và bọc các nhà khai thác của bạn ()
!
Quy tắc của tôi rất đơn giản (tôi cũng mới bắt đầu):
.
nếu bạn muốn truyền tham số (gọi hàm) và$
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]
Tôi nghĩ một ví dụ ngắn về nơi bạn sẽ sử dụng .
và $
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 đó times6
là một chức năng được tạo ra từ thành phần chức năng.
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 $ id
ví 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)