Elixir - Panduan Cepat
Elixir adalah bahasa fungsional yang dinamis yang dirancang untuk membangun aplikasi yang dapat diskalakan dan dipelihara. Ini memanfaatkan VM Erlang, yang dikenal untuk menjalankan sistem latensi rendah, terdistribusi, dan toleransi kesalahan, sementara juga berhasil digunakan dalam pengembangan web dan domain perangkat lunak yang disematkan.
Elixir adalah bahasa fungsional dan dinamis yang dibangun di atas Erlang dan VM Erlang. Erlang adalah bahasa yang aslinya ditulis pada tahun 1986 oleh Ericsson untuk membantu memecahkan masalah telepon seperti distribusi, toleransi kesalahan, dan konkurensi. Elixir, ditulis oleh José Valim, mengembangkan Erlang dan menyediakan sintaks yang lebih bersahabat ke dalam VM Erlang. Ini dilakukan sambil menjaga kinerja pada level yang sama dengan Erlang.
Fitur Elixir
Sekarang mari kita bahas beberapa fitur penting dari Elixir -
Scalability - Semua kode Elixir berjalan di dalam proses ringan yang diisolasi dan bertukar informasi melalui pesan.
Fault Tolerance- Elixir menyediakan pengawas yang menjelaskan cara me-restart bagian sistem Anda ketika ada yang salah, kembali ke keadaan awal yang diketahui yang dijamin berfungsi. Ini memastikan aplikasi / platform Anda tidak pernah down.
Functional Programming - Pemrograman fungsional mempromosikan gaya pengkodean yang membantu pengembang menulis kode yang singkat, cepat, dan dapat dipelihara.
Build tools- Elixir dikirimkan dengan satu set alat pengembangan. Mix adalah salah satu alat yang memudahkan untuk membuat proyek, mengelola tugas, menjalankan pengujian, dll. Ia juga memiliki manajer paket sendiri - Hex.
Erlang Compatibility - Elixir berjalan di VM Erlang yang memberi developer akses penuh ke ekosistem Erlang.
Untuk menjalankan Elixir, Anda perlu mengaturnya secara lokal di sistem Anda.
Untuk menginstal Elixir, Anda memerlukan Erlang terlebih dahulu. Pada beberapa platform, paket Elixir dilengkapi dengan Erlang di dalamnya.
Menginstal Elixir
Mari kita sekarang memahami instalasi Elixir di Sistem Operasi yang berbeda.
Pengaturan Windows
Untuk menginstal Elixir di windows, unduh penginstal dari https://repo.hex.pm/elixirwebsetup.exe dan cukup klik Nextuntuk melanjutkan melalui semua langkah. Anda akan memilikinya di sistem lokal Anda.
Jika Anda mengalami masalah saat menginstalnya, Anda dapat memeriksa halaman ini untuk info lebih lanjut.
Pengaturan Mac
Jika Anda menginstal Homebrew, pastikan itu adalah versi terbaru. Untuk memperbarui, gunakan perintah berikut -
brew update
Sekarang, instal Elixir menggunakan perintah yang diberikan di bawah ini -
brew install elixir
Pengaturan Ubuntu / Debian
Langkah-langkah untuk menginstal Elixir di pengaturan Ubuntu / Debian adalah sebagai berikut -
Tambahkan repo Solusi Erlang -
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo
dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
Instal platform Erlang / OTP dan semua aplikasinya -
sudo apt-get install esl-erlang
Instal Elixir -
sudo apt-get install elixir
Distro Linux lainnya
Jika Anda memiliki distribusi Linux lainnya, kunjungi halaman ini untuk mengatur elixir di sistem lokal Anda.
Menguji Setup
Untuk menguji pengaturan Elixir pada sistem Anda, buka terminal Anda dan masukkan iex di dalamnya. Ini akan membuka shell elixir interaktif seperti berikut -
Erlang/OTP 19 [erts-8.0] [source-6dc93c1] [64-bit]
[smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
Elixir sekarang berhasil disiapkan di sistem Anda.
Kami akan mulai dengan program 'Hello World' yang biasa.
Untuk memulai shell interaktif Elixir, masukkan perintah berikut.
iex
Setelah shell dimulai, gunakan file IO.putsberfungsi untuk "meletakkan" string pada output konsol. Masukkan yang berikut ini di shell Elixir Anda -
IO.puts "Hello world"
Dalam tutorial ini, kita akan menggunakan mode script Elixir dimana kita akan menyimpan kode Elixir dalam file dengan ekstensi. .ex. Sekarang mari kita simpan kode di atas ditest.exmengajukan. Pada langkah berikutnya, kami akan menjalankannya menggunakanelixirc-
IO.puts "Hello world"
Sekarang mari kita coba menjalankan program di atas sebagai berikut -
$elixirc test.ex
Program di atas menghasilkan hasil sebagai berikut -
Hello World
Di sini kami memanggil fungsi IO.putsuntuk menghasilkan string ke konsol kami sebagai keluaran. Fungsi ini juga bisa disebut seperti yang kita lakukan di C, C ++, Java, dll., Memberikan argumen dalam tanda kurung setelah nama fungsi -
IO.puts("Hello world")
Komentar
Komentar baris tunggal dimulai dengan simbol '#'. Tidak ada komentar multi-baris, tetapi Anda dapat menumpuk banyak komentar. Misalnya -
#This is a comment in Elixir
Ujung Garis
Tidak ada akhiran baris yang diperlukan seperti ';' di Elixir. Namun, kita bisa memiliki banyak pernyataan di baris yang sama, menggunakan ';'. Sebagai contoh,
IO.puts("Hello"); IO.puts("World!")
Program di atas menghasilkan hasil sebagai berikut -
Hello
World!
Pengenal
Pengenal seperti variabel, nama fungsi digunakan untuk mengidentifikasi variabel, fungsi, dll. Dalam Elixir, Anda dapat memberi nama pengenal Anda dimulai dengan huruf kecil dengan angka, garis bawah dan huruf besar sesudahnya. Konvensi penamaan ini umumnya dikenal sebagai snake_case. Misalnya, berikut adalah beberapa pengenal yang valid di Elixir -
var1 variable_2 one_M0r3_variable
Harap dicatat bahwa variabel juga dapat diberi nama dengan garis bawah di depan. Nilai yang tidak dimaksudkan untuk digunakan harus ditetapkan ke _ atau ke variabel yang dimulai dengan garis bawah -
_some_random_value = 42
Elixir juga bergantung pada garis bawah untuk membuat fungsi menjadi privat untuk modul. Jika Anda memberi nama fungsi dengan garis bawah di awal modul, dan mengimpor modul itu, fungsi ini tidak akan diimpor.
Masih banyak lagi seluk-beluk terkait penamaan fungsi di Elixir yang akan kita bahas di bab-bab selanjutnya.
Kata yang Dicadangkan
Kata-kata berikut dicadangkan dan tidak dapat digunakan sebagai variabel, modul, atau nama fungsi.
after and catch do inbits inlist nil else end
not or false fn in rescue true when xor
__MODULE__ __FILE__ __DIR__ __ENV__ __CALLER__
Untuk menggunakan bahasa apa pun, Anda perlu memahami tipe data dasar yang didukung bahasa tersebut. Pada bab ini, kita akan membahas 7 tipe data dasar yang didukung oleh bahasa elixir: integer, float, Boolean, atom, string, list dan tuple.
Jenis Numerik
Elixir, seperti bahasa pemrograman lainnya, mendukung integer dan float. Jika Anda membuka shell elixir Anda dan memasukkan integer atau float apa pun sebagai input, itu akan mengembalikan nilainya. Sebagai contoh,
42
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
42
Anda juga dapat menentukan angka dalam basis oktal, heksadesimal, dan biner.
Oktal
Untuk menentukan angka dalam basis oktal, awali dengan '0o'. Misalnya, 0o52 dalam oktal sama dengan 42 dalam desimal.
Heksadesimal
Untuk menentukan angka dalam basis desimal, awali dengan '0x'. Misalnya, 0xF1 dalam hex setara dengan 241 dalam desimal.
Biner
Untuk menentukan angka dalam basis biner, awali dengan '0b'. Misalnya, 0b1101 dalam biner sama dengan 13 dalam desimal.
Elixir mendukung presisi ganda 64bit untuk angka floating point. Dan mereka juga dapat didefinisikan menggunakan gaya eksponensiasi. Misalnya, 10145230000 dapat ditulis sebagai 1.014523e10
Atom
Atom adalah konstanta yang namanya adalah nilainya. Mereka dapat dibuat menggunakan simbol color (:). Sebagai contoh,
:hello
Boolean
Elixir mendukung true dan falsesebagai Boolean. Kedua nilai ini sebenarnya melekat pada atom: benar dan: salah.
String
String dalam Elixir disisipkan di antara tanda kutip ganda, dan disandikan dalam UTF-8. Mereka dapat menjangkau banyak baris dan berisi interpolasi. Untuk mendefinisikan string cukup masukkan dalam tanda kutip ganda -
"Hello world"
Untuk mendefinisikan string multiline, kami menggunakan sintaks yang mirip dengan python dengan tanda kutip ganda rangkap tiga -
"""
Hello
World!
"""
Kita akan belajar tentang string, biner, dan daftar karakter (mirip dengan string) secara mendalam di bab string.
Binari
Binari adalah urutan byte yang diapit oleh << >> dipisahkan dengan koma. Sebagai contoh,
<< 65, 68, 75>>
Binari sebagian besar digunakan untuk menangani data terkait bit dan byte, jika Anda punya. Secara default, mereka dapat menyimpan 0 hingga 255 di setiap nilai. Batas ukuran ini dapat ditingkatkan dengan menggunakan fungsi ukuran yang menyatakan berapa banyak bit yang diperlukan untuk menyimpan nilai tersebut. Sebagai contoh,
<<65, 255, 289::size(15)>>
Daftar
Elixir menggunakan tanda kurung siku untuk menentukan daftar nilai. Nilai bisa dalam jenis apa pun. Sebagai contoh,
[1, "Hello", :an_atom, true]
Daftar datang dengan fungsi bawaan untuk kepala dan ekor dari daftar bernama hd dan tl yang mengembalikan kepala dan ekor dari daftar masing-masing. Terkadang saat Anda membuat daftar, daftar karakter akan ditampilkan. Ini karena ketika elixir melihat daftar karakter ASCII yang dapat dicetak, elixir mencetaknya sebagai daftar karakter. Harap dicatat bahwa string dan daftar karakter tidak sama. Kami akan membahas daftar lebih lanjut di bab-bab selanjutnya.
Tuple
Elixir menggunakan tanda kurung kurawal untuk mendefinisikan tupel. Seperti daftar, tupel dapat memiliki nilai apa pun.
{ 1, "Hello", :an_atom, true
Sebuah pertanyaan muncul di sini, - mengapa menyediakan keduanya lists dan tuplesketika keduanya bekerja dengan cara yang sama? Baik mereka memiliki implementasi yang berbeda.
Daftar sebenarnya disimpan sebagai daftar tertaut, jadi penyisipan, penghapusan sangat cepat dalam daftar.
Tupel di sisi lain, disimpan dalam blok memori yang berdekatan, yang membuat pengaksesannya lebih cepat tetapi menambah biaya tambahan pada penyisipan dan penghapusan.
Variabel memberi kita penyimpanan bernama yang dapat dimanipulasi oleh program kita. Setiap variabel di Elixir memiliki tipe tertentu, yang menentukan ukuran dan tata letak memori variabel; kisaran nilai yang dapat disimpan dalam memori itu; dan set operasi yang dapat diterapkan ke variabel.
Jenis Variabel
Elixir mendukung jenis variabel dasar berikut.
Bilangan bulat
Ini digunakan untuk Integer. Mereka berukuran 32bit pada arsitektur 32bit dan 64 bit pada arsitektur 64-bit. Integer selalu masuk dalam elixir. Jika sebuah bilangan bulat mulai membesar dalam ukuran di atas batasnya, ramuan mengubahnya menjadi Bilangan Bulat Besar yang memakan memori dalam rentang 3 hingga n kata mana saja yang dapat muat dalam memori.
Mengapung
Float memiliki presisi 64-bit dalam elixir. Mereka juga seperti bilangan bulat dalam hal memori. Saat mendefinisikan pelampung, notasi eksponensial dapat digunakan.
Boolean
Mereka dapat mengambil 2 nilai yang benar atau salah.
String
String dienkode utf-8 dalam elixir. Mereka memiliki modul string yang menyediakan banyak fungsi bagi pemrogram untuk memanipulasi string.
Fungsi Anonim / Lambdas
Ini adalah fungsi yang dapat didefinisikan dan ditetapkan ke variabel, yang kemudian dapat digunakan untuk memanggil fungsi ini.
Koleksi
Ada banyak sekali jenis koleksi yang tersedia di Elixir. Beberapa di antaranya adalah List, Tuple, Maps, Binaries, dll. Ini akan dibahas di bab-bab selanjutnya.
Deklarasi Variabel
Deklarasi variabel memberi tahu penerjemah di mana dan berapa banyak untuk membuat penyimpanan untuk variabel. Elixir tidak mengizinkan kita untuk hanya mendeklarasikan variabel. Variabel harus dideklarasikan dan diberi nilai pada saat yang bersamaan. Misalnya, untuk membuat variabel bernama kehidupan dan memberinya nilai 42, kami melakukan hal berikut -
life = 42
Ini akan mengikat variabel hidup ke nilai 42. Jika kita ingin menetapkan kembali variabel ini nilai baru, kita dapat melakukannya dengan menggunakan sintaks yang sama seperti di atas, yaitu,
life = "Hello world"
Penamaan Variabel
Variabel penamaan mengikuti a snake_casekonvensi di Elixir, yaitu, semua variabel harus dimulai dengan huruf kecil, diikuti oleh 0 atau lebih huruf (baik huruf besar maupun kecil), diikuti di bagian akhir dengan '?' opsional ATAU '!'.
Nama variabel juga dapat dimulai dengan garis bawah di depan tetapi itu harus digunakan hanya ketika mengabaikan variabel, yaitu variabel itu tidak akan digunakan lagi tetapi perlu ditempatkan ke sesuatu.
Mencetak Variabel
Di shell interaktif, variabel akan dicetak jika Anda hanya memasukkan nama variabel. Misalnya, jika Anda membuat variabel -
life = 42
Dan masukkan 'kehidupan' di cangkang Anda, Anda akan mendapatkan hasilnya sebagai -
42
Tetapi jika Anda ingin mengeluarkan variabel ke konsol (Saat menjalankan skrip eksternal dari file), Anda perlu memberikan variabel sebagai masukan untuk IO.puts fungsi -
life = 42
IO.puts life
atau
life = 42
IO.puts(life)
Ini akan memberi Anda output berikut -
42
Operator adalah simbol yang memberi tahu kompiler untuk melakukan manipulasi matematika atau logika tertentu. Ada BANYAK operator yang disediakan oleh elixir. Mereka dibagi dalam kategori berikut -
- Operator aritmatika
- Operator perbandingan
- Operator Boolean
- Operator lain-lain
Operator Aritmatika
Tabel berikut menunjukkan semua operator aritmatika yang didukung oleh bahasa Elixir. Asumsikan variabelA memegang 10 dan variabel B memegang 20, lalu -
Tunjukkan Contoh
Operator | Deskripsi | Contoh |
---|---|---|
+ | Menambahkan 2 angka. | A + B akan menghasilkan 30 |
- | Mengurangi angka kedua dari angka pertama. | AB akan memberikan -10 |
* | Mengalikan dua angka. | A * B akan memberi 200 |
/ | Membagi angka pertama dari kedua. Ini melemparkan angka dalam float dan memberikan hasil float | A / B akan menghasilkan 0,5. |
div | Fungsi ini digunakan untuk mendapatkan hasil bagi pembagian. | div (10,20) akan memberikan 0 |
rem | Fungsi ini digunakan untuk mendapatkan sisa pembagian. | rem (A, B) akan memberikan 10 |
Operator Perbandingan
Operator perbandingan di Elixir sebagian besar sama dengan yang disediakan di sebagian besar bahasa lain. Tabel berikut meringkas operator perbandingan di Elixir. Asumsikan variabelA memegang 10 dan variabel B memegang 20, lalu -
Tunjukkan Contoh
Operator | Deskripsi | Contoh |
---|---|---|
== | Memeriksa apakah nilai di kiri sama dengan nilai di kanan (Ketik nilai cetakan jika bukan jenis yang sama). | A == B akan memberikan nilai salah |
! = | Memeriksa apakah nilai di kiri tidak sama dengan nilai di kanan. | A! = B akan memberikan true |
=== | Memeriksa apakah jenis nilai di kiri sama dengan jenis nilai di kanan, jika ya, periksa nilai yang sama. | A === B akan memberikan false |
! == | Sama seperti di atas tetapi memeriksa ketidaksetaraan, bukan kesetaraan. | A! == B akan memberikan true |
> | Memeriksa apakah nilai operan kiri lebih besar dari nilai operan kanan; jika ya, maka kondisinya menjadi benar. | A> B akan memberikan false |
< | Memeriksa apakah nilai operan kiri kurang dari nilai operan kanan; jika ya, maka kondisinya menjadi benar. | A <B akan memberikan true |
> = | Memeriksa apakah nilai operan kiri lebih besar dari atau sama dengan nilai operan kanan; jika ya, maka kondisinya menjadi benar. | A> = B akan memberikan false |
<= | Memeriksa apakah nilai operan kiri kurang dari atau sama dengan nilai operan kanan; jika ya, maka kondisinya menjadi benar. | A <= B akan memberikan true |
Operator logika
Elixir menyediakan 6 operator logika: dan, atau, tidak, &&, || dan!. Tiga yang pertama,and or notadalah operator Boolean yang ketat, artinya mereka mengharapkan argumen pertama mereka adalah Boolean. Argumen non Boolean akan memunculkan kesalahan. Sedangkan tiga berikutnya,&&, || and !tidak ketat, jangan mengharuskan kami memiliki nilai pertama secara ketat sebagai boolean. Mereka bekerja dengan cara yang sama seperti rekan-rekan ketat mereka. Asumsikan variabelA berlaku dan variabel B memegang 20, lalu -
Tunjukkan Contoh
Operator | Deskripsi | Contoh |
---|---|---|
dan | Memeriksa apakah kedua nilai yang diberikan benar, jika ya maka mengembalikan nilai variabel kedua. (Logis dan). | A dan B akan memberi 20 |
atau | Memeriksa apakah salah satu nilai yang diberikan benar. Mengembalikan nilai mana pun yang benar. Lain mengembalikan false. (Logis atau). | A atau B akan memberikan true |
tidak | Operator unary yang membalik nilai input yang diberikan. | bukan A akan memberikan false |
&& | Tidak ketat and. Bekerja sama sepertiand tetapi tidak mengharapkan argumen pertama menjadi Boolean. | B && A akan memberi 20 |
|| | Tidak ketat or. Bekerja sama sepertior tetapi tidak mengharapkan argumen pertama menjadi Boolean. | B || A akan memberikan kebenaran |
! | Tidak ketat not. Bekerja sama sepertinot tetapi tidak mengharapkan argumen menjadi Boolean. | ! A akan memberikan false |
NOTE −dan , atau , && dan || || adalah operator sirkuit pendek. Artinya jika argumen pertamaandsalah, maka tidak akan diperiksa lebih lanjut untuk yang kedua. Dan jika argumen pertamaorbenar, maka itu tidak akan memeriksa yang kedua. Sebagai contoh,
false and raise("An error")
#This won't raise an error as raise function wont get executed because of short
#circuiting nature of and operator
Operator Bitwise
Operator bitwise bekerja pada bit dan melakukan operasi bit demi bit. Elixir menyediakan modul bitwise sebagai bagian dari paketBitwise, jadi untuk menggunakannya, Anda perlu menggunakan modul bitwise. Untuk menggunakannya, masukkan perintah berikut di shell Anda -
use Bitwise
Asumsikan A menjadi 5 dan B menjadi 6 untuk contoh berikut -
Tunjukkan Contoh
Operator | Deskripsi | Contoh |
---|---|---|
&&& | Bitwise dan operator menyalin sedikit ke hasil jika ada di kedua operan. | A &&& B akan memberikan 4 |
|| Necklace | Bitwise atau operator menyalin sedikit ke hasil jika ada di salah satu operan. | A ||i> B akan memberi 7 |
>>> | Operator bitwise right shift menggeser bit operan pertama ke kanan dengan nomor yang ditentukan di operan kedua. | A >>> B akan memberi 0 |
<<< | Operator bitwise left shift menggeser bit operan pertama ke kiri dengan nomor yang ditentukan di operan kedua. | A <<< B akan menghasilkan 320 |
^^^ | Operator Bitwise XOR menyalin sedikit ke hasil hanya jika berbeda pada kedua operan. | A ^^^ B akan memberikan 3 |
~~~ | Bitwise unary tidak membalikkan bit pada nomor yang diberikan. | ~~~ A akan memberikan -6 |
Operator Misc
Selain operator di atas, Elixir juga menyediakan berbagai operator sejenisnya Concatenation Operator, Match Operator, Pin Operator, Pipe Operator, String Match Operator, Code Point Operator, Capture Operator, Ternary Operator yang membuatnya menjadi bahasa yang cukup kuat.
Tunjukkan Contoh
Pencocokan pola adalah teknik yang diwarisi oleh Elixir dari Erlang. Ini adalah teknik yang sangat kuat yang memungkinkan kita mengekstrak substruktur yang lebih sederhana dari struktur data yang rumit seperti daftar, tupel, peta, dll.
Pertandingan memiliki 2 bagian utama, a left dan a rightsisi. Sisi kanan adalah struktur data apa pun. Sisi kiri mencoba mencocokkan struktur data di sisi kanan dan mengikat variabel di kiri ke substruktur masing-masing di kanan. Jika kecocokan tidak ditemukan, operator menimbulkan kesalahan.
Pencocokan paling sederhana adalah variabel mandiri di kiri dan struktur data apa pun di sebelah kanan. This variable will match anything. Sebagai contoh,
x = 12
x = "Hello"
IO.puts(x)
Anda dapat menempatkan variabel di dalam struktur sehingga Anda dapat menangkap substruktur. Sebagai contoh,
[var_1, _unused_var, var_2] = [{"First variable"}, 25, "Second variable" ]
IO.puts(var_1)
IO.puts(var_2)
Ini akan menyimpan nilai, {"First variable"}di var_1 dan"Second variable"di var_2 . Ada juga yang spesial_ variabel (atau variabel yang diawali dengan '_') yang berfungsi persis seperti variabel lain tetapi memberi tahu elixir, "Make sure something is here, but I don't care exactly what it is.". Dalam contoh sebelumnya, _unused_var adalah salah satu variabel tersebut.
Kita bisa mencocokkan pola yang lebih rumit menggunakan teknik ini. Untukexample jika Anda ingin membuka bungkus dan mendapatkan nomor dalam tupel yang ada di dalam daftar yang juga ada dalam daftar, Anda dapat menggunakan perintah berikut -
[_, [_, {a}]] = ["Random string", [:an_atom, {24}]]
IO.puts(a)
Program di atas menghasilkan hasil sebagai berikut -
24
Ini akan mengikat a menjadi 24. Nilai-nilai lain diabaikan karena kita menggunakan '_'.
Dalam pencocokan pola, jika kita menggunakan variabel pada right, nilainya digunakan. Jika Anda ingin menggunakan nilai variabel di sebelah kiri, Anda harus menggunakan operator pin.
Misalnya, jika Anda memiliki variabel "a" yang memiliki nilai 25 dan Anda ingin mencocokkannya dengan variabel "b" lain yang memiliki nilai 25, Anda harus memasukkan -
a = 25
b = 25
^a = b
Baris terakhir cocok dengan nilai saat ini a, alih-alih menetapkannya, ke nilai b. Jika kita memiliki himpunan yang tidak cocok di sisi kiri dan kanan, operator pertandingan memunculkan kesalahan. Misalnya, jika kami mencoba mencocokkan tupel dengan daftar atau daftar ukuran 2 dengan daftar ukuran 3, kesalahan akan ditampilkan.
Struktur pengambilan keputusan mengharuskan programmer menentukan satu atau lebih kondisi untuk dievaluasi atau diuji oleh program, bersama dengan pernyataan atau pernyataan yang akan dieksekusi jika kondisi ditentukan untuk menjadi true, dan secara opsional, pernyataan lain yang akan dijalankan jika kondisi ditentukan untuk dijalankan false.
Berikut ini adalah umum dari struktur pengambilan keputusan khas yang ditemukan di sebagian besar bahasa pemrograman -
Elixir menyediakan konstruksi bersyarat if / else seperti banyak bahasa pemrograman lainnya. Ini juga memilikicondpernyataan yang memanggil nilai benar pertama yang ditemukannya. Kasus adalah pernyataan aliran kontrol lain yang menggunakan pencocokan pola untuk mengontrol aliran program. Mari kita lihat lebih dalam.
Elixir memberikan jenis pernyataan pengambilan keputusan berikut. Klik tautan berikut untuk memeriksa detailnya.
Sr.No. | Pernyataan & Deskripsi |
---|---|
1 | jika pernyataan Pernyataan if terdiri dari ekspresi Boolean yang diikuti oleh do, satu atau lebih pernyataan yang dapat dieksekusi dan akhirnya endkata kunci. Kode dalam pernyataan if hanya dijalankan jika kondisi Boolean bernilai true. |
2 | if..else pernyataan Pernyataan if dapat diikuti oleh pernyataan lain opsional (dalam blok do..end), yang dieksekusi ketika ekspresi Boolean salah. |
3 | kecuali pernyataan Pernyataan kecuali memiliki badan yang sama dengan pernyataan if. Kode di dalam pernyataan kecuali hanya dijalankan ketika kondisi yang ditentukan salah. |
4 | kecuali .. pernyataan lain Pernyataan kecuali..else memiliki isi yang sama dengan pernyataan if..else. Kode di dalam pernyataan kecuali hanya dijalankan ketika kondisi yang ditentukan salah. |
5 | kond Pernyataan cond digunakan di mana kita ingin mengeksekusi kode berdasarkan beberapa kondisi. Ia bekerja seperti konstruksi if ... else if… .else dalam beberapa bahasa pemrograman lain. |
6 | kasus Pernyataan kasus dapat dianggap sebagai pengganti pernyataan switch dalam bahasa imperatif. Kasus mengambil variabel / literal dan menerapkan pola yang cocok dengannya dengan kasus yang berbeda. Jika ada kasus yang cocok, Elixir mengeksekusi kode yang terkait dengan kasus itu dan keluar dari pernyataan kasus. |
String dalam Elixir disisipkan di antara tanda kutip ganda, dan disandikan dalam UTF-8. Tidak seperti C dan C ++ di mana string default dikodekan ASCII dan hanya 256 karakter berbeda yang dimungkinkan, UTF-8 terdiri dari 1.112.064 titik kode. Ini berarti pengkodean UTF-8 terdiri dari banyak karakter yang mungkin berbeda. Karena string menggunakan utf-8, kita juga dapat menggunakan simbol seperti: ö, ł, dll.
Buat String
Untuk membuat variabel string, cukup tetapkan string ke variabel -
str = "Hello world"
Untuk mencetak ini ke konsol Anda, cukup panggil IO.puts fungsi dan berikan variabel str -
str = str = "Hello world"
IO.puts(str)
Program di atas menghasilkan hasil sebagai berikut -
Hello World
String Kosong
Anda dapat membuat string kosong menggunakan string literal, "". Sebagai contoh,
a = ""
if String.length(a) === 0 do
IO.puts("a is an empty string")
end
Program di atas menghasilkan hasil sebagai berikut.
a is an empty string
Interpolasi String
Interpolasi string adalah cara untuk membuat nilai String baru dari campuran konstanta, variabel, literal, dan ekspresi dengan memasukkan nilainya di dalam literal string. Elixir mendukung interpolasi string, untuk menggunakan variabel dalam string, saat menulisnya, bungkus dengan kurung kurawal dan tambahkan tanda kurung kurawal dengan'#' tanda.
Sebagai contoh,
x = "Apocalypse"
y = "X-men #{x}"
IO.puts(y)
Ini akan mengambil nilai x dan menggantikannya dengan y. Kode di atas akan menghasilkan hasil sebagai berikut -
X-men Apocalypse
Penggabungan String
Kami telah melihat penggunaan penggabungan String di bab-bab sebelumnya. Operator '<>' digunakan untuk menggabungkan string di Elixir. Untuk menggabungkan 2 string,
x = "Dark"
y = "Knight"
z = x <> " " <> y
IO.puts(z)
Kode di atas menghasilkan hasil sebagai berikut -
Dark Knight
Panjang String
Untuk mendapatkan panjang string, kami menggunakan String.lengthfungsi. Lewatkan string sebagai parameter dan itu akan menunjukkan ukurannya. Sebagai contoh,
IO.puts(String.length("Hello"))
Saat menjalankan program di atas, ini menghasilkan hasil sebagai berikut -
5
Membalikkan String
Untuk membalikkan string, teruskan ke fungsi String.reverse. Sebagai contoh,
IO.puts(String.reverse("Elixir"))
Program di atas menghasilkan hasil sebagai berikut -
rixilE
Perbandingan String
Untuk membandingkan 2 string, kita dapat menggunakan operator == atau ===. Sebagai contoh,
var_1 = "Hello world"
var_2 = "Hello Elixir"
if var_1 === var_2 do
IO.puts("#{var_1} and #{var_2} are the same")
else
IO.puts("#{var_1} and #{var_2} are not the same")
end
Program di atas menghasilkan hasil sebagai berikut -
Hello world and Hello elixir are not the same.
Pencocokan String
Kita telah melihat penggunaan operator pencocokan string = ~. Untuk memeriksa apakah suatu string cocok dengan regex, kita juga dapat menggunakan operator pencocokan string atau String.match? fungsi. Sebagai contoh,
IO.puts(String.match?("foo", ~r/foo/))
IO.puts(String.match?("bar", ~r/foo/))
Program di atas menghasilkan hasil sebagai berikut -
true
false
Hal yang sama juga dapat dicapai dengan menggunakan operator = ~. Sebagai contoh,
IO.puts("foo" =~ ~r/foo/)
Program di atas menghasilkan hasil sebagai berikut -
true
Fungsi String
Elixir mendukung sejumlah besar fungsi yang terkait dengan string, beberapa yang paling sering digunakan tercantum dalam tabel berikut.
Sr.No. | Fungsi dan Tujuannya |
---|---|
1 | at(string, position) Mengembalikan grafik pada posisi string utf8 yang ditentukan. Jika posisi lebih besar dari panjang string, maka ia mengembalikan nihil |
2 | capitalize(string) Mengonversi karakter pertama dalam string yang diberikan menjadi huruf besar dan sisanya menjadi huruf kecil |
3 | contains?(string, contents) Memeriksa apakah string berisi salah satu konten yang diberikan |
4 | downcase(string) Mengonversi semua karakter dalam string yang diberikan menjadi huruf kecil |
5 | ends_with?(string, suffixes) Mengembalikan nilai benar jika string diakhiri dengan salah satu sufiks yang diberikan |
6 | first(string) Mengembalikan grafem pertama dari string utf8, nihil jika string kosong |
7 | last(string) Mengembalikan grafem terakhir dari string utf8, nihil jika string kosong |
8 | replace(subject, pattern, replacement, options \\ []) Mengembalikan string baru yang dibuat dengan mengganti kemunculan pola dalam subjek dengan penggantian |
9 | slice(string, start, len) Mengembalikan substring mulai dari awal offset, dan panjang len |
10 | split(string) Membagi string menjadi beberapa substring di setiap kejadian spasi kosong Unicode dengan spasi kosong di depan dan di belakangnya diabaikan. Grup spasi putih diperlakukan sebagai kejadian tunggal. Pembagian tidak terjadi pada spasi kosong non-breaking |
11 | upcase(string) Mengonversi semua karakter dalam string yang diberikan menjadi huruf besar |
Binari
Biner hanyalah urutan byte. Biner didefinisikan menggunakan<< >>. Sebagai contoh:
<< 0, 1, 2, 3 >>
Tentu saja, byte tersebut dapat diatur dengan cara apa pun, bahkan dalam urutan yang tidak menjadikannya string yang valid. Sebagai contoh,
<< 239, 191, 191 >>
String juga merupakan binari. Dan operator penggabungan string<> sebenarnya adalah operator penggabungan biner:
IO.puts(<< 0, 1 >> <> << 2, 3 >>)
Kode di atas menghasilkan hasil sebagai berikut -
<< 0, 1, 2, 3 >>
Perhatikan karakter ł. Karena ini dikodekan utf-8, representasi karakter ini membutuhkan 2 byte.
Karena setiap angka yang direpresentasikan dalam biner dimaksudkan sebagai byte, ketika nilai ini naik dari 255, itu dipotong. Untuk mencegah hal ini, kami menggunakan pengubah ukuran untuk menentukan berapa banyak bit yang kami inginkan untuk diambil nomor tersebut. Misalnya -
IO.puts(<< 256 >>) # truncated, it'll print << 0 >>
IO.puts(<< 256 :: size(16) >>) #Takes 16 bits/2 bytes, will print << 1, 0 >>
Program di atas akan menghasilkan hasil sebagai berikut -
<< 0 >>
<< 1, 0 >>
Kita juga dapat menggunakan pengubah utf8, jika sebuah karakter adalah titik kode maka itu akan diproduksi di keluaran; kalau tidak byte -
IO.puts(<< 256 :: utf8 >>)
Program di atas menghasilkan hasil sebagai berikut -
Ā
Kami juga memiliki fungsi yang disebut is_binaryyang memeriksa apakah variabel yang diberikan adalah biner. Perhatikan bahwa hanya variabel yang disimpan sebagai kelipatan 8 bit yang merupakan biner.
Bitstring
Jika kita mendefinisikan biner menggunakan pengubah ukuran dan memberikan nilai yang bukan kelipatan 8, kita akan berakhir dengan bitstring, bukan biner. Sebagai contoh,
bs = << 1 :: size(1) >>
IO.puts(bs)
IO.puts(is_binary(bs))
IO.puts(is_bitstring(bs))
Program di atas menghasilkan hasil sebagai berikut -
<< 1::size(1) >>
false
true
Artinya variabel itu bsbukan biner melainkan bitstring. Kita juga dapat mengatakan bahwa biner adalah bitstring di mana jumlah bitnya habis dibagi 8. Pencocokan pola bekerja pada biner serta bitstring dengan cara yang sama.
Daftar karakter tidak lebih dari daftar karakter. Pertimbangkan program berikut untuk memahami hal yang sama.
IO.puts('Hello')
IO.puts(is_list('Hello'))
Program di atas menghasilkan hasil sebagai berikut -
Hello
true
Alih-alih berisi byte, daftar karakter berisi poin kode karakter di antara tanda kutip tunggal. So while the double-quotes represent a string (i.e. a binary), singlequotes represent a char list (i.e. a list). Perhatikan bahwa IEx hanya akan menghasilkan titik kode sebagai keluaran jika salah satu karakter berada di luar rentang ASCII.
Daftar karakter kebanyakan digunakan saat berinteraksi dengan Erlang, khususnya pustaka lama yang tidak menerima binari sebagai argumen. Anda dapat mengubah daftar karakter menjadi string dan kembali dengan menggunakan fungsi to_string (char_list) dan to_char_list (string) -
IO.puts(is_list(to_char_list("hełło")))
IO.puts(is_binary(to_string ('hełło')))
Program di atas menghasilkan hasil sebagai berikut -
true
true
NOTE - Fungsinya to_string dan to_char_list bersifat polimorfik, yaitu, mereka dapat mengambil berbagai jenis masukan seperti atom, bilangan bulat, dan mengubahnya menjadi string dan daftar karakter.
Daftar (Tertaut)
Daftar tertaut adalah daftar beragam elemen yang disimpan di lokasi berbeda dalam memori dan dilacak dengan menggunakan referensi. Daftar tertaut adalah struktur data yang terutama digunakan dalam pemrograman fungsional.
Elixir menggunakan tanda kurung siku untuk menentukan daftar nilai. Nilai bisa dari jenis apa pun -
[1, 2, true, 3]
Ketika Elixir melihat daftar nomor ASCII yang dapat dicetak, Elixir akan mencetaknya sebagai daftar karakter (secara harfiah adalah daftar karakter). Setiap kali Anda melihat nilai di IEx dan tidak yakin apa nilainya, Anda dapat menggunakani berfungsi untuk mengambil informasi tentangnya.
IO.puts([104, 101, 108, 108, 111])
Karakter di atas dalam daftar semuanya dapat dicetak. Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
hello
Anda juga dapat menentukan daftar dengan cara lain, menggunakan tanda kutip tunggal -
IO.puts(is_list('Hello'))
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
true
Ingatlah bahwa representasi yang dikutip tunggal dan yang dikutip ganda tidak setara dalam Elixir karena diwakili oleh jenis yang berbeda.
Panjang Daftar
Untuk mencari panjang list, kita menggunakan fungsi length seperti pada program berikut -
IO.puts(length([1, 2, :true, "str"]))
Program di atas menghasilkan hasil sebagai berikut -
4
Rangkaian dan Pengurangan
Dua daftar dapat digabungkan dan dikurangi menggunakan ++ dan --operator. Perhatikan contoh berikut untuk memahami fungsinya.
IO.puts([1, 2, 3] ++ [4, 5, 6])
IO.puts([1, true, 2, false, 3, true] -- [true, false])
Ini akan memberi Anda string gabungan di kasus pertama dan string dikurangi di kasus kedua. Program di atas menghasilkan hasil sebagai berikut -
[1, 2, 3, 4, 5, 6]
[1, 2, 3, true]
Kepala dan Ekor Daftar
Kepala adalah elemen pertama dari daftar dan ekor adalah sisa daftar. Mereka bisa diambil dengan fungsihd dan tl. Mari kita menetapkan daftar ke variabel dan mengambil kepala dan ekornya.
list = [1, 2, 3]
IO.puts(hd(list))
IO.puts(tl(list))
Ini akan memberi kita kepala dan ekor dari daftar sebagai keluaran. Program di atas menghasilkan hasil sebagai berikut -
1
[2, 3]
Note - Mendapatkan kepala atau ekor dari daftar kosong adalah kesalahan.
Fungsi Daftar lainnya
Pustaka standar Elixir menyediakan banyak fungsi untuk menangani daftar. Kami akan melihat beberapa di antaranya di sini. Anda dapat melihat sisanya di sini . Daftar .
S. tidak. | Nama Fungsi dan Deskripsi |
---|---|
1 | delete(list, item) Menghapus item yang diberikan dari daftar. Menampilkan daftar tanpa item. Jika item muncul lebih dari sekali dalam daftar, hanya kemunculan pertama yang dihapus. |
2 | delete_at(list, index) Menghasilkan daftar baru dengan menghapus nilai pada indeks yang ditentukan. Indeks negatif menunjukkan offset dari akhir daftar. Jika indeks di luar batas, daftar asli dikembalikan. |
3 | first(list) Mengembalikan elemen pertama dalam daftar atau nihil jika daftar kosong. |
4 | flatten(list) Meratakan daftar daftar bersarang yang diberikan. |
5 | insert_at(list, index, value) Menampilkan daftar dengan nilai yang disisipkan pada indeks yang ditentukan. Perhatikan bahwa indeks dibatasi pada panjang daftar. Indeks negatif menunjukkan offset dari akhir daftar. |
6 | last(list) Mengembalikan elemen terakhir dalam daftar atau nihil jika daftar kosong. |
Tuple
Tupel juga merupakan struktur data yang menyimpan sejumlah struktur lain di dalamnya. Tidak seperti daftar, mereka menyimpan elemen dalam blok memori yang berdekatan. Ini berarti mengakses elemen tupel per indeks atau mendapatkan ukuran tupel adalah operasi yang cepat. Indeks dimulai dari nol.
Elixir menggunakan tanda kurung kurawal untuk mendefinisikan tupel. Seperti daftar, tupel dapat memiliki nilai apa pun -
{:ok, "hello"}
Panjang Tupel
Untuk mendapatkan panjang tupel, gunakan tuple_size berfungsi seperti pada program berikut -
IO.puts(tuple_size({:ok, "hello"}))
Program di atas menghasilkan hasil sebagai berikut -
2
Menambahkan Nilai
Untuk menambahkan nilai ke tupel, gunakan fungsi Tuple.append -
tuple = {:ok, "Hello"}
Tuple.append(tuple, :world)
Ini akan membuat dan mengembalikan tupel baru: {: ok, "Hello",: world}
Memasukkan Nilai
Untuk memasukkan nilai pada posisi tertentu, kita dapat menggunakan Tuple.insert_at fungsi atau put_elemfungsi. Pertimbangkan contoh berikut untuk memahami hal yang sama -
tuple = {:bar, :baz}
new_tuple_1 = Tuple.insert_at(tuple, 0, :foo)
new_tuple_2 = put_elem(tuple, 1, :foobar)
Perhatikan itu put_elem dan insert_atmengembalikan tupel baru. Tupel asli yang disimpan dalam variabel tupel tidak dimodifikasi karena tipe data Elixir tidak dapat diubah. Dengan menjadi tidak dapat diubah, kode Elixir lebih mudah untuk dipikirkan karena Anda tidak perlu khawatir jika kode tertentu mengubah struktur data Anda.
Tupel vs. Daftar
Apa perbedaan antara List dan Tuple?
Daftar disimpan dalam memori sebagai daftar tertaut, yang berarti bahwa setiap elemen dalam daftar menyimpan nilainya dan menunjuk ke elemen berikut hingga akhir daftar tercapai. Kami menyebut setiap pasangan nilai dan menunjuk sel kontra. Ini berarti mengakses panjang daftar adalah operasi linier: kita perlu melintasi seluruh daftar untuk mengetahui ukurannya. Memperbarui daftar dengan cepat selama kita menyiapkan elemen.
Tupel, di sisi lain, disimpan berdekatan dalam memori. Ini berarti mendapatkan ukuran tupel atau mengakses elemen dengan indeks dengan cepat. Namun, memperbarui atau menambahkan elemen ke tupel mahal karena memerlukan penyalinan seluruh tupel dalam memori.
Sejauh ini, kita belum membahas struktur data asosiatif apa pun, yaitu struktur data yang dapat mengaitkan nilai tertentu (atau beberapa nilai) ke sebuah kunci. Bahasa yang berbeda menyebut fitur ini dengan nama yang berbeda seperti kamus, hash, array asosiatif, dll.
Di Elixir, kami memiliki dua struktur data asosiatif utama: daftar kata kunci dan peta. Dalam bab ini, kita akan fokus pada daftar Kata Kunci.
Dalam banyak bahasa pemrograman fungsional, adalah umum untuk menggunakan daftar tupel 2-item sebagai representasi dari struktur data asosiatif. Di Elixir, ketika kita memiliki daftar tupel dan item pertama dari tupel (yaitu kunci) adalah atom, kita menyebutnya daftar kata kunci. Pertimbangkan contoh berikut untuk memahami hal yang sama -
list = [{:a, 1}, {:b, 2}]
Elixir mendukung sintaks khusus untuk menentukan daftar tersebut. Kita dapat menempatkan titik dua di akhir setiap atom dan menyingkirkan tupel seluruhnya. Sebagai contoh,
list_1 = [{:a, 1}, {:b, 2}]
list_2 = [a: 1, b: 2]
IO.puts(list_1 == list_2)
Program di atas akan menghasilkan hasil sebagai berikut -
true
Keduanya mewakili daftar kata kunci. Karena daftar kata kunci juga merupakan daftar, kami dapat menggunakan semua operasi yang kami gunakan pada daftar di dalamnya.
Untuk mengambil nilai yang terkait dengan atom dalam daftar kata kunci, berikan atom sebagai [] setelah nama daftar -
list = [a: 1, b: 2]
IO.puts(list[:a])
Program di atas menghasilkan hasil sebagai berikut -
1
Daftar kata kunci memiliki tiga karakteristik khusus -
- Kunci harus berupa atom.
- Kunci diurutkan, seperti yang ditentukan oleh pengembang.
- Kunci bisa diberikan lebih dari satu kali.
Untuk memanipulasi daftar kata kunci, Elixir menyediakan modul Kata Kunci . Namun, ingatlah, daftar kata kunci hanyalah daftar, dan dengan demikian daftar tersebut memberikan karakteristik kinerja linier yang sama seperti daftar. Semakin panjang daftarnya, semakin lama waktu yang dibutuhkan untuk menemukan kunci, menghitung jumlah item, dan seterusnya. Karena alasan ini, daftar kata kunci digunakan di Elixir terutama sebagai opsi. Jika Anda perlu menyimpan banyak item atau menjamin satu kunci terkait dengan satu nilai maksimum, Anda harus menggunakan peta sebagai gantinya.
Mengakses kunci
Untuk mengakses nilai yang terkait dengan kunci tertentu, kami menggunakan Keyword.getfungsi. Ini mengembalikan nilai pertama yang terkait dengan kunci yang diberikan. Untuk mendapatkan semua nilai, kami menggunakan fungsi Keyword.get_values. Misalnya -
kl = [a: 1, a: 2, b: 3]
IO.puts(Keyword.get(kl, :a))
IO.puts(Keyword.get_values(kl))
Program di atas akan menghasilkan hasil sebagai berikut -
1
[1, 2]
Memasukkan kunci
Untuk menambahkan nilai baru, gunakan Keyword.put_new. Jika kunci sudah ada, nilainya tetap tidak berubah -
kl = [a: 1, a: 2, b: 3]
kl_new = Keyword.put_new(kl, :c, 5)
IO.puts(Keyword.get(kl_new, :c))
Ketika program di atas dijalankan, itu menghasilkan daftar Kata Kunci baru dengan kunci tambahan, c dan menghasilkan hasil sebagai berikut -
5
Menghapus kunci
Jika Anda ingin menghapus semua entri untuk sebuah kunci, gunakan Keyword.delete; untuk menghapus hanya entri pertama untuk kunci, gunakan Keyword.delete_first.
kl = [a: 1, a: 2, b: 3, c: 0]
kl = Keyword.delete_first(kl, :b)
kl = Keyword.delete(kl, :a)
IO.puts(Keyword.get(kl, :a))
IO.puts(Keyword.get(kl, :b))
IO.puts(Keyword.get(kl, :c))
Ini akan menghapus yang pertama b dalam Daftar dan semua adalam daftar. Ketika program di atas dijalankan, maka akan menghasilkan hasil sebagai berikut -
0
Daftar kata kunci adalah cara mudah untuk menangani konten yang disimpan dalam daftar dengan kunci, tetapi di bawahnya, Elixir masih berjalan melalui daftar. Itu mungkin cocok jika Anda memiliki rencana lain untuk daftar itu yang membutuhkan berjalan melalui semuanya, tetapi ini bisa menjadi overhead yang tidak perlu jika Anda berencana untuk menggunakan kunci sebagai satu-satunya pendekatan Anda ke data.
Di sinilah peta datang untuk menyelamatkan Anda. Kapan pun Anda membutuhkan penyimpanan nilai kunci, peta adalah struktur data "tujuan" di Elixir.
Membuat Peta
Peta dibuat menggunakan sintaks% {} -
map = %{:a => 1, 2 => :b}
Dibandingkan dengan daftar kata kunci, kami sudah dapat melihat dua perbedaan -
- Peta mengizinkan nilai apa pun sebagai kunci.
- Kunci Maps tidak mengikuti urutan apa pun.
Mengakses kunci
Untuk mengakses nilai yang terkait dengan kunci, Maps menggunakan sintaks yang sama seperti daftar Kata Kunci -
map = %{:a => 1, 2 => :b}
IO.puts(map[:a])
IO.puts(map[2])
Ketika program di atas dijalankan, ini menghasilkan hasil sebagai berikut -
1
b
Memasukkan kunci
Untuk memasukkan kunci di peta, kami menggunakan Dict.put_new fungsi yang mengambil peta, kunci baru dan nilai baru sebagai argumen -
map = %{:a => 1, 2 => :b}
new_map = Dict.put_new(map, :new_val, "value")
IO.puts(new_map[:new_val])
Ini akan memasukkan pasangan nilai kunci :new_val - "value"di peta baru. Ketika program di atas dijalankan, ini menghasilkan hasil sebagai berikut -
"value"
Memperbarui Nilai
Untuk memperbarui nilai yang sudah ada di peta, Anda dapat menggunakan sintaks berikut -
map = %{:a => 1, 2 => :b}
new_map = %{ map | a: 25}
IO.puts(new_map[:a])
Ketika program di atas dijalankan, ini menghasilkan hasil sebagai berikut -
25
Pencocokan Pola
Berbeda dengan daftar kata kunci, peta sangat berguna dengan pencocokan pola. Saat peta digunakan dalam pola, peta akan selalu cocok dengan subset dari nilai yang diberikan -
%{:a => a} = %{:a => 1, 2 => :b}
IO.puts(a)
Program di atas menghasilkan hasil sebagai berikut -
1
Ini akan cocok a dengan 1. Dan karenanya, itu akan menghasilkan keluaran sebagai1.
Seperti yang ditunjukkan di atas, peta cocok selama kunci dalam pola ada di peta yang diberikan. Oleh karena itu, peta kosong cocok dengan semua peta.
Variabel dapat digunakan saat mengakses, mencocokkan dan menambahkan kunci peta -
n = 1
map = %{n => :one}
%{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
Modul Peta menyediakan API yang sangat mirip dengan modul Kata Kunci dengan fungsi praktis untuk memanipulasi peta. Anda dapat menggunakan fungsi sepertiMap.get, Map.delete, untuk memanipulasi peta.
Peta dengan kunci Atom
Peta hadir dengan beberapa properti menarik. Jika semua kunci di peta adalah atom, Anda dapat menggunakan sintaks kata kunci untuk kenyamanan -
map = %{:a => 1, 2 => :b}
IO.puts(map.a)
Properti menarik lainnya dari peta adalah mereka menyediakan sintaksnya sendiri untuk memperbarui dan mengakses kunci atom -
map = %{:a => 1, 2 => :b}
IO.puts(map.a)
Program di atas menghasilkan hasil sebagai berikut -
1
Perhatikan bahwa untuk mengakses kunci atom dengan cara ini, itu harus ada atau program akan gagal bekerja.
Di Elixir, kami mengelompokkan beberapa fungsi ke dalam modul. Kami telah menggunakan modul yang berbeda di bab-bab sebelumnya seperti modul String, modul Bitwise, modul Tuple, dll.
Untuk membuat modul kami sendiri di Elixir, kami menggunakan defmodulemakro. Kami menggunakandef makro untuk menentukan fungsi dalam modul itu -
defmodule Math do
def sum(a, b) do
a + b
end
end
Pada bagian berikut, contoh kita akan menjadi lebih panjang ukurannya, dan mungkin sulit untuk mengetik semuanya di shell. Kita perlu belajar bagaimana mengkompilasi kode Elixir dan juga bagaimana menjalankan skrip Elixir.
Kompilasi
Selalu mudah untuk menulis modul ke dalam file sehingga dapat dikompilasi dan digunakan kembali. Mari kita asumsikan kita memiliki file bernama math.ex dengan konten berikut -
defmodule Math do
def sum(a, b) do
a + b
end
end
Kita dapat mengkompilasi file menggunakan perintah -elixirc :
$ elixirc math.ex
Ini akan menghasilkan file bernama Elixir.Math.beamberisi bytecode untuk modul yang ditentukan. Jika kita mulaiiexsekali lagi, definisi modul kami akan tersedia (asalkan iex dimulai di direktori yang sama dengan file bytecode). Sebagai contoh,
IO.puts(Math.sum(1, 2))
Program di atas akan menghasilkan hasil sebagai berikut -
3
Mode Skrip
Selain ekstensi file Elixir .ex, Elixir juga mendukung .exsfile untuk pembuatan skrip. Elixir memperlakukan kedua file dengan cara yang persis sama, satu-satunya perbedaan adalah pada tujuannya..ex file dimaksudkan untuk dikompilasi sementara file .exs digunakan untuk scripting. Ketika dijalankan, kedua ekstensi mengkompilasi dan memuat modul mereka ke dalam memori, meskipun hanya.ex file menulis bytecode mereka ke disk dalam format file .beam.
Misalnya, jika kita ingin menjalankan file Math.sum di file yang sama, kita dapat menggunakan .exs dengan cara berikut -
Math.exs
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts(Math.sum(1, 2))
Kita bisa menjalankannya menggunakan perintah Elixir -
$ elixir math.exs
Program di atas akan menghasilkan hasil sebagai berikut -
3
File akan dikompilasi dalam memori dan dieksekusi, mencetak "3" sebagai hasilnya. Tidak ada file bytecode yang akan dibuat.
Module Nesting
Modul dapat disarangkan di Elixir. Fitur bahasa ini membantu kami mengatur kode kami dengan cara yang lebih baik. Untuk membuat modul bersarang, kami menggunakan sintaks berikut -
defmodule Foo do
#Foo module code here
defmodule Bar do
#Bar module code here
end
end
Contoh yang diberikan di atas akan mendefinisikan dua modul: Foo dan Foo.Bar. Yang kedua dapat diakses sebagaiBar dalam Fooselama mereka berada dalam lingkup leksikal yang sama. Jika, nanti, fileBar modul dipindahkan di luar definisi modul Foo, itu harus direferensikan dengan nama lengkapnya (Foo.Bar) atau sebuah alias harus diatur menggunakan direktif alias yang dibahas dalam bab alias.
Note- Di Elixir, tidak perlu mendefinisikan modul Foo untuk mendefinisikan modul Foo.Bar, karena bahasa menerjemahkan semua nama modul menjadi atom. Anda dapat menentukan modul yang disinkronkan dengan arbitrase tanpa menentukan modul apa pun dalam rantai. Misalnya, Anda dapat mendefinisikanFoo.Bar.Baz tanpa mendefinisikan Foo atau Foo.Bar.
Untuk memfasilitasi penggunaan kembali perangkat lunak, Elixir menyediakan tiga arahan - alias, require dan import. Ini juga menyediakan makro yang disebut penggunaan yang diringkas di bawah ini -
# Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar
# Ensure the module is compiled and available (usually for macros)
require Foo
# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo
# Invokes the custom code defined in Foo as an extension point
use Foo
Sekarang mari kita pahami secara rinci tentang setiap arahan.
alias
Direktif alias memungkinkan Anda mengatur alias untuk nama modul apa pun. Misalnya jika Anda ingin memberi alias'Str' ke modul String, Anda cukup menulis -
alias String, as: Str
IO.puts(Str.length("Hello"))
Program di atas menghasilkan hasil sebagai berikut -
5
Alias diberikan ke String modul sebagai Str. Sekarang ketika kita memanggil fungsi apa pun menggunakan literal Str, sebenarnya itu merujuk keStringmodul. Ini sangat membantu ketika kita menggunakan nama modul yang sangat panjang dan ingin menggantinya dengan yang lebih pendek di lingkup saat ini.
NOTE - Alias MUST mulai dengan huruf kapital.
Alias hanya valid di dalam lexical scope mereka dipanggil. Misalnya, jika Anda memiliki 2 modul dalam sebuah file dan membuat sebuah alias dalam salah satu modul, alias itu tidak akan dapat diakses di modul kedua.
Jika Anda memberi nama modul bawaan, seperti String atau Tuple, sebagai alias untuk beberapa modul lain, untuk mengakses modul bawaan, Anda harus menambahkannya dengan "Elixir.". Sebagai contoh,
alias List, as: String
#Now when we use String we are actually using List.
#To use the string module:
IO.puts(Elixir.String.length("Hello"))
Ketika program di atas dijalankan, ini menghasilkan hasil sebagai berikut -
5
memerlukan
Elixir menyediakan makro sebagai mekanisme untuk meta-pemrograman (menulis kode yang menghasilkan kode).
Makro adalah potongan kode yang dieksekusi dan diperluas pada waktu kompilasi. Artinya, untuk menggunakan makro, kita perlu menjamin bahwa modul dan implementasinya tersedia selama kompilasi. Ini dilakukan denganrequire pengarahan.
Integer.is_odd(3)
Ketika program di atas dijalankan, maka akan menghasilkan hasil sebagai berikut -
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1
Dalam Elixir, Integer.is_odd didefinisikan sebagai a macro. Makro ini bisa digunakan sebagai pelindung. Artinya, untuk memohonInteger.is_odd, kita membutuhkan modul Integer.
Menggunakan require Integer berfungsi dan menjalankan program seperti yang ditunjukkan di bawah ini.
require Integer
Integer.is_odd(3)
Kali ini program akan berjalan dan menghasilkan keluaran berupa: true.
Secara umum, modul tidak diperlukan sebelum digunakan, kecuali jika kita ingin menggunakan makro yang tersedia di modul tersebut. Upaya untuk memanggil makro yang tidak dimuat akan menimbulkan kesalahan. Perhatikan bahwa seperti perintah alias, require juga memiliki cakupan leksikal . Kami akan berbicara lebih banyak tentang makro di bab selanjutnya.
impor
Kami menggunakan importdirektif untuk mengakses fungsi atau makro dengan mudah dari modul lain tanpa menggunakan nama yang sepenuhnya memenuhi syarat. Misalnya, jika kita ingin menggunakan fileduplicate fungsi dari modul Daftar beberapa kali, kita cukup mengimpornya.
import List, only: [duplicate: 2]
Dalam hal ini, kami hanya mengimpor fungsi duplikat (dengan panjang daftar argumen 2) dari Daftar. Meskipun:only bersifat opsional, penggunaannya disarankan untuk menghindari impor semua fungsi dari modul tertentu di dalam namespace. :except juga dapat diberikan sebagai opsi untuk mengimpor semuanya dalam modul kecuali daftar fungsi.
Itu import direktif juga mendukung :macros dan :functions untuk diberikan kepada :only. Misalnya, untuk mengimpor semua makro, pengguna dapat menulis -
import Integer, only: :macros
Perhatikan bahwa impor juga Lexically scopedseperti yang dibutuhkan dan arahan alias. Perhatikan juga itu'import'ing a module also 'require's it.
menggunakan
Meski bukan arahan, use adalah makro yang terkait erat dengan requireyang memungkinkan Anda menggunakan modul dalam konteks saat ini. Makro penggunaan sering digunakan oleh pengembang untuk membawa fungsionalitas eksternal ke dalam lingkup leksikal saat ini, seringkali modul. Mari kita pahami penggunaan direktif melalui contoh -
defmodule Example do
use Feature, option: :value
end
Gunakan adalah makro yang mengubah hal di atas menjadi -
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
Itu use Module pertama membutuhkan modul dan kemudian memanggil __using__makro pada Modul. Elixir memiliki kemampuan metaprogramming yang hebat dan memiliki makro untuk menghasilkan kode pada waktu kompilasi. Makro _ _using__ dipanggil pada contoh di atas, dan kode dimasukkan ke dalam konteks lokal kita. Konteks lokal adalah tempat penggunaan makro dipanggil pada saat kompilasi.
Fungsi adalah sekumpulan pernyataan yang disusun bersama untuk melakukan tugas tertentu. Fungsi dalam pemrograman bekerja kebanyakan seperti fungsi dalam Matematika. Anda memberi fungsi beberapa masukan, mereka menghasilkan keluaran berdasarkan masukan yang diberikan.
Ada 2 jenis fungsi di Elixir -
Fungsi anonim
Fungsi ditentukan menggunakan fn..end constructadalah fungsi anonim. Fungsi ini terkadang juga disebut sebagai lambda. Mereka digunakan dengan menugaskannya ke nama variabel.
Fungsi bernama
Fungsi ditentukan menggunakan def keyworddiberi nama fungsi. Ini adalah fungsi asli yang disediakan di Elixir.
Fungsi Anonim
Seperti namanya, fungsi anonim tidak memiliki nama. Ini sering kali diteruskan ke fungsi lain. Untuk mendefinisikan fungsi anonim di Elixir, kita membutuhkanfn dan endkata kunci. Di dalamnya, kita dapat menentukan sejumlah parameter dan badan fungsi yang dipisahkan oleh->. Sebagai contoh,
sum = fn (a, b) -> a + b end
IO.puts(sum.(1, 5))
Saat menjalankan program di atas, dijalankan, ini menghasilkan hasil sebagai berikut -
6
Perhatikan bahwa fungsi ini tidak dipanggil seperti fungsi bernama. Kita punya sebuah '.'antara nama fungsi dan argumennya.
Menggunakan Capture Operator
Kami juga dapat mendefinisikan fungsi-fungsi ini menggunakan operator penangkapan. Ini adalah metode yang lebih mudah untuk membuat fungsi. Sekarang kita akan mendefinisikan fungsi penjumlahan di atas menggunakan operator penangkapan,
sum = &(&1 + &2)
IO.puts(sum.(1, 2))
Ketika program di atas dijalankan, ini menghasilkan hasil sebagai berikut -
3
Dalam versi singkat, parameter kita tidak dinamai tetapi tersedia bagi kita sebagai & 1, & 2, & 3, dan seterusnya.
Fungsi Pencocokan Pola
Pencocokan pola tidak hanya terbatas pada variabel dan struktur data. Kita bisa menggunakan pencocokan pola untuk membuat fungsi kita polimorfik. Misalnya, kami akan mendeklarasikan fungsi yang dapat mengambil 1 atau 2 input (dalam tupel) dan mencetaknya ke konsol,
handle_result = fn
{var1} -> IO.puts("#{var1} found in a tuple!")
{var_2, var_3} -> IO.puts("#{var_2} and #{var_3} found!")
end
handle_result.({"Hey people"})
handle_result.({"Hello", "World"})
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hey people found in a tuple!
Hello and World found!
Fungsi Bernama
Kita bisa mendefinisikan fungsi dengan nama sehingga kita bisa dengan mudah merujuknya nanti. Fungsi bernama didefinisikan dalam modul menggunakan kata kunci def. Fungsi bernama selalu ditentukan dalam modul. Untuk memanggil fungsi bernama, kita perlu mereferensikannya menggunakan nama modulnya.
Berikut ini adalah sintaks untuk fungsi bernama -
def function_name(argument_1, argument_2) do
#code to be executed when function is called
end
Sekarang mari kita tentukan jumlah fungsi yang dinamai dalam modul Matematika.
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts(Math.sum(5, 6))
Saat menjalankan program di atas, ini menghasilkan hasil sebagai berikut -
11
Untuk fungsi 1-liner, terdapat notasi singkatan untuk mendefinisikan fungsi-fungsi ini, menggunakan do:. Misalnya -
defmodule Math do
def sum(a, b), do: a + b
end
IO.puts(Math.sum(5, 6))
Saat menjalankan program di atas, ini menghasilkan hasil sebagai berikut -
11
Fungsi Pribadi
Elixir memberi kita kemampuan untuk mendefinisikan fungsi privat yang dapat diakses dari dalam modul di mana mereka didefinisikan. Untuk menentukan fungsi privat, gunakandefp dari pada def. Sebagai contoh,
defmodule Greeter do
def hello(name), do: phrase <> name
defp phrase, do: "Hello "
end
Greeter.hello("world")
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hello world
Tetapi jika kita hanya mencoba memanggil fungsi frase secara eksplisit, menggunakan Greeter.phrase() fungsi, itu akan menimbulkan kesalahan.
Argumen default
Jika kita menginginkan nilai default untuk sebuah argumen, kita menggunakan argument \\ value sintaks -
defmodule Greeter do
def hello(name, country \\ "en") do
phrase(country) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
Greeter.hello("Ayush", "en")
Greeter.hello("Ayush")
Greeter.hello("Ayush", "es")
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hello, Ayush
Hello, Ayush
Hola, Ayush
Rekursi adalah metode di mana solusi untuk suatu masalah bergantung pada solusi untuk contoh yang lebih kecil dari masalah yang sama. Sebagian besar bahasa pemrograman komputer mendukung rekursi dengan mengizinkan suatu fungsi memanggil dirinya sendiri di dalam teks program.
Idealnya fungsi rekursif memiliki kondisi akhir. Kondisi akhir ini, juga dikenal sebagai kasus dasar berhenti masuk kembali ke fungsi dan menambahkan panggilan fungsi ke tumpukan. Di sinilah panggilan fungsi rekursif berhenti. Mari kita perhatikan contoh berikut untuk lebih memahami fungsi rekursif.
defmodule Math do
def fact(res, num) do
if num === 1 do
res
else
new_res = res * num
fact(new_res, num-1)
end
end
end
IO.puts(Math.fact(1,5))
Ketika program di atas dijalankan, ini menghasilkan hasil sebagai berikut -
120
Jadi pada fungsi di atas, Math.fact, kami menghitung faktorial sebuah angka. Perhatikan bahwa kita memanggil fungsi itu sendiri. Mari kita sekarang memahami cara kerjanya.
Kami telah menyediakannya dengan 1 dan bilangan yang faktorialnya ingin kami hitung. Fungsi tersebut memeriksa apakah angkanya 1 atau tidak dan mengembalikan res jika 1(Ending condition). Jika tidak maka itu membuat variabel new_res dan memberikan nilai res * current num sebelumnya. Ini mengembalikan nilai yang dikembalikan oleh fakta panggilan fungsi kita (new_res, num-1) . Ini berulang sampai kita mendapatkan jumlah sebagai 1. Setelah itu terjadi, kita mendapatkan hasilnya.
Mari kita pertimbangkan contoh lain, mencetak setiap elemen dari daftar satu per satu. Untuk melakukan ini, kami akan memanfaatkanhd dan tl fungsi daftar dan pencocokan pola dalam fungsi -
a = ["Hey", 100, 452, :true, "People"]
defmodule ListPrint do
def print([]) do
end
def print([head | tail]) do
IO.puts(head)
print(tail)
end
end
ListPrint.print(a)
Fungsi cetak pertama dipanggil ketika kita memiliki daftar kosong(ending condition). Jika tidak, maka fungsi cetak kedua akan dipanggil yang akan membagi daftar menjadi 2 dan menetapkan elemen pertama daftar ke kepala dan sisa daftar ke ekor. Kepala kemudian dicetak dan kami memanggil fungsi cetak lagi dengan sisa daftar, yaitu, ekor. Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hey
100
452
true
People
Karena kekekalan, loop di Elixir (seperti dalam bahasa pemrograman fungsional apa pun) ditulis berbeda dari bahasa imperatif. Misalnya, dalam bahasa imperatif seperti C, Anda akan menulis -
for(i = 0; i < 10; i++) {
printf("%d", array[i]);
}
Dalam contoh yang diberikan di atas, kami memutasi array dan variabel i. Mutasi tidak dimungkinkan di Elixir. Sebagai gantinya, bahasa fungsional mengandalkan rekursi: sebuah fungsi dipanggil secara rekursif sampai kondisi tercapai yang menghentikan tindakan rekursif untuk melanjutkan. Tidak ada data yang dimutasi dalam proses ini.
Sekarang mari kita tulis loop sederhana menggunakan rekursi yang mencetak hello n waktu.
defmodule Loop do
def print_multiple_times(msg, n) when n <= 1 do
IO.puts msg
end
def print_multiple_times(msg, n) do
IO.puts msg
print_multiple_times(msg, n - 1)
end
end
Loop.print_multiple_times("Hello", 10)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Kami telah menggunakan teknik pencocokan pola dan rekursi fungsi untuk berhasil mengimplementasikan loop. Definisi rekursif sulit untuk dipahami tetapi mengubah loop menjadi rekursi itu mudah.
Elixir memberi kita Enum module. Modul ini digunakan untuk panggilan perulangan yang paling berulang karena lebih mudah menggunakannya daripada mencoba mencari definisi rekursif untuk hal yang sama. Kami akan membahasnya di bab berikutnya. Definisi rekursif Anda sendiri hanya boleh digunakan ketika Anda tidak menemukan solusi menggunakan modul itu. Fungsi-fungsi tersebut dioptimalkan untuk panggilan ekor dan cukup cepat.
Enumerable adalah obyek yang bisa dicacah. "Enumerated" berarti menghitung anggota suatu set / koleksi / kategori satu per satu (biasanya berdasarkan urutan, biasanya berdasarkan nama).
Elixir memberikan konsep enumerabel dan modul Enum untuk bekerja dengannya. Fungsi dalam modul Enum dibatasi, seperti namanya, menghitung nilai dalam struktur data. Contoh struktur data enumerable adalah list, tuple, map, dll. Modul Enum memberi kita lebih dari 100 fungsi untuk menangani enum. Kami akan membahas beberapa fungsi penting dalam bab ini.
Semua fungsi ini mengambil enumerable sebagai elemen pertama dan fungsi sebagai elemen kedua dan mengerjakannya. Fungsi-fungsinya dijelaskan di bawah ini.
semua?
Saat kami menggunakan all? fungsi, seluruh koleksi harus dievaluasi menjadi benar jika tidak salah akan dikembalikan. Misalnya, untuk memeriksa apakah semua elemen dalam daftar adalah bilangan ganjil, maka.
res = Enum.all?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
false
Ini karena tidak semua elemen dalam daftar ini ganjil.
apa saja?
Seperti namanya, fungsi ini mengembalikan nilai true jika ada elemen koleksi yang bernilai true. Misalnya -
res = Enum.any?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
true
bingkah
Fungsi ini membagi koleksi kita menjadi potongan-potongan kecil dengan ukuran yang diberikan sebagai argumen kedua. Misalnya -
res = Enum.chunk([1, 2, 3, 4, 5, 6], 2)
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
[[1, 2], [3, 4], [5, 6]]
setiap
Mungkin perlu untuk mengulang koleksi tanpa menghasilkan nilai baru, untuk kasus ini kami menggunakan each fungsi -
Enum.each(["Hello", "Every", "one"], fn(s) -> IO.puts(s) end)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hello
Every
one
peta
Untuk menerapkan fungsi kami ke setiap item dan menghasilkan koleksi baru kami menggunakan fungsi peta. Ini adalah salah satu konstruksi yang paling berguna dalam pemrograman fungsional karena cukup ekspresif dan pendek. Mari kita pertimbangkan contoh untuk memahami ini. Kami akan menggandakan nilai yang disimpan dalam daftar dan menyimpannya dalam daftar barures -
res = Enum.map([2, 5, 3, 6], fn(a) -> a*2 end)
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
[4, 10, 6, 12]
mengurangi
Itu reducefungsi membantu kami mengurangi jumlah kami menjadi satu nilai. Untuk melakukan ini, kami menyediakan akumulator opsional (5 dalam contoh ini) untuk diteruskan ke fungsi kami; jika akumulator tidak disediakan, nilai pertama digunakan -
res = Enum.reduce([1, 2, 3, 4], 5, fn(x, accum) -> x + accum end)
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
15
Akumulator adalah nilai awal yang diteruskan ke fn. Dari panggilan kedua dan seterusnya, nilai yang dikembalikan dari panggilan sebelumnya diteruskan sebagai akumulasi. Kami juga dapat menggunakan pengurangan tanpa akumulator -
res = Enum.reduce([1, 2, 3, 4], fn(x, accum) -> x + accum end)
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
10
uniq
Fungsi uniq menghapus duplikat dari koleksi kami dan hanya mengembalikan kumpulan elemen dalam koleksi. Misalnya -
res = Enum.uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
IO.puts(res)
Saat menjalankan program di atas, ini menghasilkan hasil sebagai berikut -
[1, 2, 3, 4]
Evaluasi Semangat
Semua fungsi dalam modul Enum sangat dibutuhkan. Banyak fungsi mengharapkan enumerable dan mengembalikan daftar. Ini berarti bahwa saat melakukan beberapa operasi dengan Enum, setiap operasi akan menghasilkan daftar perantara hingga kami mencapai hasilnya. Mari kita perhatikan contoh berikut untuk memahami ini -
odd? = &(odd? = &(rem(&1, 2) != 0)
res = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
7500000000
Contoh di atas memiliki pipeline operasi. Kita mulai dengan rentang dan kemudian mengalikan setiap elemen dalam rentang dengan 3. Operasi pertama ini sekarang akan membuat dan mengembalikan daftar dengan 100_000 item. Kemudian kami menyimpan semua elemen ganjil dari daftar, membuat daftar baru, sekarang dengan 50_000 item, dan kemudian kami menjumlahkan semua entri.
Itu |> Simbol yang digunakan dalam potongan di atas adalah pipe operator: ia hanya mengambil keluaran dari ekspresi di sisi kirinya dan meneruskannya sebagai argumen pertama ke pemanggilan fungsi di sisi kanannya. Ini mirip dengan Unix | operator. Tujuannya adalah untuk menyoroti aliran data yang diubah oleh serangkaian fungsi.
Tanpa pipe operator, kodenya terlihat rumit -
Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
Kami memiliki banyak fungsi lain, namun hanya beberapa yang penting yang telah dijelaskan di sini.
Banyak fungsi mengharapkan enumerable dan mengembalikan a listkembali. Artinya, saat melakukan beberapa operasi dengan Enum, setiap operasi akan menghasilkan daftar perantara hingga kami mencapai hasilnya.
Stream mendukung operasi malas, bukan operasi bersemangat oleh enum. Pendeknya,streams are lazy, composable enumerables. Artinya, Stream tidak melakukan operasi kecuali benar-benar diperlukan. Mari kita pertimbangkan contoh untuk memahami ini -
odd? = &(rem(&1, 2) != 0)
res = 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
IO.puts(res)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
7500000000
Dalam contoh yang diberikan di atas, 1..100_000 |> Stream.map(&(&1 * 3))mengembalikan tipe data, aliran aktual, yang merepresentasikan perhitungan peta pada rentang 1..100_000. Representasi ini belum dievaluasi. Alih-alih membuat daftar perantara, aliran membangun serangkaian komputasi yang dipanggil hanya ketika kita meneruskan aliran yang mendasari ke modul Enum. Aliran berguna saat bekerja dengan koleksi yang besar, mungkin tak terbatas.
Stream dan enum memiliki banyak fungsi yang sama. Stream terutama menyediakan fungsi yang sama yang disediakan oleh modul Enum yang menghasilkan List sebagai nilai kembaliannya setelah melakukan penghitungan pada enumerable masukan. Beberapa dari mereka tercantum dalam tabel berikut -
Sr.No. | Fungsi dan Deskripsi nya |
---|---|
1 | chunk(enum, n, step, leftover \\ nil) Mengalirkan enumerable dalam potongan, masing-masing berisi n item, di mana setiap potongan baru memulai langkah elemen ke dalam enumerable. |
2 | concat(enumerables) Membuat aliran yang menghitung setiap enumerable dalam satu enumerable. |
3 | each(enum, fun) Jalankan fungsi yang diberikan untuk setiap item. |
4 | filter(enum, fun) Membuat aliran yang menyaring elemen sesuai dengan fungsi yang diberikan pada pencacahan. |
5 | map(enum, fun) Membuat aliran yang akan menerapkan fungsi yang diberikan pada pencacahan. |
6 | drop(enum, n) Dengan malas membuang n item berikutnya dari enumerable. |
Struktur adalah ekstensi yang dibangun di atas peta yang menyediakan pemeriksaan waktu kompilasi dan nilai default.
Mendefinisikan Struktur
Untuk mendefinisikan struct, konstruksi defstruct digunakan -
defmodule User do
defstruct name: "John", age: 27
end
Daftar kata kunci yang digunakan dengan defstruct menentukan bidang apa yang akan dimiliki struct beserta nilai defaultnya. Structs mengambil nama modul yang mereka definisikan. Dalam contoh yang diberikan di atas, kami mendefinisikan sebuah struct bernama User. Sekarang kita dapat membuat User struct dengan menggunakan sintaks yang mirip dengan yang digunakan untuk membuat peta -
new_john = %User{})
ayush = %User{name: "Ayush", age: 20}
megan = %User{name: "Megan"})
Kode di atas akan menghasilkan tiga struct berbeda dengan nilai -
%User{age: 27, name: "John"}
%User{age: 20, name: "Ayush"}
%User{age: 27, name: "Megan"}
Structs memberikan jaminan waktu kompilasi bahwa hanya kolom (dan semuanya) yang ditentukan melalui defstruct yang akan diizinkan untuk ada di struct. Jadi, Anda tidak dapat menentukan kolom Anda sendiri setelah Anda membuat struct di modul.
Mengakses dan Memperbarui Struktur
Saat kami membahas peta, kami menunjukkan bagaimana kami dapat mengakses dan memperbarui bidang peta. Teknik yang sama (dan sintaks yang sama) juga berlaku untuk struct. Misalnya, jika kita ingin memperbarui pengguna yang kita buat di contoh sebelumnya, maka -
defmodule User do
defstruct name: "John", age: 27
end
john = %User{}
#john right now is: %User{age: 27, name: "John"}
#To access name and age of John,
IO.puts(john.name)
IO.puts(john.age)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
John
27
Untuk memperbarui nilai dalam sebuah struct, kami akan kembali menggunakan prosedur yang sama yang kami gunakan di bab peta,
meg = %{john | name: "Meg"}
Struktur juga dapat digunakan dalam pencocokan pola, baik untuk pencocokan nilai kunci tertentu maupun untuk memastikan bahwa nilai yang cocok adalah struktur dengan jenis yang sama dengan nilai yang cocok.
Protokol adalah mekanisme untuk mencapai polimorfisme di Elixir. Pengiriman pada protokol tersedia untuk semua tipe data selama itu mengimplementasikan protokol.
Mari kita perhatikan contoh penggunaan protokol. Kami menggunakan fungsi yang disebutto_stringdi bab-bab sebelumnya untuk mengkonversi dari tipe lain ke tipe string. Ini sebenarnya adalah protokol. Ini bertindak sesuai dengan masukan yang diberikan tanpa menghasilkan kesalahan. Ini mungkin tampak seperti kita sedang mendiskusikan fungsi pencocokan pola, tetapi saat kita melangkah lebih jauh, ternyata berbeda.
Perhatikan contoh berikut untuk lebih memahami mekanisme protokol.
Mari kita buat protokol yang akan ditampilkan jika input yang diberikan kosong atau tidak. Kami akan menyebut protokol iniblank?.
Mendefinisikan Protokol
Kita dapat mendefinisikan protokol di Elixir dengan cara berikut -
defprotocol Blank do
def blank?(data)
end
Seperti yang Anda lihat, kita tidak perlu mendefinisikan body untuk fungsi tersebut. Jika Anda terbiasa dengan antarmuka dalam bahasa pemrograman lain, Anda dapat menganggap Protokol pada dasarnya sama.
Jadi Protokol ini mengatakan bahwa apapun yang mengimplementasikannya harus memiliki ekstensi empty?fungsi, meskipun terserah pelaksana tentang bagaimana fungsi merespons. Dengan protokol yang ditentukan, mari kita pahami cara menambahkan beberapa implementasi.
Menerapkan Protokol
Karena kita telah mendefinisikan protokol, sekarang kita perlu memberi tahu cara menangani input berbeda yang mungkin didapatnya. Mari kita membangun contoh yang telah kita ambil sebelumnya. Kami akan menerapkan protokol kosong untuk daftar, peta, dan string. Ini akan menunjukkan apakah benda yang kita lewati kosong atau tidak.
#Defining the protocol
defprotocol Blank do
def blank?(data)
end
#Implementing the protocol for lists
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
#Implementing the protocol for strings
defimpl Blank, for: BitString do
def blank?(""), do: true
def blank?(_), do: false
end
#Implementing the protocol for maps
defimpl Blank, for: Map do
def blank?(map), do: map_size(map) == 0
end
IO.puts(Blank.blank? [])
IO.puts(Blank.blank? [:true, "Hello"])
IO.puts(Blank.blank? "")
IO.puts(Blank.blank? "Hi")
Anda dapat mengimplementasikan Protokol Anda untuk jenis sebanyak atau sesedikit yang Anda inginkan, apa pun yang masuk akal untuk penggunaan Protokol Anda. Ini adalah kasus penggunaan protokol yang cukup mendasar. Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
true
false
true
false
Note - Jika Anda menggunakan ini untuk jenis apa pun selain yang Anda tentukan protokolnya, ini akan menghasilkan kesalahan.
File IO adalah bagian integral dari bahasa pemrograman apa pun karena memungkinkan bahasa untuk berinteraksi dengan file di sistem file. Pada bab ini, kita akan membahas dua modul - Path dan File.
Modul Path
Itu pathmodule adalah modul yang sangat kecil yang dapat dianggap sebagai modul pembantu untuk operasi sistem file. Mayoritas fungsi dalam modul File mengharapkan jalur sebagai argumen. Paling umum, jalur tersebut akan menjadi biner biasa. Modul Path menyediakan fasilitas untuk bekerja dengan jalur tersebut. Menggunakan fungsi dari modul Path dibandingkan dengan hanya memanipulasi binari lebih disukai karena modul Path menangani sistem operasi yang berbeda secara transparan. Perlu diperhatikan bahwa Elixir akan secara otomatis mengubah garis miring (/) menjadi garis miring terbalik (\) di Windows saat melakukan operasi file.
Mari kita pertimbangkan contoh berikut untuk lebih memahami modul Path -
IO.puts(Path.join("foo", "bar"))
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
foo/bar
Ada banyak metode yang disediakan modul jalur. Anda dapat melihat berbagai metode di sini . Metode ini sering digunakan jika Anda melakukan banyak operasi manipulasi file.
Modul File
Modul file berisi fungsi yang memungkinkan kita untuk membuka file sebagai perangkat IO. Secara default, file dibuka dalam mode biner, yang mengharuskan pengembang untuk menggunakan fileIO.binread dan IO.binwritefungsi dari modul IO. Mari kita buat file bernamanewfile dan menulis beberapa data padanya.
{:ok, file} = File.read("newfile", [:write])
# Pattern matching to store returned stream
IO.binwrite(file, "This will be written to the file")
Jika Anda membuka file yang baru saja kami tulis, konten akan ditampilkan dengan cara berikut -
This will be written to the file
Sekarang mari kita memahami cara menggunakan modul file.
Membuka file
Untuk membuka file, kita dapat menggunakan salah satu dari 2 fungsi berikut -
{:ok, file} = File.open("newfile")
file = File.open!("newfile")
Mari kita sekarang memahami perbedaan antara file File.open fungsi dan File.open!() fungsi.
Itu File.openfungsi selalu mengembalikan tupel. Jika file berhasil dibuka, ia mengembalikan nilai pertama dalam tupel sebagai:okdan nilai kedua adalah literal tipe io_device. Jika terjadi kesalahan, itu akan mengembalikan tupel dengan nilai pertama sebagai:error dan nilai kedua sebagai alasannya.
Itu File.open!() fungsi di sisi lain akan mengembalikan a io_devicejika file berhasil dibuka, maka akan menimbulkan error. CATATAN: Ini adalah pola yang diikuti di semua fungsi modul file yang akan kita diskusikan.
Kami juga dapat menentukan mode di mana kami ingin membuka file ini. Untuk membuka file sebagai read only dan dalam mode encoding utf-8, kami menggunakan kode berikut -
file = File.open!("newfile", [:read, :utf8])
Menulis ke File
Kami memiliki dua cara untuk menulis ke file. Mari kita lihat yang pertama menggunakan fungsi tulis dari modul File.
File.write("newfile", "Hello")
Tetapi ini tidak boleh digunakan jika Anda membuat beberapa penulisan ke file yang sama. Setiap kali fungsi ini dipanggil, deskriptor file dibuka dan proses baru muncul untuk menulis ke file. Jika Anda melakukan beberapa penulisan dalam satu putaran, buka file melaluiFile.opendan menuliskannya menggunakan metode dalam modul IO. Mari kita pertimbangkan contoh untuk memahami hal yang sama -
#Open the file in read, write and utf8 modes.
file = File.open!("newfile_2", [:read, :utf8, :write])
#Write to this "io_device" using standard IO functions
IO.puts(file, "Random text")
Anda dapat menggunakan metode modul IO lainnya seperti IO.write dan IO.binwrite untuk menulis ke file yang dibuka sebagai io_device.
Reading from a File
We have two ways to read from files. Let us see the first one using the read function from the File module.
IO.puts(File.read("newfile"))
When running this code, you should get a tuple with the first element as :ok and the second one as the contents of newfile
We can also use the File.read! function to just get the contents of the files returned to us.
Closing an Open File
Whenever you open a file using the File.open function, after you are done using it, you should close it using the File.close function −
File.close(file)
In Elixir, all code runs inside processes. Processes are isolated from each other, run concurrent to one another and communicate via message passing. Elixir’s processes should not be confused with operating system processes. Processes in Elixir are extremely lightweight in terms of memory and CPU (unlike threads in many other programming languages). Because of this, it is not uncommon to have tens or even hundreds of thousands of processes running simultaneously.
In this chapter, we will learn about the basic constructs for spawning new processes, as well as sending and receiving messages between different processes.
The Spawn Function
The easiest way to create a new process is to use the spawn function. The spawn accepts a function that will be run in the new process. For example −
pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)
When the above program is run, it produces the following result −
false
The return value of the spawn function is a PID. This is a unique identifier for the process and so if you run the code above your PID, it will be different. As you can see in this example, the process is dead when we check to see if it alive. This is because the process will exit as soon as it has finished running the given function.
As already mentioned, all Elixir codes run inside processes. If you run the self function you will see the PID for your current session −
pid = self
Process.alive?(pid)
When the above program is run, it produces following result −
true
Message Passing
We can send messages to a process with send and receive them with receive. Let us pass a message to the current process and receive it on the same.
send(self(), {:hello, "Hi people"})
receive do
{:hello, msg} -> IO.puts(msg)
{:another_case, msg} -> IO.puts("This one won't match!")
end
When the above program is run, it produces the following result −
Hi people
We sent a message to the current process using the send function and passed it to the PID of self. Then we handled the incoming message using the receive function.
When a message is sent to a process, the message is stored in the process mailbox. The receive block goes through the current process mailbox searching for a message that matches any of the given patterns. The receive block supports guards and many clauses, such as case.
If there is no message in the mailbox matching any of the patterns, the current process will wait until a matching message arrives. A timeout can also be specified. For example,
receive do
{:hello, msg} -> msg
after
1_000 -> "nothing after 1s"
end
When the above program is run, it produces the following result −
nothing after 1s
NOTE − A timeout of 0 can be given when you already expect the message to be in the mailbox.
Links
The most common form of spawning in Elixir is actually via spawn_link function. Before taking a look at an example with spawn_link, let us understand what happens when a process fails.
spawn fn -> raise "oops" end
When the above program is run, it produces the following error −
[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
:erlang.apply/2
It logged an error but the spawning process is still running. This is because processes are isolated. If we want the failure in one process to propagate to another one, we need to link them. This can be done with the spawn_link function. Let us consider an example to understand the same −
spawn_link fn -> raise "oops" end
When the above program is run, it produces the following error −
** (EXIT from #PID<0.41.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
If you are running this in iex shell then the shell handles this error and does not exit. But if you run by first making a script file and then using elixir <file-name>.exs, the parent process will also be brought down due to this failure.
Processes and links play an important role when building fault-tolerant systems. In Elixir applications, we often link our processes to supervisors which will detect when a process dies and start a new process in its place. This is only possible because processes are isolated and don’t share anything by default. And since processes are isolated, there is no way a failure in a process will crash or corrupt the state of another. While other languages will require us to catch/handle exceptions; in Elixir, we are actually fine with letting processes fail because we expect supervisors to properly restart our systems.
State
If you are building an application that requires state, for example, to keep your application configuration, or you need to parse a file and keep it in memory, where would you store it? Elixir's process functionality can come in handy when doing such things.
We can write processes that loop infinitely, maintain state, and send and receive messages. As an example, let us write a module that starts new processes that work as a key-value store in a file named kv.exs.
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
Note that the start_link function starts a new process that runs the loop function, starting with an empty map. The loop function then waits for messages and performs the appropriate action for each message. In the case of a :get message, it sends a message back to the caller and calls loop again, to wait for a new message. While the :put message actually invokes loop with a new version of the map, with the given key and value stored.
Let us now run the following −
iex kv.exs
Now you should be in your iex shell. To test out our module, try the following −
{:ok, pid} = KV.start_link
# pid now has the pid of our new process that is being
# used to get and store key value pairs
# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}
# Ask for the key :hello
send pid, {:get, :hello, self()}
# Print all the received messages on the current process.
flush()
When the above program is run, it produces the following result −
"Hello"
In this chapter, we are going to explore sigils, the mechanisms provided by the language for working with textual representations. Sigils start with the tilde (~) character which is followed by a letter (which identifies the sigil) and then a delimiter; optionally, modifiers can be added after the final delimiter.
Regex
Regexes in Elixir are sigils. We have seen their use in the String chapter. Let us again take an example to see how we can use regex in Elixir.
# A regular expression that matches strings which contain "foo" or
# "bar":
regex = ~r/foo|bar/
IO.puts("foo" =~ regex)
IO.puts("baz" =~ regex)
When the above program is run, it produces the following result −
true
false
Sigils support 8 different delimiters −
~r/hello/
~r|hello|
~r"hello"
~r'hello'
~r(hello)
~r[hello]
~r{hello}
~r<hello>
The reason behind supporting different delimiters is that different delimiters can be more suited for different sigils. For example, using parentheses for regular expressions may be a confusing choice as they can get mixed with the parentheses inside the regex. However, parentheses can be handy for other sigils, as we will see in the next section.
Elixir mendukung regex yang kompatibel dengan Perl dan juga mendukung pengubah. Anda dapat membaca lebih lanjut tentang penggunaan regex di sini .
String, daftar Char, dan daftar Word
Selain regex, Elixir memiliki 3 sigil bawaan lainnya. Mari kita lihat sigilsnya.
String
Sigil ~ s digunakan untuk menghasilkan string, seperti tanda kutip ganda. Sigil ~ s berguna, misalnya, ketika sebuah string berisi tanda kutip ganda dan tunggal -
new_string = ~s(this is a string with "double" quotes, not 'single' ones)
IO.puts(new_string)
Sigil ini menghasilkan string. Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
"this is a string with \"double\" quotes, not 'single' ones"
Daftar Karakter
The ~ c sigil digunakan untuk menghasilkan daftar karakter -
new_char_list = ~c(this is a char list containing 'single quotes')
IO.puts(new_char_list)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
this is a char list containing 'single quotes'
Daftar Kata
~ W sigil digunakan untuk menghasilkan daftar kata (kata hanyalah string biasa). Di dalam ~ w sigil, kata dipisahkan oleh spasi.
new_word_list = ~w(foo bar bat)
IO.puts(new_word_list)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
foobarbat
The ~ w sigil juga menerima c, s dan a pengubah (untuk daftar karakter, string dan atom, masing-masing), yang menentukan tipe data dari elemen dari daftar yang dihasilkan -
new_atom_list = ~w(foo bar bat)a
IO.puts(new_atom_list)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
[:foo, :bar, :bat]
Interpolasi dan Escaping di Sigils
Selain tanda huruf kecil, Elixir mendukung tanda huruf besar untuk menangani karakter yang keluar dan interpolasi. Meskipun ~ s dan ~ S akan mengembalikan string, yang pertama memungkinkan kode escape dan interpolasi sedangkan yang terakhir tidak. Mari kita pertimbangkan contoh untuk memahami ini -
~s(String with escape codes \x26 #{"inter" <> "polation"})
# "String with escape codes & interpolation"
~S(String without escape codes \x26 without #{interpolation})
# "String without escape codes \\x26 without \#{interpolation}"
Sigil Kustom
Kita dapat dengan mudah membuat sigil kustom kita sendiri. Dalam contoh ini, kita akan membuat sigil untuk mengubah string menjadi huruf besar.
defmodule CustomSigil do
def sigil_u(string, []), do: String.upcase(string)
end
import CustomSigil
IO.puts(~u/tutorials point/)
Ketika kami menjalankan kode di atas, itu menghasilkan hasil sebagai berikut -
TUTORIALS POINT
Pertama kita mendefinisikan modul yang disebut CustomSigil dan di dalam modul itu, kita membuat fungsi yang disebut sigil_u. Karena tidak ada ~ u sigil di ruang sigil yang ada, kami akan menggunakannya. _U menunjukkan bahwa kita ingin menggunakan u sebagai karakter setelah tilde. Definisi fungsi harus mengambil dua argumen, masukan dan daftar.
Pemahaman daftar adalah gula sintaksis untuk mengulang melalui enumerable di Elixir. Dalam bab ini kita akan menggunakan pemahaman untuk iterasi dan generasi.
Dasar
Ketika kita melihat modul Enum di bab enumerables, kita menemukan fungsi peta.
Enum.map(1..3, &(&1 * 2))
Dalam contoh ini, kami akan meneruskan fungsi sebagai argumen kedua. Setiap item dalam rentang akan diteruskan ke fungsi, dan kemudian daftar baru akan dikembalikan yang berisi nilai baru.
Pemetaan, pemfilteran, dan transformasi adalah tindakan yang sangat umum di Elixir sehingga ada cara yang sedikit berbeda untuk mencapai hasil yang sama seperti contoh sebelumnya -
for n <- 1..3, do: n * 2
Ketika kami menjalankan kode di atas, itu menghasilkan hasil sebagai berikut -
[2, 4, 6]
Contoh kedua adalah pemahaman, dan seperti yang mungkin Anda lihat, itu hanyalah gula sintaksis untuk apa yang juga dapat Anda capai jika Anda menggunakan Enum.mapfungsi. Namun, tidak ada manfaat nyata menggunakan pemahaman atas fungsi dari modul Enum dalam hal kinerja.
Pemahaman tidak terbatas pada daftar tetapi dapat digunakan dengan semua enumerable.
Saring
Anda dapat menganggap filter sebagai semacam penjaga pemahaman. Saat nilai yang difilter kembalifalse atau nilitu dikecualikan dari daftar akhir. Mari kita mengulang suatu rentang dan hanya mengkhawatirkan bilangan genap. Kami akan menggunakanis_even fungsi dari modul Integer untuk memeriksa apakah suatu nilai genap atau tidak.
import Integer
IO.puts(for x <- 1..10, is_even(x), do: x)
Ketika kode di atas dijalankan, menghasilkan hasil sebagai berikut -
[2, 4, 6, 8, 10]
Kami juga dapat menggunakan beberapa filter dalam pemahaman yang sama. Tambahkan filter lain yang Anda inginkan setelahis_even filter dipisahkan dengan koma.
: ke dalam Opsi
Dalam contoh di atas, semua pemahaman mengembalikan daftar sebagai hasilnya. Namun, hasil pemahaman dapat dimasukkan ke dalam struktur data yang berbeda dengan melewatkan:into pilihan untuk pemahaman.
Misalnya, a bitstring generator dapat digunakan dengan opsi: into untuk dengan mudah menghapus semua spasi dalam string -
IO.puts(for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>)
Ketika kode di atas dijalankan, menghasilkan hasil sebagai berikut -
helloworld
Kode di atas menghapus semua spasi dari string yang digunakan c != ?\s filter dan kemudian menggunakan opsi: into, ini menempatkan semua karakter yang dikembalikan dalam sebuah string.
Elixir adalah bahasa yang diketik secara dinamis, jadi semua jenis di Elixir disimpulkan oleh runtime. Meskipun demikian, Elixir hadir dengan typepecs, yang merupakan notasi yang digunakan untukdeclaring custom data types and declaring typed function signatures (specifications).
Spesifikasi Fungsi (spesifikasi)
Secara default, Elixir menyediakan beberapa tipe dasar, seperti integer atau pid, dan juga tipe kompleks: misalnya, file roundfunction, yang membulatkan float ke integer terdekatnya, mengambil angka sebagai argumen (integer atau float) dan mengembalikan integer. Dalam dokumentasi terkait , tanda tangan yang diketik bulat ditulis sebagai -
round(number) :: integer
Deskripsi di atas menyiratkan bahwa fungsi di sebelah kiri mengambil argumen apa yang ditentukan dalam tanda kurung dan mengembalikan apa yang ada di sebelah kanan ::, yaitu, Integer. Spesifikasi fungsi ditulis dengan@specdirektif, ditempatkan tepat sebelum definisi fungsi. Fungsi bulat dapat ditulis sebagai -
@spec round(number) :: integer
def round(number), do: # Function implementation
...
Jenispec juga mendukung jenis yang kompleks, misalnya, jika Anda ingin mengembalikan daftar bilangan bulat, Anda dapat menggunakan [Integer]
Jenis Kustom
Meskipun Elixir menyediakan banyak jenis bawaan yang berguna, akan lebih mudah untuk menentukan jenis khusus jika perlu. Ini dapat dilakukan saat mendefinisikan modul melalui direktif @type. Mari kita pertimbangkan contoh untuk memahami hal yang sama -
defmodule FunnyCalculator do
@type number_with_joke :: {number, String.t}
@spec add(number, number) :: number_with_joke
def add(x, y), do: {x + y, "You need a calculator to do that?"}
@spec multiply(number, number) :: number_with_joke
def multiply(x, y), do: {x * y, "It is like addition on steroids."}
end
{result, comment} = FunnyCalculator.add(10, 20)
IO.puts(result)
IO.puts(comment)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
30
You need a calculator to do that?
NOTE - Jenis kustom yang ditentukan melalui @type diekspor dan tersedia di luar modul tempat mereka didefinisikan. Jika Anda ingin menjaga jenis kustom tetap pribadi, Anda dapat menggunakan @typep direktif, bukan @type.
Behaviors di Elixir (dan Erlang) adalah cara untuk memisahkan dan mengabstraksi bagian generik dari sebuah komponen (yang menjadi modul perilaku) dari bagian tertentu (yang menjadi modul callback). Perilaku menyediakan cara untuk -
- Tentukan sekumpulan fungsi yang harus diimplementasikan oleh modul.
- Pastikan bahwa modul mengimplementasikan semua fungsi di set itu.
Jika perlu, Anda dapat memikirkan perilaku seperti antarmuka dalam bahasa berorientasi objek seperti Java: satu set tanda tangan fungsi yang harus diimplementasikan modul.
Mendefinisikan Perilaku
Mari kita pertimbangkan contoh untuk membuat perilaku kita sendiri dan kemudian menggunakan perilaku umum ini untuk membuat modul. Kami akan mendefinisikan perilaku yang menyapa orang dan selamat tinggal dalam berbagai bahasa.
defmodule GreetBehaviour do
@callback say_hello(name :: string) :: nil
@callback say_bye(name :: string) :: nil
end
Itu @callbackdirektif digunakan untuk membuat daftar fungsi yang mengadopsi modul perlu didefinisikan. Ini juga menentukan tidak. argumen, jenisnya, dan nilai kembaliannya.
Mengadopsi Perilaku
Kami telah berhasil mendefinisikan sebuah perilaku. Sekarang kita akan mengadopsi dan menerapkannya dalam beberapa modul. Mari kita buat dua modul yang menerapkan perilaku ini dalam bahasa Inggris dan Spanyol.
defmodule GreetBehaviour do
@callback say_hello(name :: string) :: nil
@callback say_bye(name :: string) :: nil
end
defmodule EnglishGreet do
@behaviour GreetBehaviour
def say_hello(name), do: IO.puts("Hello " <> name)
def say_bye(name), do: IO.puts("Goodbye, " <> name)
end
defmodule SpanishGreet do
@behaviour GreetBehaviour
def say_hello(name), do: IO.puts("Hola " <> name)
def say_bye(name), do: IO.puts("Adios " <> name)
end
EnglishGreet.say_hello("Ayush")
EnglishGreet.say_bye("Ayush")
SpanishGreet.say_hello("Ayush")
SpanishGreet.say_bye("Ayush")
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Hello Ayush
Goodbye, Ayush
Hola Ayush
Adios Ayush
Seperti yang telah Anda lihat, kami mengadopsi perilaku menggunakan @behaviourdirektif dalam modul. Kita harus mendefinisikan semua fungsi yang diimplementasikan dalam perilaku untuk semua modul anak . Ini kira-kira dapat dianggap setara dengan antarmuka dalam bahasa OOP.
Elixir memiliki tiga mekanisme kesalahan: kesalahan, lemparan dan keluar. Mari kita bahas setiap mekanisme secara mendetail.
Kesalahan
Kesalahan (atau pengecualian) digunakan saat hal-hal luar biasa terjadi dalam kode. Kesalahan sampel dapat diambil dengan mencoba menambahkan angka ke dalam string -
IO.puts(1 + "Hello")
Ketika program di atas dijalankan, itu menghasilkan kesalahan berikut -
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(1, "Hello")
Ini adalah contoh kesalahan bawaan.
Meningkatkan Kesalahan
Kita dapat raisekesalahan menggunakan fungsi kenaikan. Mari kita pertimbangkan contoh untuk memahami hal yang sama -
#Runtime Error with just a message
raise "oops" # ** (RuntimeError) oops
Kesalahan lainnya dapat dimunculkan dengan menaikkan / 2 melewati nama kesalahan dan daftar argumen kata kunci
#Other error type with a message
raise ArgumentError, message: "invalid argument foo"
Anda juga dapat menentukan kesalahan Anda sendiri dan meningkatkannya. Perhatikan contoh berikut -
defmodule MyError do
defexception message: "default message"
end
raise MyError # Raises error with default message
raise MyError, message: "custom message" # Raises error with custom message
Kesalahan Penyelamatan
Kami tidak ingin program kami berhenti secara tiba-tiba tetapi kesalahan harus ditangani dengan hati-hati. Untuk ini kami menggunakan penanganan kesalahan. Kitarescue kesalahan menggunakan try/rescuemembangun. Mari kita pertimbangkan contoh berikut untuk memahami hal yang sama -
err = try do
raise "oops"
rescue
e in RuntimeError -> e
end
IO.puts(err.message)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
oops
Kami telah menangani kesalahan dalam pernyataan penyelamatan menggunakan pencocokan pola. Jika kami tidak memiliki penggunaan kesalahan, dan hanya ingin menggunakannya untuk tujuan identifikasi, kami juga dapat menggunakan formulir -
err = try do
1 + "Hello"
rescue
RuntimeError -> "You've got a runtime error!"
ArithmeticError -> "You've got a Argument error!"
end
IO.puts(err)
Saat menjalankan program di atas, ini menghasilkan hasil sebagai berikut -
You've got a Argument error!
NOTE- Sebagian besar fungsi di pustaka standar Elixir diimplementasikan dua kali, sekali mengembalikan tupel dan waktu lainnya meningkatkan kesalahan. Misalnya, fileFile.read dan File.read!fungsi. Yang pertama mengembalikan tupel jika file berhasil dibaca dan jika terjadi kesalahan, tupel ini digunakan untuk memberikan alasan kesalahan. Yang kedua memunculkan kesalahan jika terjadi kesalahan.
Jika kita menggunakan pendekatan fungsi pertama, maka kita perlu menggunakan kasus untuk pola yang cocok dengan kesalahan dan mengambil tindakan sesuai dengan itu. Dalam kasus kedua, kami menggunakan pendekatan coba penyelamatan untuk kode rawan kesalahan dan menangani kesalahan yang sesuai.
Melempar
Di Elixir, sebuah nilai bisa dilempar dan kemudian ditangkap. Throw dan Catch disediakan untuk situasi di mana tidak mungkin mengambil nilai kecuali dengan menggunakan throw and catch.
Instance ini sangat tidak umum dalam praktiknya kecuali saat berinteraksi dengan perpustakaan. Misalnya, sekarang mari kita asumsikan bahwa modul Enum tidak menyediakan API apa pun untuk menemukan nilai dan bahwa kita perlu menemukan kelipatan pertama dari 13 dalam daftar angka -
val = try do
Enum.each 20..100, fn(x) ->
if rem(x, 13) == 0, do: throw(x)
end
"Got nothing"
catch
x -> "Got #{x}"
end
IO.puts(val)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
Got 26
Keluar
Ketika suatu proses mati karena "penyebab alami" (misalnya, pengecualian yang tidak tertangani), ia mengirimkan sinyal keluar. Suatu proses juga bisa mati dengan mengirimkan sinyal keluar secara eksplisit. Mari kita perhatikan contoh berikut -
spawn_link fn -> exit(1) end
Pada contoh di atas, proses yang terhubung mati dengan mengirimkan sinyal keluar dengan nilai 1. Perhatikan bahwa keluar juga bisa “ditangkap” menggunakan coba / tangkap. Misalnya -
val = try do
exit "I am exiting"
catch
:exit, _ -> "not really"
end
IO.puts(val)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
not really
Setelah
Terkadang perlu untuk memastikan bahwa sumber daya dibersihkan setelah beberapa tindakan yang berpotensi menimbulkan kesalahan. Konstruksi coba / setelah memungkinkan Anda melakukan itu. Misalnya, kita dapat membuka file dan menggunakan klausa setelah untuk menutupnya – bahkan jika terjadi kesalahan.
{:ok, file} = File.open "sample", [:utf8, :write]
try do
IO.write file, "olá"
raise "oops, something went wrong"
after
File.close(file)
end
Ketika kami menjalankan program ini, itu akan memberi kami kesalahan. Tetapiafter pernyataan akan memastikan bahwa deskriptor file ditutup saat kejadian seperti itu.
Makro adalah salah satu fitur Elixir yang paling canggih dan kuat. Seperti semua fitur lanjutan bahasa apa pun, makro harus digunakan dengan hemat. Mereka memungkinkan untuk melakukan transformasi kode yang kuat dalam waktu kompilasi. Sekarang kita akan memahami apa itu makro dan bagaimana menggunakannya secara singkat.
Kutipan
Sebelum kita mulai berbicara tentang makro, mari kita lihat internal Elixir. Program Elixir dapat diwakili oleh struktur datanya sendiri. Blok bangunan program Elixir adalah tupel dengan tiga elemen. Misalnya, panggilan fungsi sum (1, 2, 3) direpresentasikan secara internal sebagai -
{:sum, [], [1, 2, 3]}
Elemen pertama adalah nama fungsi, yang kedua adalah daftar kata kunci yang berisi metadata dan yang ketiga adalah daftar argumen. Anda bisa mendapatkan ini sebagai output di shell iex jika Anda menulis yang berikut -
quote do: sum(1, 2, 3)
Operator juga direpresentasikan sebagai tupel tersebut. Variabel juga direpresentasikan menggunakan triplet semacam itu, kecuali bahwa elemen terakhir adalah atom, bukan daftar. Saat mengutip ekspresi yang lebih kompleks, kita dapat melihat bahwa kode tersebut direpresentasikan dalam tupel seperti itu, yang sering bertumpuk di dalam satu sama lain dalam struktur yang menyerupai pohon. Banyak bahasa akan menyebut representasi seperti itu sebagaiAbstract Syntax Tree (AST). Elixir menyebut ekspresi yang dikutip ini.
Batalkan kutipan
Sekarang setelah kita dapat mengambil struktur internal kode kita, bagaimana kita memodifikasinya? Untuk memasukkan kode atau nilai baru, kami menggunakanunquote. Saat kami menghapus kutipan, ekspresi itu akan dievaluasi dan dimasukkan ke dalam AST. Mari kita pertimbangkan contoh (di iex shell) untuk memahami konsep -
num = 25
quote do: sum(15, num)
quote do: sum(15, unquote(num))
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
{:sum, [], [15, {:num, [], Elixir}]}
{:sum, [], [15, 25]}
Dalam contoh ekspresi kutipan, ia tidak secara otomatis mengganti num dengan 25. Kita perlu menghapus variabel ini jika kita ingin memodifikasi AST.
Makro
Jadi sekarang kita sudah familiar dengan quote dan unquote, kita bisa menjelajahi metaprogramming di Elixir menggunakan macro.
Dalam istilah yang paling sederhana, makro adalah fungsi khusus yang dirancang untuk mengembalikan ekspresi yang dikutip yang akan dimasukkan ke dalam kode aplikasi kita. Bayangkan makro diganti dengan ekspresi yang dikutip daripada dipanggil seperti fungsi. Dengan makro kita memiliki semua yang diperlukan untuk memperluas Elixir dan secara dinamis menambahkan kode ke aplikasi kita
Mari kita terapkan kecuali sebagai makro. Kami akan mulai dengan mendefinisikan makro menggunakandefmacromakro. Ingatlah bahwa makro kita perlu mengembalikan ekspresi yang dikutip.
defmodule OurMacro do
defmacro unless(expr, do: block) do
quote do
if !unquote(expr), do: unquote(block)
end
end
end
require OurMacro
OurMacro.unless true, do: IO.puts "True Expression"
OurMacro.unless false, do: IO.puts "False expression"
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
False expression
Apa yang terjadi di sini adalah kode kita digantikan oleh kode kutipan yang dikembalikan oleh makro kecuali . Kami telah menghapus ekspresi untuk mengevaluasinya dalam konteks saat ini dan juga menghapus tanda kutip blok do untuk mengeksekusinya dalam konteksnya. Contoh ini menunjukkan kepada kita metaprogramming menggunakan makro di elixir.
Makro dapat digunakan dalam tugas yang jauh lebih kompleks tetapi harus digunakan dengan hemat. Ini karena metaprogramming secara umum dianggap sebagai praktik yang buruk dan harus digunakan hanya jika diperlukan.
Elixir memberikan interoperabilitas yang sangat baik dengan perpustakaan Erlang. Mari kita bahas beberapa perpustakaan secara singkat.
Modul Biner
Modul Elixir String bawaan menangani biner yang dikodekan UTF-8. Modul biner berguna saat Anda berurusan dengan data biner yang belum tentu dikodekan UTF-8. Mari kita pertimbangkan contoh untuk lebih memahami modul Biner -
# UTF-8
IO.puts(String.to_char_list("Ø"))
# binary
IO.puts(:binary.bin_to_list "Ø")
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
[216]
[195, 152]
Contoh di atas menunjukkan perbedaannya; modul String mengembalikan titik kode UTF-8, sedangkan: biner berurusan dengan byte data mentah.
Modul Crypto
Modul kripto berisi fungsi hashing, tanda tangan digital, enkripsi, dan lainnya. Modul ini bukan bagian dari pustaka standar Erlang, tetapi disertakan dengan distribusi Erlang. Ini berarti Anda harus mencantumkan: crypto dalam daftar aplikasi proyek Anda setiap kali Anda menggunakannya. Mari kita lihat contoh menggunakan modul crypto -
IO.puts(Base.encode16(:crypto.hash(:sha256, "Elixir")))
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB
Modul Digraph
Modul digraf berisi fungsi untuk menangani grafik terarah yang dibangun dari simpul dan tepi. Setelah membuat grafik, algoritme di sana akan membantu menemukan, misalnya, jalur terpendek antara dua simpul, atau loop pada grafik. Perhatikan bahwa fungsinyain :digraph mengubah struktur grafik secara tidak langsung sebagai efek samping, sekaligus mengembalikan simpul atau tepi yang ditambahkan.
digraph = :digraph.new()
coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
[v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
:digraph.add_edge(digraph, v0, v1)
:digraph.add_edge(digraph, v1, v2)
for point <- :digraph.get_short_path(digraph, v0, v2) do
{x, y} = point
IO.puts("#{x}, #{y}")
end
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
0.0, 0.0
1.0, 0.0
1.0, 1.0
Modul Matematika
Modul matematika berisi operasi matematika umum yang mencakup fungsi trigonometri, eksponensial, dan logaritmik. Mari kita perhatikan contoh berikut untuk memahami cara kerja modul Matematika -
# Value of pi
IO.puts(:math.pi())
# Logarithm
IO.puts(:math.log(7.694785265142018e23))
# Exponentiation
IO.puts(:math.exp(55.0))
#...
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
3.141592653589793
55.0
7.694785265142018e23
Modul Antrian
Antrian adalah struktur data yang mengimplementasikan antrian FIFO (first-in first-out) secara efisien. Contoh berikut menunjukkan cara kerja modul Queue -
q = :queue.new
q = :queue.in("A", q)
q = :queue.in("B", q)
{{:value, val}, q} = :queue.out(q)
IO.puts(val)
{{:value, val}, q} = :queue.out(q)
IO.puts(val)
Ketika program di atas dijalankan, menghasilkan hasil sebagai berikut -
A
B