Hanya untuk memperjelas penamaan, keduanya berfungsi. Satu adalah fungsi bernama dan yang lainnya adalah fungsi anonim. Tetapi Anda benar, mereka bekerja agak berbeda dan saya akan menggambarkan mengapa mereka bekerja seperti itu.
Mari kita mulai dengan yang kedua fn
,. fn
adalah penutupan, mirip dengan lambda
di Ruby. Kita bisa membuatnya sebagai berikut:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Suatu fungsi dapat memiliki beberapa klausa juga:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Sekarang, mari kita coba sesuatu yang berbeda. Mari kita coba mendefinisikan beberapa klausa yang berbeda yang mengharapkan sejumlah argumen:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
Oh tidak! Kami mendapat kesalahan! Kami tidak dapat mencampur klausa yang mengharapkan jumlah argumen yang berbeda. Suatu fungsi selalu memiliki arity tetap.
Sekarang, mari kita bicara tentang fungsi-fungsi yang disebutkan:
def hello(x, y) do
x + y
end
Seperti yang diharapkan, mereka memiliki nama dan mereka juga dapat menerima beberapa argumen. Namun, mereka bukan penutupan:
x = 1
def hello(y) do
x + y
end
Kode ini akan gagal dikompilasi karena setiap kali Anda melihat def
, Anda mendapatkan lingkup variabel kosong. Itu adalah perbedaan penting di antara mereka. Saya terutama menyukai kenyataan bahwa setiap fungsi yang dinamai dimulai dengan yang bersih dan Anda tidak mendapatkan variabel dari cakupan yang berbeda semuanya tercampur menjadi satu. Anda memiliki batas yang jelas.
Kita dapat mengambil fungsi hello di atas sebagai fungsi anonim. Anda menyebutkannya sendiri:
other_function(&hello(&1))
Dan kemudian Anda bertanya, mengapa saya tidak bisa begitu saja lulus hello
seperti dalam bahasa lain? Itu karena fungsi dalam Elixir diidentifikasi oleh nama dan arity. Jadi fungsi yang mengharapkan dua argumen adalah fungsi yang berbeda dari yang argumen tiga, bahkan jika mereka memiliki nama yang sama. Jadi jika kita lewat begitu saja hello
, kita tidak akan tahu apa yang hello
sebenarnya Anda maksudkan. Yang satu dengan dua, tiga atau empat argumen? Ini adalah alasan yang persis sama mengapa kita tidak dapat membuat fungsi anonim dengan klausa dengan arities yang berbeda.
Sejak Elixir v0.10.1, kami memiliki sintaks untuk menangkap fungsi bernama:
&hello/1
Itu akan menangkap fungsi bernama lokal halo dengan arity 1. Sepanjang bahasa dan dokumentasinya, sangat umum untuk mengidentifikasi fungsi dalam hello/1
sintaks ini .
Ini juga mengapa Elixir menggunakan titik untuk memanggil fungsi anonim. Karena Anda tidak bisa begitu saja lulus hello
sebagai fungsi, alih-alih Anda perlu menangkapnya secara eksplisit, ada perbedaan alami antara fungsi yang dinamai dan anonim dan sintaksis yang berbeda untuk memanggil masing-masing membuat semuanya sedikit lebih eksplisit (Lispers akan terbiasa dengan ini karena diskusi Lisp 1 vs. Lisp 2).
Secara keseluruhan, itulah alasan mengapa kita memiliki dua fungsi dan mengapa mereka berperilaku berbeda.
f()
(tanpa titik).is_function/2
penjaga.is_function(f, 2)
memeriksanya memiliki arity 2. :)SomeFun()
dansome_fun()
. Dalam Elixir, jika kita menghapus titik, mereka akan samasome_fun()
dansome_fun()
karena variabel menggunakan pengidentifikasi yang sama dengan nama fungsi. Karena itu titik.Saya tidak tahu bagaimana ini akan berguna bagi orang lain, tetapi cara saya akhirnya membungkus kepala saya di sekitar konsep adalah untuk menyadari bahwa fungsi elixir bukan Fungsi.
Segala sesuatu dalam elixir adalah ekspresi. Begitu
bukan fungsi tetapi ekspresi yang dikembalikan dengan mengeksekusi kode di
my_function
. Sebenarnya hanya ada satu cara untuk mendapatkan "Function" yang bisa Anda berikan sebagai argumen dan itu adalah dengan menggunakan notasi fungsi anonim.Sangat menggoda untuk merujuk ke fn atau & notasi sebagai penunjuk fungsi, tetapi sebenarnya jauh lebih. Ini adalah penutupan dari lingkungan sekitar.
Jika Anda bertanya pada diri sendiri:
Apakah saya memerlukan lingkungan eksekusi atau nilai data di tempat ini?
Dan jika Anda membutuhkan eksekusi menggunakan fn, maka sebagian besar kesulitan menjadi lebih jelas.
sumber
MyModule.my_function(foo)
adalah ekspresi tetapiMyModule.my_function
"bisa" adalah ekspresi yang mengembalikan fungsi "objek". Tetapi karena Anda perlu memberi tahu arity, Anda akan membutuhkan sesuatu seperti ituMyModule.my_function/1
. Dan saya kira mereka memutuskan lebih baik menggunakan&MyModule.my_function(&1)
sintaks yang memungkinkan untuk mengekspresikan arity (dan melayani tujuan lain juga). Masih belum jelas mengapa ada()
operator untuk fungsi yang disebutkan dan.()
operator untuk fungsi yang tidak disebutkan namanya&MyModule.my_function/1
itulah bagaimana Anda bisa menyebarkannya sebagai fungsi.Saya tidak pernah mengerti mengapa penjelasan ini sangat rumit.
Ini benar-benar hanya perbedaan yang sangat kecil dikombinasikan dengan realitas "eksekusi fungsi tanpa parens" gaya Ruby.
Membandingkan:
Untuk:
Meskipun keduanya hanya pengidentifikasi ...
fun1
adalah pengidentifikasi yang menjelaskan fungsi bernama yang didefinisikan dengandef
.fun2
adalah pengenal yang menggambarkan variabel (yang kebetulan mengandung referensi ke fungsi).Pertimbangkan apa artinya itu ketika Anda melihat
fun1
ataufun2
dalam ekspresi lain? Saat mengevaluasi ekspresi itu, apakah Anda memanggil fungsi yang direferensikan atau Anda hanya referensi nilai dari memori?Tidak ada cara yang baik untuk mengetahui pada waktu kompilasi. Ruby memiliki kemewahan untuk mengintrospeksi ruang nama variabel untuk mengetahui apakah suatu variabel mengikat telah membayangi suatu fungsi pada suatu saat. Elixir, sedang dikompilasi, tidak bisa melakukan ini. Itulah yang dilakukan notasi titik, ia memberi tahu Elixir bahwa itu harus berisi referensi fungsi dan harus dipanggil.
Dan ini sangat sulit. Bayangkan bahwa tidak ada notasi titik. Pertimbangkan kode ini:
Mengingat kode di atas, saya pikir cukup jelas mengapa Anda harus memberi petunjuk kepada Elixir. Bayangkan jika setiap variabel de-referensi harus memeriksa fungsi? Atau, bayangkan heroik apa yang diperlukan untuk selalu menyimpulkan bahwa dereferensi variabel menggunakan fungsi?
sumber
Saya mungkin salah karena tidak ada yang menyebutkannya, tetapi saya juga mendapat kesan bahwa alasan untuk ini juga merupakan warisan rubi untuk dapat memanggil fungsi tanpa tanda kurung.
Arity jelas terlibat tetapi mari kita mengesampingkannya untuk sementara waktu dan menggunakan fungsi tanpa argumen. Dalam bahasa seperti javascript di mana tanda kurung adalah wajib, mudah untuk membuat perbedaan antara melewati fungsi sebagai argumen dan memanggil fungsi. Anda menyebutnya hanya saat Anda menggunakan tanda kurung.
Seperti yang Anda lihat, memberi nama atau tidak tidak membuat perbedaan besar. Tapi elixir dan ruby memungkinkan Anda untuk memanggil fungsi tanpa tanda kurung. Ini adalah pilihan desain yang saya pribadi suka tetapi memiliki efek samping ini Anda tidak dapat menggunakan nama saja tanpa tanda kurung karena itu bisa berarti Anda ingin memanggil fungsi. Ini untuk apa
&
. Jika Anda meninggalkan appity arity sesaat, menambahkan nama fungsi Anda dengan&
berarti bahwa Anda secara eksplisit ingin menggunakan fungsi ini sebagai argumen, bukan apa fungsi ini mengembalikan.Sekarang fungsi anonim sedikit berbeda karena sebagian besar digunakan sebagai argumen. Sekali lagi ini adalah pilihan desain tetapi rasional di balik itu adalah bahwa itu terutama digunakan oleh iterators jenis fungsi yang mengambil fungsi sebagai argumen. Jadi jelas Anda tidak perlu menggunakan
&
karena mereka sudah dianggap sebagai argumen secara default. Itu tujuan mereka.Sekarang masalah terakhir adalah bahwa kadang-kadang Anda harus memanggil mereka dalam kode Anda, karena mereka tidak selalu digunakan dengan jenis fungsi iterator, atau Anda mungkin membuat kode iterator sendiri. Untuk cerita kecil, karena ruby berorientasi objek, cara utama untuk melakukannya adalah menggunakan
call
metode pada objek. Dengan begitu, Anda bisa menjaga perilaku kurung non-wajib konsisten.Sekarang seseorang datang dengan jalan pintas yang pada dasarnya terlihat seperti metode tanpa nama.
Sekali lagi, ini adalah pilihan desain. Sekarang elixir tidak berorientasi objek dan karenanya panggilan tidak menggunakan bentuk pertama pasti. Saya tidak dapat berbicara untuk José tetapi sepertinya bentuk kedua digunakan di elixir karena masih terlihat seperti pemanggilan fungsi dengan karakter tambahan. Cukup dekat dengan panggilan fungsi.
Saya tidak memikirkan semua pro dan kontra, tetapi sepertinya dalam kedua bahasa Anda bisa lolos hanya dengan tanda kurung selama Anda membuat tanda kurung wajib untuk fungsi anonim. Sepertinya itu adalah:
Tanda kurung VS Notasi sedikit berbeda
Dalam kedua kasus Anda membuat pengecualian karena Anda membuat keduanya berperilaku berbeda. Karena ada perbedaan, Anda dapat membuatnya menjadi jelas dan menggunakan notasi yang berbeda. Kurung wajib akan terlihat alami dalam banyak kasus tetapi sangat membingungkan ketika hal-hal tidak berjalan sesuai rencana.
Ini dia Sekarang ini mungkin bukan penjelasan terbaik di dunia karena saya menyederhanakan sebagian besar detail. Juga sebagian besar adalah pilihan desain dan saya mencoba memberikan alasan bagi mereka tanpa menilai mereka. Saya suka elixir, saya suka ruby, saya suka panggilan fungsi tanpa tanda kurung, tetapi seperti Anda, saya menemukan konsekuensinya cukup sesekali sesekali.
Dan di elixir, hanya titik tambahan ini, sedangkan di ruby Anda memiliki blok di atas ini. Blok luar biasa dan saya terkejut betapa banyak yang dapat Anda lakukan hanya dengan blok, tetapi mereka hanya bekerja ketika Anda hanya membutuhkan satu fungsi anonim yang merupakan argumen terakhir. Maka karena Anda harus bisa berurusan dengan skenario lain, inilah seluruh metode / lambda / proc / block confusion.
Lagi pula ... ini di luar jangkauan.
sumber
Ada posting blog yang bagus tentang perilaku ini: tautan
Dua jenis fungsi
Dot menelepon
fn
sumber
Elixir memiliki kawat gigi opsional untuk berbagai fungsi, termasuk fungsi dengan 0 arity. Mari kita lihat contoh mengapa itu membuat sintaks pemanggilan yang terpisah menjadi penting:
Tanpa membuat perbedaan antara 2 jenis fungsi, kita tidak bisa mengatakan apa
Insanity.dive
artinya: mendapatkan fungsi itu sendiri, memanggilnya, atau juga memanggil fungsi anonim yang dihasilkan.sumber
fn ->
sintaks untuk menggunakan fungsi anonim. Melakukan var. () Hanya memberitahu elixir bahwa saya ingin Anda mengambil var itu dengan func di dalamnya dan menjalankannya alih-alih merujuk ke var sebagai sesuatu yang hanya memegang fungsi itu.Elixir memiliki pola umum ini di mana alih-alih memiliki logika di dalam fungsi untuk melihat bagaimana sesuatu harus dieksekusi, kita memadukan pola fungsi yang berbeda berdasarkan pada jenis input yang kita miliki. Saya berasumsi inilah sebabnya kami mengacu pada hal-hal oleh arity dalam
function_name/1
arti.Agak aneh membiasakan diri melakukan definisi fungsi singkatan (func (& 1), dll), tetapi berguna ketika Anda mencoba untuk mem-pipe atau membuat kode Anda singkat.
sumber
Anda dapat melakukan
otherfunction(&myfunction/2)
Karena elixir dapat menjalankan fungsi tanpa tanda kurung (seperti
myfunction
), menggunakannyaotherfunction(myfunction))
akan mencoba mengeksekusimyfunction/0
.Jadi, Anda perlu menggunakan operator tangkap dan menentukan fungsi, termasuk arity, karena Anda dapat memiliki fungsi yang berbeda dengan nama yang sama. Jadi
&myfunction/2
,.sumber