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