Apa perbedaan antara . (titik) dan $ (tanda dolar)?

709

Apa perbedaan antara titik (.)dan tanda dolar ($)?

Seperti yang saya pahami, keduanya adalah gula sintaksis karena tidak perlu menggunakan tanda kurung.

Rabarberski
sumber

Jawaban:

1226

The $operator adalah untuk menghindari tanda kurung. Apa pun yang muncul setelah itu akan diutamakan daripada apa pun yang datang sebelumnya.

Misalnya, Anda memiliki garis yang berbunyi:

putStrLn (show (1 + 1))

Jika Anda ingin menghilangkan tanda kurung itu, salah satu dari baris berikut ini juga akan melakukan hal yang sama:

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

Tujuan utama .operator bukan untuk menghindari tanda kurung, tetapi untuk fungsi rantai. Ini memungkinkan Anda mengikat output apa pun yang muncul di sebelah kanan dengan input apa pun yang muncul di sebelah kiri. Ini biasanya juga menghasilkan lebih sedikit kurung, tetapi bekerja secara berbeda.

Kembali ke contoh yang sama:

putStrLn (show (1 + 1))
  1. (1 + 1)tidak memiliki input, dan karena itu tidak dapat digunakan dengan .operator.
  2. showdapat mengambil Intdan mengembalikan a String.
  3. putStrLndapat mengambil Stringdan mengembalikan sebuah IO ().

Anda dapat rantai showuntuk putStrLnseperti ini:

(putStrLn . show) (1 + 1)

Jika terlalu banyak tanda kurung untuk keinginan Anda, singkirkan mereka dengan $operator:

putStrLn . show $ 1 + 1
Michael Steele
sumber
55
Sebenarnya, karena + adalah fungsi juga, tidak bisakah Anda membuatnya awalan lalu menulisnya juga, seperti `putStrLn. tunjukkan. (+) 1 1 `Bukannya lebih jelas, tapi maksud saya ... Anda bisa, kan?
CodexArcanum
4
@ CodexArcanum Dalam contoh ini, sesuatu seperti putStrLn . show . (+1) $ 1akan setara. Anda benar karena sebagian besar (semua?) Operator infiks adalah fungsi.
Michael Steele 8-10
79
Saya bertanya-tanya mengapa tidak ada yang pernah menyebutkan penggunaan map ($3). Maksudku, aku lebih sering menggunakannya $untuk menghindari tanda kurung, tapi bukan hanya itu yang ada di sana.
Cubic
43
map ($3)adalah fungsi dari tipe Num a => [(a->b)] -> [b]. Dibutuhkan daftar fungsi mengambil nomor, berlaku 3 untuk semuanya dan mengumpulkan hasilnya.
Kubik
21
Anda harus berhati-hati saat menggunakan $ dengan operator lain. "x + f (y + z)" tidak sama dengan "x + f $ y + z" karena yang terakhir sebenarnya berarti "(x + f) (y + z)" (yaitu jumlah x dan f adalah diperlakukan sebagai fungsi).
Paul Johnson
186

Mereka memiliki tipe dan definisi yang berbeda:

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

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

($)dimaksudkan untuk menggantikan aplikasi fungsi normal tetapi pada prioritas yang berbeda untuk membantu menghindari tanda kurung. (.)adalah untuk menyusun dua fungsi bersama untuk membuat fungsi baru.

Dalam beberapa kasus mereka dapat dipertukarkan, tetapi ini tidak benar secara umum. Contoh khas di mana mereka berada adalah:

f $ g $ h $ x

==>

f . g . h $ x

Dengan kata lain dalam rantai $s, semua kecuali yang terakhir dapat digantikan oleh.

GS - Mohon maaf kepada Monica
sumber
1
Bagaimana jika xsuatu fungsi? Bisakah Anda gunakan .sebagai yang terakhir?
richizy
3
@ Richizy jika Anda benar-benar menerapkan xdalam konteks ini, maka ya - tapi kemudian yang "final" akan berlaku untuk sesuatu selain x. Jika Anda tidak mendaftar x, maka tidak ada bedanya dengan xmenjadi nilai.
GS - Minta maaf kepada Monica
124

Juga mencatat bahwa ($)adalah fungsi identitas khusus untuk jenis fungsi . Fungsi identitas terlihat seperti ini:

id :: a -> a
id x = x

Sementara ($)terlihat seperti ini:

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

Perhatikan bahwa saya sengaja menambahkan tanda kurung tambahan pada tanda tangan jenis.

