1. Langkah pertama
2. Gabungkan fungsinya
3. Penggunaan parsial (kari)
4. Pemrograman deklaratif
5. Notasi klasik
6. Kekekalan dan objek
7. Kekekalan dan array
8. Lensa
9. Kesimpulan
Posting ini adalah bagian keenam dari serangkaian artikel tentang pemrograman fungsional yang disebut Ramda Style Thinking.
Pada bagian kelima, kita berbicara tentang fungsi menulis dalam gaya notasi pointless, di mana argumen utama dengan data untuk fungsi kita tidak ditentukan secara eksplisit.
Pada saat itu, kami tidak dapat menulis ulang semua fungsi kami dengan gaya tanpa bit, karena kami tidak memiliki beberapa alat yang diperlukan untuk ini. Sudah waktunya untuk mempelajarinya.
Membaca properti objek
Mari kita lihat kembali contoh definisi orang yang memiliki hak pilih, yang kami periksa di bagian kelima :
const wasBornInCountry = person => person.birthCountry === OUR_COUNTRY const wasNaturalized = person => Boolean(person.naturalizationDate) const isOver18 = person => person.age >= 18 const isCitizen = either(wasBornInCountry, wasNaturalized) const isEligibleToVote = both(isOver18, isCitizen)
Seperti yang Anda lihat, kami membuat isCitizen
dan isEligibleToVote
, tetapi kami tidak dapat melakukan ini dengan tiga fungsi pertama.
Seperti yang kita pelajari di bagian keempat , kita dapat membuat fungsi kita lebih deklaratif melalui penggunaan equals dan gte . Mari kita mulai dengan ini:
const wasBornInCountry = person => equals(person.birthCountry, OUR_COUNTRY) const wasNaturalized = person => Boolean(person.naturalizationDate) const isOver18 = person => gte(person.age, 18)
Untuk membuat fungsi-fungsi ini tidak berguna, kita perlu cara untuk membangun fungsi sehingga kita menerapkan variabel person
di akhir ekspresi. Masalahnya adalah kita perlu mengakses properti person
, sekarang kita tahu satu-satunya cara untuk melakukan ini - dan itu sangat penting.
penyangga
Untungnya, Ramda sekali lagi datang membantu kami. Ini menyediakan fungsi penyangga untuk mengakses properti objek.
Dengan menggunakan prop
, kita dapat menulis ulang person.birthCountry
ke prop('birthCountry', person)
. Mari kita lakukan:
const wasBornInCountry = person => equals(prop('birthCountry', person), OUR_COUNTRY) const wasNaturalized = person => Boolean(prop('naturalizationDate', person)) const isOver18 = person => gte(prop('age', person), 18)
Wow, sekarang terlihat jauh lebih buruk. Tapi mari kita lanjutkan refactoring kami. Mari kita ubah urutan argumen yang kita berikan menjadi equals
sehingga prop
menjadi yang terakhir. equals
bekerja dengan cara yang persis sama secara terbalik, jadi kami tidak akan merusak apa pun:
const wasBornInCountry = person => equals(OUR_COUNTRY, prop('birthCountry', person)) const wasNaturalized = person => Boolean(prop('naturalizationDate', person)) const isOver18 = person => gte(prop('age', person), 18)
Selanjutnya, mari kita gunakan currying, properti alami equals
dan gte
, untuk membuat fungsi baru yang mana hasil dari panggilan prop
akan berlaku:
const wasBornInCountry = person => equals(OUR_COUNTRY)(prop('birthCountry', person)) const wasNaturalized = person => Boolean(prop('naturalizationDate', person)) const isOver18 = person => gte(__, 18)(prop('age', person))
Ini masih terlihat sebagai opsi terburuk, tetapi mari kita lanjutkan. Mari manfaatkan kari lagi untuk semua panggilan prop
:
const wasBornInCountry = person => equals(OUR_COUNTRY)(prop('birthCountry')(person)) const wasNaturalized = person => Boolean(prop('naturalizationDate')(person)) const isOver18 = person => gte(__, 18)(prop('age')(person))
Sekali lagi, entah bagaimana tidak terlalu. Tapi sekarang kita melihat pola yang umum. Semua fungsi kami memiliki gambar yang sama f(g(person))
, dan seperti yang kita ketahui dari bagian kedua , ini setara dengan compose(f, g)(person)
.
Mari kita terapkan keunggulan ini pada kode kita:
const wasBornInCountry = person => compose(equals(OUR_COUNTRY), prop('birthCountry'))(person) const wasNaturalized = person => compose(Boolean, prop('naturalizationDate'))(person) const isOver18 = person => compose(gte(__, 18), prop('age'))(person)
Sekarang kita punya sesuatu. Semua fungsi kami terlihat seperti person => f(person)
. Dan kita sudah tahu dari bagian kelima bahwa kita dapat membuat fungsi-fungsi ini sia-sia.
const wasBornInCountry = compose(equals(OUR_COUNTRY), prop('birthCountry')) const wasNaturalized = compose(Boolean, prop('naturalizationDate')) const isOver18 = compose(gte(__, 18), prop('age'))
Ketika kami mulai, tidak jelas bahwa metode kami melakukan dua hal. Mereka beralih ke properti objek dan menyiapkan beberapa operasi dengan nilainya. Refactoring ini menjadi gaya yang tidak berguna menjadikannya sangat eksplisit.
Mari kita lihat beberapa alat lain yang disediakan Ramda untuk bekerja dengan objek.
pilih
Di mana prop
membaca satu properti objek dan mengembalikan nilainya, pilih membaca banyak properti dari objek dan mengembalikan objek baru hanya dengan mereka.
Misalnya, jika kita hanya perlu nama dan tahun orang, kita dapat menggunakan pick(['name','age'], person)
.
telah
Jika kita hanya ingin tahu bahwa objek kita memiliki properti, tanpa membaca nilainya, kita dapat menggunakan fungsi has untuk memeriksa propertinya, serta hasIn untuk memeriksa rantai prototipe: has('name', person)
.
jalan
Ketika prop
properti objek, path masuk lebih dalam ke objek bersarang. Sebagai contoh, kami ingin menarik kode pos dari struktur yang lebih dalam: path(['address','zipCode'], person)
.
Perhatikan bahwa path
lebih memaafkan daripada prop
. path
akan kembali undefined
jika ada sesuatu di jalan (termasuk argumen asli) adalah null
atau undefined
, sementara prop
akan menyebabkan kesalahan dalam situasi seperti itu.
propOr / pathOr
propOr dan pathOr mirip dengan prop
dan path
dikombinasikan dengan defaultTo
. Mereka memberi Anda kemampuan untuk menentukan nilai default untuk properti atau jalur yang tidak dapat ditemukan dalam objek yang sedang dipelajari.
Misalnya, kami dapat menyediakan placeholder ketika kami tidak tahu nama orang tersebut: propOr('<Unnamed>, 'name', person)
. Perhatikan bahwa tidak seperti prop
, propOr
tidak akan menyebabkan kesalahan jika person
null
atau undefined
; sebagai gantinya, itu akan mengembalikan nilai default.
kunci / nilai
kunci mengembalikan array yang berisi semua nama dari semua properti yang dikenal dari objek. nilai akan mengembalikan nilai properti ini. Fungsi-fungsi ini dapat berguna ketika dikombinasikan dengan fungsi iterasi untuk koleksi, yang kami pelajari di bagian pertama .
Tambah, perbarui, dan hapus properti
Sekarang kita memiliki banyak alat untuk membaca dari objek dalam gaya deklaratif, tetapi bagaimana dengan membuat perubahan?
Karena kekekalan penting bagi kami, kami tidak ingin memodifikasi objek secara langsung. Sebagai gantinya, kami ingin mengembalikan objek baru yang telah berubah seperti yang kami inginkan.
Sekali lagi, Ramda memberi kita banyak manfaat.
assoc / assocPath
Saat kami memprogram dengan gaya imperatif, kami dapat mengatur atau mengubah nama orang melalui operator penugasan: person.name = 'New name'
.
Dalam dunia kita yang fungsional dan tidak berubah, kita dapat menggunakan assoc : const updatedPerson = assoc('name', 'newName', person)
.
assoc
mengembalikan objek baru dengan nilai properti yang ditambahkan atau diperbarui, meninggalkan objek asli tidak berubah.
Kami juga memiliki assocPath kami untuk memperbarui properti terlampir: const updatedPerson = assocPath(['address', 'zipCode'], '97504', person)
.
dissoc / dissocPath / menghilangkan
Bagaimana dengan menghapus properti? Secara imperatif, kami mungkin ingin mengatakan delete person.age
. Dalam Ramda, kita akan menggunakan dissoc : `const updatedPerson = dissoc ('age', person)
dissocPath hampir sama, tetapi bekerja pada struktur objek yang lebih dalam: dissocPath(['address', 'zipCode'], person)
.
Dan kami juga memiliki omit , yang dapat menghapus beberapa properti sekaligus: const updatedPerson = omit(['age', 'birthCountry'], person)
.
Harap dicatat bahwa pick
dan omit
sedikit mirip dan saling melengkapi dengan sangat baik. Mereka sangat nyaman untuk masuk daftar putih (hanya menyimpan satu set properti tertentu menggunakan pick
) dan daftar hitam (menyingkirkan properti tertentu melalui penggunaan omit
).
Sekarang kita cukup tahu untuk bekerja dengan objek dalam gaya deklaratif dan abadi. Mari kita menulis fungsi celebrateBirthday
yang memperbarui usia orang tersebut pada hari ulang tahunnya.
const nextAge = compose(inc, prop('age')) const celebrateBirthday = person => assoc('age', nextAge(person), person)
Ini adalah pola yang sangat umum. Alih-alih memperbarui properti dengan nilai baru, kami benar-benar ingin mengubah nilai dengan menerapkan fungsi ke nilai lama, seperti yang kami lakukan di sini.
Saya tidak tahu cara yang baik untuk menulis ini dengan duplikasi lebih sedikit dan dengan gaya yang tidak terlalu ketat, memiliki alat-alat yang kami pelajari sebelumnya.
Ramda sekali lagi menyelamatkan kita dengan fungsi yang berkembang . evolve
menerima sebuah objek dan memungkinkan Anda menentukan fungsi transformasi untuk properti yang ingin kami ubah. Mari kita ulangi celebrateBirthday
menggunakan evolve
:
const celebrateBirthday = evolve({ age: inc })
Kode ini mengatakan bahwa kami akan mengonversi objek yang ditentukan (yang tidak ditampilkan karena gaya brutal) dengan membuat objek baru dengan properti dan nilai yang sama, tetapi properti age
akan diperoleh dengan menerapkan inc
pada nilai asli properti age
.
evolve
dapat mengubah banyak properti sekaligus, dan bahkan pada beberapa level sarang. Transformasi objek dapat memiliki gambar yang sama dengan objek yang bisa berubah, dan evolve
akan melewati antara struktur secara rekursif, menggunakan fungsi transformasi dalam bentuk yang ditentukan.
Perhatikan bahwa evolve
tidak menambahkan properti baru; jika Anda menentukan transformasi untuk properti yang tidak terjadi pada objek yang sedang diproses, evolve
hanya evolve
mengabaikannya.
Saya menemukan bahwa evolve
dengan cepat menjadi pekerja keras dalam aplikasi saya.
Gabungkan Objek
Terkadang Anda perlu menggabungkan dua objek menjadi satu. Kasus khas adalah ketika Anda memiliki fungsi yang mengambil opsi bernama, dan Anda ingin menggabungkannya dengan opsi default. Ramda menyediakan fungsi gabungan untuk tujuan ini.
function f(a, b, options = {}) { const defaultOptions = { value: 42, local: true } const finalOptions = merge(defaultOptions, options) }
merge
mengembalikan objek baru yang berisi semua properti dan nilai dari kedua objek. Jika kedua objek memiliki properti yang sama, maka nilai argumen kedua akan diperoleh.
Kehadiran aturan ini dengan argumen kedua yang menang membuatnya bermakna untuk menggunakan merge
sebagai alat mandiri, tetapi kurang bermakna dalam situasi konveyor. Dalam hal ini, Anda sering perlu menyiapkan serangkaian transformasi untuk suatu objek, dan salah satu transformasi tersebut adalah penyatuan beberapa nilai properti baru. Dalam hal ini, Anda ingin argumen pertama menang, bukan argumen kedua.
Mencoba menggunakan merge(newValues)
di pipeline tidak akan memberikan apa yang ingin kami dapatkan.
Untuk situasi ini, saya biasanya membuat utilitas sendiri bernama reverseMerge
. Itu dapat ditulis sebagai const reverseMerge = flip(merge)
. Panggilan flip
menukar dua argumen pertama dari fungsi yang berlaku padanya.
merge
melakukan merge
permukaan. Jika objek, ketika digabungkan, memiliki properti yang nilainya adalah sebuah sub-objek, maka sub-objek ini tidak bergabung. Ramda saat ini tidak memiliki kemampuan penggabungan yang dalam ( Artikel asli yang saya terjemahkan sudah memiliki informasi yang ketinggalan zaman tentang topik ini. Hari ini Ramda memiliki fungsi seperti mergeDeepLeft , mergeDeepRight untuk objek penggabungan yang dalam secara rekursif, dan metode lain untuk penggabungan ).
Perhatikan bahwa merge
hanya menerima dua argumen. Jika Anda ingin menggabungkan banyak objek menjadi satu, Anda bisa menggunakan mergeAll , yang membutuhkan array objek untuk digabungkan.
Kesimpulan
Hari ini kami memiliki seperangkat alat yang luar biasa untuk bekerja dengan objek dalam gaya deklaratif dan tidak berubah. Sekarang kita dapat membaca, menambah, memperbarui, menghapus dan mengubah properti di objek tanpa mengubah objek aslinya. Dan kita dapat melakukan semua hal ini dengan gaya yang membuatnya mudah untuk menggabungkan fungsi satu sama lain.
Selanjutnya
Sekarang kita dapat bekerja dengan objek dalam gaya yang tidak berubah, tetapi bagaimana dengan array? "Kekebalan dan susunan" akan memberi tahu kita apa yang harus dilakukan dengan mereka.