Penggunaan ($)biasanya dapat dihilangkan dengan menambahkan tanda kurung (kecuali operator digunakan dalam bagian). Misalnya: f $ g xmenjadi f (g x).

Penggunaan (.)sering sedikit lebih sulit untuk diganti; mereka biasanya membutuhkan lambda atau pengenalan parameter fungsi eksplisit. Sebagai contoh:

f = g . h

menjadi

f x = (g . h) x

menjadi

f x = g (h x)

Semoga ini membantu!

Martijn
sumber
"Perhatikan bahwa saya sengaja menambahkan tanda kurung tambahan di tanda tangan jenis." Saya bingung ... mengapa Anda melakukan ini?
Mateen Ulhaq
3
@MateenUlhaq Jenis ($) adalah (a -> b) -> a -> b, yang sama dengan (a -> b) -> (a -> b), tetapi kurung tambahan di sini menambahkan beberapa kejelasan.
Rudi
2
Oh, kurasa. Saya memikirkannya sebagai fungsi dari dua argumen ... tetapi karena currying, itu persis sama dengan fungsi yang mengembalikan fungsi.
Mateen Ulhaq
78

($) memungkinkan fungsi dirantai bersama tanpa menambahkan tanda kurung untuk mengontrol urutan evaluasi:

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

Prelude> head $ tail "asdf"
's'

Operator penulisan (.)membuat fungsi baru tanpa menentukan argumen:

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

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

Contoh di atas bisa dibilang ilustratif, tetapi tidak benar-benar menunjukkan kenyamanan menggunakan komposisi. Berikut analogi lainnya:

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

Jika kita hanya menggunakan yang ketiga sekali, kita dapat menghindari memberi nama dengan menggunakan lambda:

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

Akhirnya, komposisi memungkinkan kita menghindari lambda:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"
softmechanics
sumber
3
Jika stackoverflow memiliki fungsi kombinasi, saya lebih suka jawaban yang menggabungkan dua penjelasan sebelumnya dengan contoh dalam jawaban ini.
Chris.Q
59

Versi singkat dan manis:

  • ($) memanggil fungsi yang merupakan argumen sebelah kiri pada nilai yang merupakan argumen sebelah kanannya.
  • (.) menyusun fungsi yang merupakan argumen sebelah kiri pada fungsi yang merupakan argumen kanannya.
ellisbben
sumber
29

Salah satu aplikasi yang berguna dan perlu waktu bagi saya untuk mencari tahu dari deskripsi yang sangat singkat di belajar Anda haskell : Sejak:

f $ x = f x

dan tanda kurung sisi kanan ekspresi yang mengandung operator infiks mengubahnya menjadi fungsi awalan, orang dapat menulis ($ 3) (4+)analog dengan (++", world") "hello".

Mengapa ada orang yang melakukan hal ini? Untuk daftar fungsi, misalnya. Kedua:

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

dan:

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

lebih pendek dari map (\x -> x ++ ", world") ...atau map (\f -> f 3) .... Jelas, varian yang terakhir akan lebih mudah dibaca oleh kebanyakan orang.

Christoph
sumber
14
btw, saya akan menyarankan agar tidak menggunakan $3ruang. Jika Template Haskell diaktifkan, ini akan diuraikan sebagai sambungan, sedangkan $ 3selalu berarti apa yang Anda katakan. Secara umum tampaknya ada kecenderungan di Haskell untuk "mencuri" sintaks dengan bersikeras bahwa operator tertentu memiliki ruang di sekitar mereka untuk diperlakukan seperti itu.
GS - Minta maaf kepada Monica
1
Butuh waktu beberapa saat untuk mencari tahu bagaimana tanda kurung bekerja: en.wikibooks.org/wiki/Haskell/…
Casebash
18

Haskell: perbedaan antara .(titik) dan $(tanda dolar)

Apa perbedaan antara titik (.)dan tanda dolar ($)? Seperti yang saya pahami, keduanya adalah gula sintaksis karena tidak perlu menggunakan tanda kurung.

Mereka bukan gula sintaksis karena tidak perlu menggunakan tanda kurung - mereka adalah fungsi, - diinfiksasi, sehingga kita dapat menyebutnya operator.

Tulis,, (.)dan kapan menggunakannya.

(.)adalah fungsi menulis. Begitu

result = (f . g) x

sama dengan membangun fungsi yang meneruskan hasil dari argumen yang diteruskan gke f.

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

Gunakan (.)saat Anda tidak memiliki argumen yang tersedia untuk diteruskan ke fungsi yang ingin Anda buat.

Asosiatif yang tepat berlaku ($),, dan kapan menggunakannya

($)adalah fungsi berlaku asosiatif-kanan dengan diutamakan mengikat rendah. Jadi itu hanya menghitung hal-hal di sebelah kanannya terlebih dahulu. Jadi,

result = f $ g x

sama dengan ini, secara prosedural (yang penting karena Haskell dievaluasi dengan malas, itu akan mulai dievaluasi fterlebih dahulu):

h = f
g_x = g x
result = h g_x

atau lebih ringkas:

result = f (g x)

Gunakan ($)ketika Anda memiliki semua variabel untuk dievaluasi sebelum Anda menerapkan fungsi sebelumnya pada hasilnya.

Kita dapat melihat ini dengan membaca sumber untuk setiap fungsi.

Baca Sumbernya

Inilah sumber untuk (.):

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

Dan inilah sumber untuk ($):

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

Kesimpulan

Gunakan komposisi ketika Anda tidak perlu segera mengevaluasi fungsi. Mungkin Anda ingin meneruskan fungsi yang dihasilkan dari komposisi ke fungsi lain.

Gunakan aplikasi saat Anda memasok semua argumen untuk evaluasi penuh.

Jadi untuk contoh kita, secara semantik lebih disukai untuk melakukannya

f $ g x

ketika kita memiliki x(atau lebih tepatnya, gargumen), dan lakukan:

f . g

ketika kita tidak melakukannya.

Aaron Hall
sumber
12

... atau Anda dapat menghindari .dan $konstruksi dengan menggunakan pipelining :

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

Itu setelah Anda menambahkan fungsi helper:

(|>) x y = y x
pengguna1721780
sumber
2
Ya, |> adalah operator pipa F #.
user1721780
6
Satu hal yang perlu diperhatikan di sini, adalah bahwa $operator Haskell sebenarnya bekerja lebih seperti F # <|daripada itu |>, biasanya dalam haskell Anda akan menulis fungsi di atas seperti ini: third xs = head $ tail $ tail $ xsatau mungkin bahkan seperti third = head . tail . tail, yang dalam sintaks gaya F # akan menjadi seperti ini:let third = List.head << List.tail << List.tail
Kopi Listrik
1
Mengapa menambahkan fungsi pembantu untuk membuat Haskell terlihat seperti F #? -1
vikingsteve
9
Membalik $sudah tersedia, dan itu disebut & hackage.haskell.org/package/base-4.8.0.0/docs/...
pat
11

Cara yang bagus untuk belajar lebih banyak tentang apa saja (fungsi apa saja) adalah dengan mengingat bahwa semuanya adalah fungsi! Mantra umum itu membantu, tetapi dalam kasus-kasus tertentu seperti operator, ada baiknya mengingat trik kecil ini:

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

dan

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

Ingatlah untuk menggunakan :tsecara bebas, dan bungkus operator Anda ()!

lol
sumber
11

Aturan saya sederhana (saya juga pemula):

  • jangan gunakan . jika Anda ingin melewatkan parameter (panggil fungsi), dan
  • jangan gunakan $jika belum ada parameter (susun fungsi)

Itu adalah

show $ head [1, 2]

tapi tidak pernah:

show . head [1, 2]
halacsy
sumber
3
Heuristik bagus, tetapi bisa menggunakan lebih banyak contoh
Zoey Hewll
0

Saya pikir contoh singkat dari mana Anda akan menggunakan .dan tidak $akan membantu memperjelas hal-hal.

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

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

Perhatikan bahwa times6ini adalah fungsi yang dibuat dari komposisi fungsi.

Brennan Cheung
sumber
0

Semua jawaban lain cukup bagus. Tetapi ada detail kegunaan yang penting tentang bagaimana ghc memperlakukan $, bahwa pemeriksa tipe ghc memungkinkan untuk instatiarion dengan peringkat yang lebih tinggi / jenis terkuantifikasi. Jika Anda melihat jenis $ idmisalnya Anda akan menemukan itu akan mengambil fungsi yang argumennya sendiri merupakan fungsi polimorfik. Hal-hal kecil seperti itu tidak diberi fleksibilitas yang sama dengan operator gangguan yang setara. (Ini benar-benar membuat saya bertanya-tanya apakah $! Layak mendapatkan perawatan yang sama atau tidak)

Carter Tazio Schonwald
sumber