
Saya ingin memperkenalkan konsep
Pemrograman Fungsional kepada pemula dengan cara paling sederhana, menyoroti beberapa kelebihannya dari banyak yang lain yang benar-benar akan membuat kode lebih mudah dibaca dan ekspresif. Saya mengambil beberapa demo menarik untuk Anda yang berada di
Playground
on
Github .
Pemrograman Fungsional: Definisi
Pertama-tama,
Pemrograman Fungsional bukan bahasa atau sintaksis, tetapi kemungkinan besar cara untuk memecahkan masalah dengan memecah proses kompleks menjadi yang lebih sederhana dan komposisi mereka selanjutnya. Seperti namanya, "
Pemrograman Fungsional, " unit komposisi untuk pendekatan ini adalah
fungsi ; dan tujuan dari
fungsi tersebut adalah untuk menghindari perubahan status atau nilai di luar
scope)
.
Di
Swift
World, ada semua kondisi untuk ini, karena
fungsi di sini adalah sebagai peserta penuh dalam proses pemrograman sebagai
objek, dan masalah
mutation
diselesaikan pada tingkat konsep JENIS
value
(struktur struktur dan
enum
enumerasi) yang membantu mengelola mutabilitas (
mutation
) dan jelas mengomunikasikan bagaimana dan kapan ini bisa terjadi.
Namun,
Swift
tidak sepenuhnya memahami bahasa
pemrograman Fungsional , ia tidak memaksa Anda untuk
pemrograman Fungsional , meskipun
Swift
mengakui keunggulan pendekatan
Fungsional dan menemukan cara untuk menanamkannya.
Dalam artikel ini, kami akan fokus pada penggunaan elemen
bawaan dari
Pemrograman Fungsional di
Swift
(yaitu, "di luar kotak") dan memahami bagaimana Anda dapat menggunakannya dengan nyaman dalam aplikasi Anda.
Pendekatan Imperatif dan Fungsional: Perbandingan
Untuk mengevaluasi Pendekatan
Fungsional , mari kita bandingkan solusi untuk beberapa masalah sederhana dengan dua cara berbeda. Solusi pertama adalah "
Imperatif, " di mana kode mengubah keadaan di dalam program.
Perhatikan bahwa kita memanipulasi nilai-nilai di dalam array yang dapat diubah yang bernama
numbers
, dan kemudian mencetaknya ke konsol. Melihat kode ini, cobalah untuk menjawab pertanyaan-pertanyaan berikut yang akan kita bahas dalam waktu dekat:
- Apa yang ingin Anda capai dengan kode Anda?
- Apa yang terjadi jika
thread
lain mencoba mengakses array numbers
saat kode Anda berjalan? - Apa yang terjadi jika Anda ingin memiliki akses ke nilai asli dalam array
numbers
? - Seberapa andal kode ini dapat diuji?
Sekarang mari kita lihat pendekatan "
Fungsional " alternatif:
Dalam potongan kode ini, kita mendapatkan hasil yang sama pada konsol, mendekati solusi untuk masalah dengan cara yang sangat berbeda. Perhatikan bahwa kali ini array
numbers
kita tidak dapat diubah berkat kata kunci
let
. Kami telah memindahkan proses mengalikan angka dari array
numbers
ke metode
timesTen()
, yang terletak di ekstensi
extension
Array
. Kami masih menggunakan
for
dan memodifikasi variabel yang disebut
output
, tetapi
scope
variabel ini hanya dibatasi oleh metode ini. Demikian pula, argumen input kami diteruskan ke metode
timesTen()
berdasarkan nilai (
by value
), yang memiliki cakupan yang sama dengan output variabel. Metode
timesTen()
dipanggil, dan kita dapat mencetak pada konsol array
numbers
asli dan hasil array
result
.
Mari kita kembali ke 4 pertanyaan kita.
1. Apa yang ingin Anda capai dengan kode Anda?Dalam contoh kami, kami melakukan tugas yang sangat sederhana dengan mengalikan angka dalam array
numbers
dengan
10
.
Dengan pendekatan
imperatif , untuk mendapatkan output, Anda harus berpikir seperti komputer, mengikuti instruksi dalam
for
loop. Dalam hal ini, kode menunjukkan
Anda mencapai hasil. Dengan Pendekatan
Fungsional , "
" "dibungkus" dalam metode
timesTen()
. Asalkan metode ini diterapkan di tempat lain, Anda benar-benar hanya dapat melihat ekspresi
numbers.timesTen()
. Kode semacam itu dengan jelas menunjukkan
dicapai oleh kode ini, dan bukan
tugas tersebut diselesaikan. Ini disebut
Pemrograman Deklaratif , dan mudah ditebak mengapa pendekatan semacam itu menarik. Pendekatan
imperatif membuat pengembang memahami
kode bekerja untuk menentukan
harus ia lakukan. Pendekatan
fungsional dibandingkan dengan pendekatan
Imperatif jauh lebih "ekspresif" dan memberikan pengembang kesempatan mewah untuk hanya berasumsi bahwa metode melakukan apa yang diklaimnya harus dilakukan! (Jelas, asumsi ini hanya berlaku untuk kode pra-diverifikasi).
2. Apa yang terjadi jika thread
lain mencoba mengakses array numbers
saat kode Anda berjalan?Contoh-contoh yang disajikan di atas ada dalam ruang yang sepenuhnya terisolasi, meskipun dalam lingkungan multi-utas yang kompleks, sangat mungkin bahwa dua
threads
mencoba mengakses sumber daya yang sama secara bersamaan. Dalam kasus pendekatan
Imperatif , mudah untuk melihat bahwa ketika
thread
lain memiliki akses ke array
numbers
dalam proses menggunakannya, hasilnya akan ditentukan oleh urutan di mana
threads
mengakses array
numbers
. Situasi ini disebut
race condition
dan dapat menyebabkan perilaku yang tidak terduga dan bahkan ketidakstabilan dan crash aplikasi.
Sebagai perbandingan, Pendekatan
Fungsional tidak memiliki "efek samping". Dengan kata lain, output dari metode
output
tidak mengubah nilai yang tersimpan di sistem kami dan hanya ditentukan oleh input. Dalam hal ini, setiap utas (
threads
) yang memiliki akses ke array
numbers
akan SELALU menerima nilai yang sama dan perilakunya akan stabil dan dapat diprediksi.
3. Apa yang terjadi jika Anda ingin memiliki akses ke nilai asli yang disimpan dalam array
numbers
?
Ini adalah kelanjutan dari diskusi kami tentang "efek samping". Jelas, perubahan status tidak dilacak. Oleh karena itu, dengan pendekatan
Imperatif , kami kehilangan status awal array
numbers
kami selama proses konversi. Solusi kami, berdasarkan pada Pendekatan
Fungsional , menyimpan array
numbers
asli dan menghasilkan array
result
baru dengan properti yang diinginkan pada output. Itu meninggalkan array
numbers
asli utuh dan cocok untuk diproses di masa depan.
4. Seberapa andal kode ini dapat diuji?
Karena pendekatan
Fungsional menghancurkan semua "efek samping", fungsi yang diuji sepenuhnya berada di dalam metode. Input dari metode ini TIDAK PERNAH akan mengalami perubahan, sehingga Anda dapat menguji beberapa kali menggunakan siklus sebanyak yang Anda suka, dan Anda akan SELALU mendapatkan hasil yang sama. Dalam hal ini, pengujian sangat mudah. Sebagai perbandingan, menguji solusi
Imperatif dalam satu lingkaran akan mengubah awal entri dan Anda akan mendapatkan hasil yang sangat berbeda setelah setiap iterasi.
Ringkasan Manfaat
Seperti yang kita lihat dari contoh yang sangat sederhana, Pendekatan
Fungsional adalah hal yang keren jika Anda berurusan dengan Model Data karena:
- Itu deklaratif
- Ini memperbaiki masalah terkait utas seperti
race condition
dan kebuntuan - Ini membuat keadaan tidak berubah, yang dapat digunakan untuk transformasi selanjutnya.
- Mudah untuk diuji.
Mari kita melangkah lebih jauh dalam mempelajari Pemrograman
Fungsional di
Swift
. Ini mengasumsikan bahwa "aktor" utama adalah fungsi, dan mereka harus menjadi
objek utama dari kelas pertama .
Fungsi Kelas Pertama dan Fungsi Orde Tinggi
Agar suatu fungsi menjadi kelas satu, ia harus memiliki kemampuan untuk dinyatakan sebagai variabel. Ini memungkinkan Anda untuk mengelola fungsi sebagai JENIS data normal dan sekaligus menjalankannya. Untungnya, di
Swift
fungsi adalah objek dari kelas pertama, yaitu, mereka didukung dengan meneruskannya sebagai argumen ke fungsi lain, mengembalikannya sebagai hasil dari fungsi lain, menugaskannya ke variabel, atau menyimpannya dalam struktur data.
Karena itu, kami memiliki fungsi lain di
Swift
- fungsi tingkat tinggi yang didefinisikan sebagai fungsi yang menggunakan fungsi lain sebagai argumen atau mengembalikan fungsi. Ada banyak dari mereka:
map
,
filter
,
reduce
,
forEach
,
flatMap
,
compactMap
,
sorted
, dll. Contoh paling umum dari fungsi tingkat tinggi adalah
map
,
filter
dan
reduce
. Mereka tidak global, mereka semua "melekat" pada JENIS-JENIS tertentu. Mereka bekerja pada semua JENIS
Sequence
, termasuk
Collection
, yang diwakili oleh struktur data
Swift
seperti
Array
,
Dictionary
dan
Set
. Di
Swift 5
, fungsi tingkat tinggi juga berfungsi dengan TYPE yang sama sekali baru -
Result
.
map(_:)
Dalam
Swift
map(_:)
mengambil fungsi sebagai parameter dan mengubah nilai-nilai
tertentu sesuai dengan fungsi ini. Misalnya, menerapkan
map(_:)
ke array nilai
Array
, kami menerapkan fungsi parameter untuk setiap elemen array asli dan kami mendapatkan array
Array
, tetapi juga nilai yang dikonversi.
Dalam kode di atas, kami membuat fungsi
timesTen (_:Int)
, yang mengambil nilai
Int
integer dan mengembalikan nilai integer
Int
dikalikan dengan
10
, dan menggunakannya sebagai parameter input ke
map(_:)
urutan tinggi kami
map(_:)
fungsi, menerapkannya ke array kami
numbers
. Kami mendapatkan hasil yang kami butuhkan di array
result
.
Nama fungsi parameter kali.
timesTen
untuk fungsi
timesTen
tinggi seperti
map(_:)
tidak masalah,
parameter input dan nilai kembali penting, yaitu tanda tangan
(Int) -> Int
parameter input fungsi. Oleh karena itu, kita dapat menggunakan fungsi anonim di
map(_:)
- closure - dalam bentuk apa pun, termasuk yang dengan nama argumen pendek
$0
,
$1
, dll.
Jika kita melihat fungsi
map(_ :)
untuk
Array
, itu mungkin terlihat seperti ini:
func map<T>(_ transform: (Element) -> T) -> [T] { var returnValue = [T]() for item in self { returnValue.append(transform(item)) } return returnValue }
Ini adalah kode penting yang sudah tidak asing lagi bagi kami, tetapi ini bukan lagi masalah pengembang, ini masalah
Apple
, masalah
Swift
. Implementasi fungsi
map(_:)
tingkat tinggi
map(_:)
dioptimalkan oleh
Apple
dalam hal kinerja, dan kami, para pengembang, dijamin fungsionalitas
map(_:)
, jadi kami hanya dapat mengekspresikan dengan benar dengan argumen fungsi
transform
kami inginkan tanpa khawatir tentang
itu akan diterapkan. Sebagai hasilnya, kami mendapatkan kode yang dapat dibaca dengan sempurna dalam bentuk satu baris, yang akan bekerja lebih baik dan lebih cepat.
dikembalikan oleh fungsi parameter mungkin tidak bersamaan dengan
elemen dalam koleksi asli.
Dalam kode di atas, kami memiliki bilangan bulat yang
possibleNumbers
, Bilangan, diwakili sebagai string, dan kami ingin mengonversinya menjadi bilangan bulat dari
Int
, menggunakan
Int(_ :String)
inisialisasi
failable
Int(_ :String)
diwakili oleh penutup
{ str in Int(str) }
. Kami melakukan ini menggunakan
map(_:)
dan mendapatkan array yang
mapped
dari
Optional
sebagai output:

Kami tidak
mengonversi
elemen dari array kami yang mungkinNomor ke bilangan bulat, sebagai akibatnya, satu bagian menerima nilai
nil
, menunjukkan ketidakmungkinan mengkonversi
String
ke
Int
integer, dan bagian lainnya berubah menjadi
Optionals
, yang memiliki nilai:
print (mapped)
compactMap(_ :)
Jika fungsi parameter yang diteruskan ke fungsi orde lebih tinggi memiliki nilai
Optional
pada output, maka mungkin lebih berguna untuk menggunakan fungsi orde lain yang lebih tinggi, serupa artinya -
compactMap(_ :)
, yang melakukan hal yang sama seperti
map(_:)
, tetapi juga "memperluas" nilai yang diterima pada output
Optional
dan menghilangkan nilai
nil
dari koleksi.

Dalam hal ini, kami mendapatkan array TYPE
[Int]
compactMapped
, tetapi mungkin lebih kecil:
let possibleNumbers = ["1", "2", "three", "///4///", "5"] let compactMapped = possibleNumbers.compactMap(Int.init) print (compactMapped)

Setiap kali Anda menggunakan
init?()
Initializer sebagai fungsi transformasi, Anda harus menggunakan
compactMap(_ :)
:
Saya harus mengatakan bahwa ada lebih dari cukup alasan untuk menggunakan fungsi
compactMap(_ :)
tingkat tinggi
compactMap(_ :)
.
Swift
“loves” Nilai
Optional
, mereka dapat diperoleh tidak hanya dengan menggunakan
init?()
“
failable
”
init?()
Initializer, tetapi juga dengan menggunakan
as?
"Casting":
let views = [innerView,shadowView,logoView] let imageViews = views.compactMap{$0 as? UIImageView}
... dan
try?
saat memproses kesalahan yang dilemparkan oleh beberapa metode. Saya harus mengatakan bahwa
Apple
khawatir bahwa penggunaan
try?
sangat sering mengarah ke
Optional
ganda dan di
Swift 5 sekarang hanya menyisakan satu level
Optional
setelah
try?
.
Ada satu lagi fungsi serupa dalam nama
flatMap(_ :)
orde tinggi
flatMap(_ :)
, yang sedikit lebih rendah.
Terkadang, untuk menggunakan
map(_:)
fungsi tingkat tinggi
map(_:)
, berguna untuk menggunakan metode
zip (_:, _:)
untuk membuat urutan pasangan dari dua urutan yang berbeda.
Misalkan kita memiliki
view
di mana beberapa titik diwakili, terhubung bersama dan membentuk garis putus-putus:

Kita perlu membangun garis terputus lain yang menghubungkan titik tengah segmen dari garis terputus asli:

Untuk menghitung titik tengah segmen, kita harus memiliki koordinat dua titik: arus dan berikutnya. Untuk melakukan ini, kita dapat membuat urutan yang terdiri dari pasangan poin - saat ini dan selanjutnya - menggunakan metode
zip (_:, _:)
points.dropFirst()
zip (_:, _:)
, di mana kita akan menggunakan array dari titik awal dan array dari
points.dropFirst()
:
let pairs = zip (points,points.dropFirst()) let averagePoints = pairs.map { CGPoint(x: ($0.x + $1.x) / 2, y: ($0.y + $1.y) / 2 )}
Memiliki urutan seperti itu, kami sangat mudah menghitung titik tengah menggunakan
map(_:)
fungsi tingkat tinggi
map(_:)
dan menampilkannya pada grafik.
filter (_:)
Di
Swift
,
filter (_:)
fungsi urutan tinggi
filter (_:)
tersedia untuk sebagian besar
yang menyediakan fungsi
map(_:)
. Anda dapat memfilter urutan
Sequence
apa pun dengan
filter (_:)
, ini jelas! Metode
filter (_:)
mengambil fungsi lain sebagai parameter, yang merupakan kondisi untuk setiap elemen dari urutan, dan jika kondisinya terpenuhi, maka elemen tersebut dimasukkan dalam hasil, dan jika tidak, itu tidak termasuk. "Fungsi lain" ini mengambil nilai tunggal - elemen dari urutan
Sequence
- dan mengembalikan
Bool
, predikat yang disebut.
Sebagai contoh, untuk array
Array
,
filter (_:)
fungsi orde tinggi
filter (_:)
menerapkan fungsi predikat dan mengembalikan array lain yang hanya terdiri dari elemen-elemen array asli di mana fungsi predikat input mengembalikan
true
.
Di sini,
filter (_:)
fungsi urutan tinggi
filter (_:)
mengambil setiap elemen dari array
numbers
(diwakili oleh
$0
) dan memeriksa untuk melihat apakah elemen ini adalah bilangan genap. Jika ini adalah bilangan genap, maka elemen-elemen dari array
numbers
termasuk dalam array yang baru, jika tidak. Kami dalam bentuk deklaratif menginformasikan program
ingin kami dapatkan alih-alih peduli
kita harus melakukannya.
Saya akan memberikan contoh lain menggunakan
filter (_:)
fungsi tingkat tinggi
filter (_:)
untuk mendapatkan hanya
20
angka Fibonacci pertama dengan nilai
< 4000
:
let fibonacci = sequence(first: (0, 1), next: { ($1, $0 + $1) }) .prefix(20).map{$0.0} .filter {$0 % 2 == 0 && $0 < 4000} print (fibonacci)
Kami mendapatkan urutan tupel yang terdiri dari dua elemen dari urutan Fibonacci: yang ke-n dan (ke-1):
(0, 1), (1, 1), (1, 2), (2, 3), (3, 5) …
Untuk pemrosesan lebih lanjut, kami membatasi jumlah elemen ke dua puluh elemen pertama menggunakan
prefix (20)
dan mengambil elemen
0
-
0
dari tupel yang dihasilkan menggunakan
map {$0.0 }
, yang akan sesuai dengan urutan Fibonacci mulai dengan
0
:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,...
Kita bisa mengambil elemen
1
dari tuple yang terbentuk menggunakan
map {$0.1 }
, yang akan sesuai dengan urutan Fibonacci dimulai dengan
1
:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,...
Kami mendapatkan elemen yang kami butuhkan dengan bantuan
filter {$0 % 2 == 0 && $0 < 4000}
fungsi orde tinggi
filter {$0 % 2 == 0 && $0 < 4000}
, yang mengembalikan array elemen urutan yang memenuhi predikat yang diberikan. Dalam kasus kami, ini akan menjadi array bilangan bulat
[Int]
:
[0, 2, 8, 34, 144, 610, 2584]
Ada contoh berguna lain menggunakan
filter (_:)
untuk
Collection
.
Saya dihadapkan
dengan satu masalah nyata , ketika Anda memiliki berbagai
images
yang ditampilkan menggunakan
CollectionView
, dan menggunakan teknologi
Drag & Drop
Anda dapat mengumpulkan sejumlah besar gambar dan memindahkannya ke mana-mana, termasuk memindahkannya ke " tempat sampah. "

Dalam hal ini, array indeks yang
removedIndexes
dibuang ke "tempat sampah" sudah diperbaiki, dan Anda perlu membuat array gambar baru, kecuali yang indeksnya berada dalam array yang
removedIndexes
. Misalkan kita memiliki array
images
integer yang meniru gambar, dan array indeks integer ini
removedIndexes
yang perlu dihapus. Kami akan menggunakan
filter (_:)
untuk menyelesaikan masalah kami:
var images = [6, 22, 8, 14, 16, 0, 7, 9] var removedIndexes = [2,5,0,6] var images1 = images .enumerated() .filter { !removedIndexes.contains($0.offset) } .map { $0.element } print (images1)
Metode
enumerated()
mengembalikan urutan tupel yang terdiri dari indeks
offset
dan nilai
element
array.
Kemudian kami menerapkan filter filter
ke urutan tupel yang dihasilkan, hanya menyisakan mereka yang indeksnya $0.offset
tidak terkandung dalam array removedIndexes
. Langkah selanjutnya, kita memilih nilai dari tuple $0.element
dan mendapatkan array yang kita butuhkan images1
.reduce (_:, _:)
Metode ini reduce (_:, _:)
juga tersedia untuk sebagian besar
yang tersedia map(_:)
dan metode filter (_:)
. Metode reduce (_:, _:)
"runtuh" urutan Sequence
ke nilai akumulasi tunggal dan memiliki dua parameter. Parameter pertama adalah nilai akumulasi awal, dan parameter kedua adalah fungsi yang menggabungkan nilai akumulasi dengan elemen urutan Sequence
untuk mendapatkan nilai akumulasi baru.Fungsi parameter input diterapkan ke setiap elemen dari urutan Sequence
, satu demi satu, hingga mencapai akhir dan menciptakan nilai akumulasi akhir. let sum = Array (1...100).reduce(0, +)
Ini adalah contoh sepele klasik menggunakan fungsi urutan yang lebih tinggi reduce (_:, _:)
- menghitung jumlah elemen array Array
. 1 0 1 0 +1 = 1 2 1 2 2 + 1 = 3 3 3 3 3 + 3 = 6 4 6 4 4 + 6 = 10 . . . . . . . . . . . . . . . . . . . 100 4950 100 4950 + 100 = 5050
Dengan menggunakan fungsi ini, reduce (_:, _:)
kita dapat dengan mudah menghitung jumlah angka Fibonacci yang memenuhi kondisi tertentu: let fibonacci = sequence(first: (0, 1), next: { ($1, $0 + $1) }) .prefix(20).map{$0.0} .filter {$0 % 2 == 0 && $0 < 4000} print (fibonacci)
Tetapi ada aplikasi yang lebih menarik dari fungsi urutan yang lebih tinggi reduce (_:, _:)
.Misalnya, kita dapat dengan sangat sederhana dan ringkas menentukan parameter yang sangat penting untuk UIScrollView
- ukuran area "yang dapat digulir" contentSize
- berdasarkan ukurannya subviews
: let scrollView = UIScrollView() scrollView.addSubview(UIView(frame: CGRect(x: 300.0, y: 0.0, width: 200, height: 300))) scrollView.addSubview(UIView(frame: CGRect(x: 100.0, y: 0.0, width: 300, height: 600))) scrollView.contentSize = scrollView.subviews .reduce(CGRect.zero,{$0.union($1.frame)}) .size
Dalam demo ini, nilai akumulasi adalah
GCRect
, dan operasi akumulasi adalah operasi menggabungkan union
persegi panjang yang adalah frame
milik kita subviews
.Terlepas dari kenyataan bahwa fungsi tingkat tinggi reduce (_:, _:)
mengasumsikan karakter akumulatif, ia dapat digunakan dalam perspektif yang sama sekali berbeda. Misalnya, untuk membagi tupel menjadi bagian-bagian dalam array tupel:
Swift 4.2
memperkenalkan jenis baru fungsi orde tinggi reduce (into:, _:)
. Metode reduce (into:, _:)
ini lebih disukai dalam efisiensi dibandingkan dengan metode reduce (:, :)
jika COW (copy-on-write)
, misalnya, Array
atau digunakan sebagai struktur yang dihasilkan Dictionary
.Ini dapat digunakan secara efektif untuk menghapus nilai yang cocok dalam array bilangan bulat:
... atau ketika menghitung jumlah elemen yang berbeda dalam array:
flatMap (_:)
Sebelum beralih ke fungsi tingkat tinggi ini, mari kita lihat demo yang sangat sederhana. let maybeNumbers = ["42", "7", "three", "///4///", "5"] let firstNumber = maybeNumbers.map (Int.init).first
Jika kita menjalankan kode ini untuk mengeksekusi Playground
, maka semuanya terlihat baik, dan kita firstNumber
adalah sama 42
:
Tapi, jika Anda tidak tahu, Playground
sering menyembunyikan yang benar
, khususnya
konstanta firstNumber
. Sebenarnya, konstanta firstNumber
memiliki
dua hal Optional
:
Ini karena map (Int.init)
pada output ia membentuk sebuah array Optional
dari nilai-nilai TYPE [Int?]
, karena tidak setiap baris String
dapat dikonversi ke Int
dan penginisialisasi Int.int
adalah "jatuh" ( failable
). Kemudian kita mengambil elemen pertama dari array yang dibentuk menggunakan fungsi first
untuk array Array
, yang juga membentuk outputOptional
, karena array mungkin kosong dan kami tidak akan bisa mendapatkan elemen pertama dari array. Sebagai hasilnya, kami memiliki dobel Optional
, yaitu Int??
.Kami memiliki struktur bersarang Optional
di Optional
mana itu benar-benar lebih sulit untuk dikerjakan dan yang secara alami tidak kami inginkan. Untuk mendapatkan nilai dari struktur bersarang ini, kita harus "menyelam" ke dalam dua tingkat. Selain itu, setiap transformasi tambahan dapat memperdalam level Optional
lebih rendah.Mendapatkan nilai dari double nested Optional
benar - benar memberatkan.Kami memiliki 3 opsi dan semuanya membutuhkan pengetahuan mendalam tentang bahasa tersebut Swift
.if let
, ; «» «» Optional
, — «» Optional
:

if case let
( pattern match
) :

??
:

- ,
switch
:

Lebih buruk lagi, masalah bersarang tersebut
muncul dalam situasi apa pun yang melibatkan generic
wadah umum ( ) yang untuknya suatu operasi didefinisikan map
. Misalnya, untuk array Array
.Pertimbangkan kode contoh lain. Misalkan kita memiliki teks multi-baris multilineString
yang ingin kita bagi menjadi kata-kata yang ditulis dalam huruf kecil (kecil): let multilineString = """ , , ; , — , : — , . , , . . , , « » . , , ! """ let words = multilineString.lowercased() .split(separator: "\n") .map{$0.split(separator: " ")}
Untuk mendapatkan array kata words
, pertama-tama kita membuat huruf besar (besar) huruf kecil (kecil) menggunakan metode lowercased()
, Kemudian kita membagi teks menjadi split(separatot: "\n")
garis - garis menggunakan metode dan mendapatkan array string, dan kemudian menggunakannya map {$0.split(separator: " ")}
untuk memisahkan setiap baris menjadi kata-kata yang terpisah.Akibatnya, kami mendapatkan array bersarang: [["", ",", "", ","], ["", "", ";", "", "", "", "", ",", "—"], ["", ",", "", "", ":"], ["", "—", "", "", ",", "", "", "."], ["", "", ",", "", "", ","], ["", "", ".", "", ""], ["", ".", "", ",", ""], ["", "", "", ""], ["", "", ",", "", "«", "»"], ["", ".", "", ","], ["", ",", "", "", "!"]]
... dan words
memiliki
ganda Array
:
Sekali lagi, kami menerima "bersarang" struktur data, tapi kali ini kami tidak Optional
, dan Array
. Jika kita ingin terus memproses kata-kata yang diterima words
, misalnya, untuk menemukan spektrum huruf dari teks multi-baris ini, maka pertama-tama kita harus “meluruskan” array ganda Array
dan mengubahnya menjadi array tunggal Array
. Ini mirip dengan apa yang kami lakukan dengan menggandakan Optional
untuk demo di awal bagian ini pada flatMap
: let maybeNumbers = ["42", "7", "three", "///4///", "5"] let firstNumber = maybeNumbers.map (Int.init).first
Untungnya, Swift
kita tidak perlu menggunakan konstruksi sintaksis yang rumit. Swift
memberi kami solusi siap pakai untuk array Array
dan Optional
. Ini adalah fungsi urutan yang lebih tinggi flatMap
! Ini sangat mirip dengan map
, tetapi memiliki fungsi tambahan yang terkait dengan "pelurusan" "lampiran" yang muncul selama eksekusi map
. Dan itulah mengapa disebut flatMap
, itu “meluruskan” ( flattens
) hasilnya map
.Mari kita coba menerapkan flatMap
ke firstNumber
:
Kami benar-benar mendapatkan output dengan satu level Optional
. Bekerjalebih menarik flatMap
untuk sebuah array Array
. Dalam ungkapan kami untuk, words
kami hanya mengganti map
denganflatMap
:
... dan kami hanya mendapatkan berbagai kata words
tanpa "bersarang": ["", ",", "", ",", "", "", ";", "", "", "", "", ",", "—", "", ",", "", "", ":", "", "—", "", "", ",", "", "", ".", "", "", ",", "", "", ",", "", "", ".", "", "", "", ".", "", ",", "", "", "", "", "", "", "", ",", "", "«", "»", "", ".", "", ",", "", ",", "", "", "!"]
Sekarang kita dapat melanjutkan pemrosesan yang kita butuhkan dari susunan kata yang dihasilkan words
, tetapi hati-hati. Jika kita menerapkannya sekali lagi flatMap
untuk setiap elemen array words
, kita akan mendapatkan, mungkin, hasil yang tak terduga, tetapi cukup bisa dimengerti.
Kami mendapatkan satu array, bukan huruf "simbol" yang [Character]
terkandung dalam frase multi-baris kami: ["", "", "", "", "", "", "", "", "", "", "", "", ",", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ",", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ";", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ...]
Faktanya adalah string String
adalah kumpulan Collection
karakter [Character]
dan, berlaku flatMap
untuk setiap kata individual, kami sekali lagi menurunkan tingkat "bersarang" dan datang ke berbagai karakter flattenCharacters
.Mungkin ini yang Anda inginkan, atau mungkin tidak. Perhatikan ini.Menyatukan semuanya: memecahkan beberapa masalah
TUGAS 1
Kita dapat melanjutkan pemrosesan susunan kata yang diperoleh pada bagian sebelumnya yang kita butuhkan words
dan menghitung frekuensi kemunculan huruf dalam frasa multi-baris kita. Untuk memulai, mari “tempel” semua kata dari array words
ke dalam satu baris besar dan kecualikan semua tanda baca darinya, yaitu, tinggalkan hanya huruf-huruf: let wordsString = words.reduce ("",+).filter { "" .contains($0)}
Jadi, kami mendapat semua surat yang kami butuhkan. Sekarang mari kita buat kamus mereka, di mana kuncinya key
adalah huruf, dan nilainya value
adalah frekuensi kemunculannya dalam teks.Kita bisa melakukan ini dengan dua cara.Metode pertama dikaitkan dengan penggunaan Swift 4.2
variasi baru dari fungsi urutan yang lebih tinggi yang telah muncul di reduce (into:, _:)
. Metode ini sangat cocok bagi kita untuk mengatur kamus letterCount
dengan frekuensi kemunculan huruf dalam frasa multi-baris kami: let letterCount = wordsString.reduce(into: [:]) { counts, letter in counts[letter, default: 0] += 1} print (letterCount)
Sebagai hasilnya, kita akan mendapatkan kamus letterCount
[Character : Int]
di mana kunci key
adalah karakter yang ditemukan dalam frasa yang diteliti, dan karena nilainya value
adalah jumlah karakter ini.Metode kedua melibatkan menginisialisasi kamus menggunakan pengelompokan, yang memberikan hasil yang sama: let letterCountDictionary = Dictionary(grouping: wordsString ){ $0}.mapValues {$0.count} letterCount == letterCountDictionary
Kami ingin mengurutkan kamus letterCount
berdasarkan abjad: let lettersStat = letterCountDictionary .sorted(by: <) .map{"\($0.0):\($0.1)"} print (lettersStat)
Tetapi kami tidak dapat mengurutkan kamus secara langsung Dictionary
, karena pada dasarnya ini bukan struktur data yang diurutkan. Jika kita menerapkan fungsi sorted (by:)
ke kamus Dictionary
, maka itu akan mengembalikan kepada kita elemen-elemen dari urutan yang diurutkan dengan predikat yang diberikan dalam bentuk array dari nama tupel, yang map
kita ubah menjadi sebuah array string yang [":17", ":5", ":18", ...]
mencerminkan frekuensi kemunculan huruf yang sesuai.Kami melihat bahwa kali ini sorted (by:)
hanya operator " <
" yang dilewatkan sebagai predikat ke fungsi tingkat tinggi . Fungsi sorted (by:)
mengharapkan "fungsi perbandingan" sebagai satu-satunya argumen pada input. Ini digunakan untuk membandingkan dua nilai yang berdekatan dan memutuskan apakah mereka dipesan dengan benar (dalam hal ini, pengembaliantrue
) atau tidak (pengembalian false
). Kami dapat memberikan fungsi "fungsi perbandingan" ini sorted (by:)
dalam bentuk penutupan anonim: sorted(by: {$0.key < $1.key}
Dan kita bisa memberikannya " <
" operator , yang memiliki tanda tangan yang kita butuhkan, seperti yang dilakukan di atas. Ini juga fungsi, dan pengurutan berdasarkan kunci sedang berlangsung key
.Jika kita ingin mengurutkan kamus berdasarkan nilai value
dan mencari huruf mana yang paling sering ditemukan dalam frasa ini, maka kita harus menggunakan penutup untuk fungsi sorted (by:)
: let countsStat = letterCountDictionary .sorted(by: {$0.value > $1.value}) .map{"\($0.0):\($0.1)"} print (countsStat )
Jika kita melihat solusi untuk masalah menentukan spektrum huruf dari frase multiline secara keseluruhan ... let multilineString = """ , , ; , — , : — , . , , . . , , « » . , , ! """ let words = multilineString.lowercased() .split(separator: "\n") .flatMap{$0.split(separator: " ")} let wordsString = words.reduce ("",+).filter { "" .contains($0)} let letterCount = wordsString.reduce(into: [:]) { counts, letter in counts[letter, default: 0] += 1} let lettersStat = letterCountDictionary .sorted(by: <) .map{"\($0.0):\($0.1)"} print (lettersStat)
... maka kita akan melihat bahwa dalam fragmen kode ini pada dasarnya tidak ada variabel (tidak var
, hanya let)
semua nama fungsi yang digunakan mencerminkan TINDAKAN (fungsi) atas informasi tertentu, sama sekali tidak khawatir tentang BAGAIMANA tindakan ini diimplementasikan:split
- split,map
- transformflatMap
- transform with keselarasan (dengan menghapus satu tingkat bersarang),filter
- filter,sorted
- menyortir,reduce
- untuk mengubah data menjadi struktur tertentu dengan cara operasi tertentudalam fragmen ini setiap baris kode menjelaskan nama fungsi yang kita gunakan jika kita berada dalam. fill "murni" transformasi digunakan map
jika kita melakukan konversi dari tingkat bersarang digunakanflatMap
, jika kami hanya ingin memilih data tertentu, maka kami menggunakan filter
, dll. Semua fungsi "urutan tertinggi" ini dirancang dan diuji Apple
dengan mempertimbangkan pengoptimalan kinerja akun. Jadi kode ini sangat andal dan ringkas - kami tidak membutuhkan lebih dari 5 kalimat untuk menyelesaikan masalah kami. Ini adalah contoh pemrograman fungsional.Satu-satunya kelemahan menerapkan pendekatan fungsional dalam demo ini adalah bahwa, demi ketidakberdayaan, kemampuan pengujian dan keterbacaan, kami berulang kali mengejar teks kami melalui berbagai fungsi tingkat tinggi. Dalam kasus sejumlah besar barang koleksi, Collection
kinerja dapat turun drastis. Misalnya, jika kita pertama kali menggunakan filter(_:)
dan, dan kemudian - first
.MasukSwift 4
Beberapa opsi fitur baru telah ditambahkan untuk meningkatkan kinerja, dan berikut adalah beberapa tips untuk menulis kode yang lebih cepat.1. Gunakan contains
, BUKANfirst( where: ) != nil
Memverifikasi bahwa suatu objek ada dalam koleksi Collection
dapat dilakukan dengan banyak cara. Performa terbaik disediakan oleh fungsi contains
.KODE YANG BENAR let numbers = [0, 1, 2, 3] numbers.contains(1)
KODE YANG TIDAK BENAR let numbers = [0, 1, 2, 3] numbers.filter { number in number == 1 }.isEmpty == false numbers.first(where: { number in number == 1 }) != nil
2. Gunakan validasi isEmpty
, BUKAN perbandingan count
dengan nol
Karena untuk beberapa koleksi, akses ke properti count
dilakukan dengan mengulangi semua elemen koleksi.KODE YANG BENAR let numbers = [] numbers.isEmpty
KODE YANG TIDAK BENAR let numbers = [] numbers.count == 0
3. Periksa string kosong String
denganisEmpty
String String
in Swift
adalah kumpulan karakter [Character]
. Ini berarti bahwa untuk string String
juga lebih baik digunakan isEmpty
.KODE YANG BENAR myString.isEmpty
KODE YANG TIDAK BENAR myString == "" myString.count == 0
4. Memperoleh elemen pertama yang memenuhi kondisi tertentu
Iterasi seluruh koleksi untuk mendapatkan objek pertama yang memenuhi kondisi tertentu dapat dilakukan dengan menggunakan metode yang filter
diikuti oleh suatu metode first
, tetapi metode ini adalah yang terbaik dalam hal kecepatan first (where:)
. Metode ini berhenti mengulangi koleksi segera setelah memenuhi kondisi yang diperlukan. Metode ini filter
akan terus beralih ke seluruh koleksi, terlepas dari apakah memenuhi elemen yang diperlukan atau tidak.Jelas, hal yang sama berlaku untuk metode ini last (where:)
.KODE YANG BENAR let numbers = [3, 7, 4, -2, 9, -6, 10, 1] let firstNegative = numbers.first(where: { $0 < 0 })
KODE YANG TIDAK BENAR let numbers = [0, 2, 4, 6] let allEven = numbers.filter { $0 % 2 != 0 }.isEmpty
Terkadang, ketika koleksinya Collection
sangat besar dan kinerjanya sangat penting bagi Anda, ada baiknya kembali untuk membandingkan pendekatan imperatif dan fungsional dan memilih yang cocok untuk Anda.TUGAS 2
Ada contoh hebat lain tentang penggunaan fungsi urutan tinggi yang sangat singkat reduce (_:, _:)
yang saya temui. Ini adalah game SET .Inilah aturan dasarnya. Nama permainan SET
berasal dari kata bahasa Inggris "set" - "set". Permainan ini SET
melibatkan 81 kartu, masing-masing dengan gambar unik:
Setiap kartu memiliki 4 atribut, tercantum di bawah ini:Jumlah : setiap kartu memiliki satu, dua atau tiga karakter.Jenis karakter : oval, belah ketupat atau gelombang.Warna : Simbol bisa merah, hijau atau ungu.Mengisi : karakter dapat dikosongkan, diarsir, atau diarsir.Tujuan permainanSET
: Di antara 12 kartu yang diletakkan di atas meja, Anda perlu menemukan SET
(satu set) yang terdiri dari 3 kartu, di mana masing-masing dari tanda-tanda itu benar-benar bertepatan atau sepenuhnya berbeda pada ketiga kartu. Semua tanda harus sepenuhnya mematuhi aturan ini.Misalnya, jumlah karakter pada ketiga kartu harus sama atau berbeda, warna pada ketiga kartu harus sama atau berbeda, dan seterusnya ...Dalam contoh ini, kita hanya akan tertarik pada Model Peta SET
struct SetCard
dan algoritma untuk menentukan SET
dengan Peta ke-3 isSet( cards:[SetCard])
: struct SetCard: Equatable { let number: Variant
Model setiap fitur - jumlah number
, jenis simbol shape
, warna color
dan Filling fill
- disajikan listing Variant
memiliki tiga nilai yang mungkin var1
, var2
dan var3
yang sesuai dengan bilangan bulat 3 rawValue
- 1,2,3
. Dalam bentuk ini, rawValue
mudah dioperasikan. Jika kita mengambil beberapa tanda, misalnya, color
kemudian menambahkan semuanya rawValue
untuk colors
3 kartu, kita akan menemukan bahwa jika colors
semua 3 kartu sama, maka jumlahnya akan sama 3
, 6
atau 9
, jika semuanya akan berbeda, maka jumlahnya akan menjadi sama 6
. Dalam salah satu dari kasus ini, kami memiliki kelipatan dari jumlah ketiga rawValue
untukcolors
semua 3 kartu. Kita tahu bahwa ini adalah prasyarat untuk membuat 3 kartu SET
. Agar 3 kartu menjadi benar-benar SET
diperlukan, untuk semua tanda SetCard
- Jumlah number
, Jenis simbol shape
, Warna color
dan Mengisi fill
- jumlahnya harus rawValue
kelipatan ke-3.Oleh karena itu, dalam static
metode, isSet( cards:[SetCard])
pertama kita menghitung array sums
dari jumlah rawValue
untuk semua 3 peta untuk semua peta 4 kinerja menggunakan fungsi yang lebih tinggi reduce
dengan nilai awal sama untuk 0
, dan mengumpulkan fungsi {$0 + $1.number.rawValue}
, {$0 + $1.color.rawValue}
, {$0 + $1.shape.rawValue}
, { {$0 + $1.fill.rawValue}
. Setiap elemen array sums
harus merupakan kelipatan ke-3, dan sekali lagi kita menggunakan fungsinyareduce
, tapi kali ini dengan nilai awal sama dengan true
dan mengakumulasikan fungsi logis " AND
" {$0 && ($1 % 3) == 0}
. Dalam Swift 5, untuk menguji multiplisitas dari satu angka ke yang lain, sebuah fungsi diperkenalkan isMultiply(of:)
sebagai ganti operator yang %
tersisa. Hal ini juga akan meningkatkan pembacaan kode: { $0 && ($1.isMultiply(of:3) }
.Kode pendek yang fantastis ini untuk mengetahui apakah 3 SetCard
kartu adalah kartu yang SET
diperoleh berkat pendekatan " fungsional ", dan kita dapat memastikan bahwa itu berhasil Playground
:
Cara SET
membangun antarmuka pengguna ( UI
) pada Model Game ini di sini , di sini dan di sini .Fitur murni dan efek samping
Fungsi murni memenuhi dua kondisi. Itu selalu mengembalikan hasil yang sama dengan parameter input yang sama. Dan perhitungan hasilnya tidak menyebabkan efek samping yang terkait dengan output data di luar (misalnya, ke disk) atau dengan meminjam data sumber dari luar (misalnya, waktu). Ini memungkinkan Anda untuk mengoptimalkan kode secara signifikan.Topik ini Swift
diatur dengan sempurna pada point.free dalam episode pertama " Fungsi " dan " Efek Samping " , yang diterjemahkan ke dalam bahasa Rusia dan disajikan sebagai " Fungsi " dan "Efek Samping . "Komposisi fungsi
Dalam pengertian matematika, ini berarti menerapkan satu fungsi ke hasil fungsi lainnya. Dalam suatu Swift
fungsi, mereka bisa mengembalikan nilai yang bisa Anda gunakan sebagai input untuk fungsi lain. Ini adalah praktik pemrograman yang umum.Bayangkan bahwa kita memiliki array bilangan bulat dan kami ingin mendapatkan array kuadrat dari angka genap unik pada output. Biasanya kami menerapkan kembali ini sebagai berikut: var integerArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 4, 5] func unique(_ array: [Int]) -> [Int] { return array.reduce(into: [], { (results, element) in if !results.contains(element) { results.append(element) } }) } func even(_ array: [Int]) -> [Int] { return array.filter{ $0%2 == 0} } func square(_ array: [Int]) -> [Int] { return array.map{ $0*$0 } } var array = square(even(unique(integerArray)))
Kode ini memberi kami hasil yang benar, tetapi Anda melihat bahwa keterbacaan baris kode terakhir tidak begitu mudah. Urutan fungsi (dari kanan ke kiri) adalah kebalikan dari yang kita terbiasa (dari kiri ke kanan) dan ingin melihat di sini. Kita perlu mengarahkan logika kita terlebih dahulu ke bagian terdalam dari beberapa embeddings - ke array inegerArray
, kemudian ke fungsi di luar array ini unique
, kemudian kita naik satu level lagi - fungsi even
, dan akhirnya, fungsi dalam kesimpulan square
.Dan di sini "komposisi" fungsi >>>
dan operator datang untuk membantu kami |>
, yang memungkinkan kami untuk menulis kode dengan cara yang sangat nyaman, mewakili pemrosesan array asli integerArray
sebagai "konveyor" fungsi: var array1 = integerArray |> unique >>> even >>> square
Hampir semua bahasa seperti pemrograman fungsional khusus F#
, Elixir
dan Elm
menggunakan operator ini untuk "komposisi" fungsi.Tidak Swift
ada operator built-in dari "komposisi" fungsi >>>
dan |>
, tetapi kita dapat dengan mudah mendapatkannya dengan bantuan Generics
penutupan ( closure
) dan infix
operator: precedencegroup ForwardComposition{ associativity: left higherThan: ForwardApplication } infix operator >>> : ForwardComposition func >>> <A, B, C>(left: @escaping (A) -> B, right: @escaping (B) -> C) -> (A) -> C { return { right(left($0)) } } precedencegroup ForwardApplication { associativity: left } infix operator |> : ForwardApplication func |> <A, B>(a: A, f: (A) -> B) -> B { return f(a) }
Meskipun ada biaya tambahan, dalam beberapa kasus ini dapat secara signifikan meningkatkan kinerja, keterbacaan dan pengujian kode Anda. Misalnya, ketika di dalam map
Anda menempatkan seluruh rangkaian fungsi menggunakan operator "komposisi" >>>
alih-alih mengejar array melalui banyak map
: var integerArray1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 4, 5] let b = integerArray1.map( { $0 + 1 } >>> { $0 * 3 } >>> String.init) print (b)
Namun tidak selalu pendekatan fungsional memberikan efek positif.Pada awalnya, ketika muncul Swift
pada tahun 2014, semua orang bergegas untuk menulis perpustakaan dengan operator untuk "komposisi" fungsi dan menyelesaikan tugas yang sulit untuk waktu itu seperti parsing JSON
menggunakan operator pemrograman fungsional alih-alih menggunakan konstruksi bersarang tak terhingga if let
. Saya sendiri menerjemahkan artikel tentang parsing fungsional JSON yang menyenangkan saya dengan solusi yang elegan dan adalah penggemar perpustakaan Argo .Tetapi para pengembang Swift
pergi dengan cara yang sama sekali berbeda dan mengusulkan, berdasarkan teknologi berorientasi protokol, cara penulisan kode yang jauh lebih ringkas. Untuk "mengirimkan" JSON
data langsung ke
Cukup untuk melakukan hal ini
Codable
, yang secara otomatis mengimplementasikan protokol ini, jika model Anda terdiri dari dikenal Swift
struktur data: String
, Int
, URL
, Array
, Dictionary
, dll struct Blog: Codable { let id: Int let name: String let url: URL }
Memiliki JSON
data dari artikel terkenal itu ... [ { "id" : 73, "name" : "Bloxus test", "url" : "http://remote.bloxus.com/" }, { "id" : 74, "name" : "Manila Test", "url" : "http://flickrtest1.userland.com/" } ]
... saat ini Anda hanya perlu satu baris kode untuk mendapatkan berbagai blog blogs
: let blogs = Bundle.main.path(forResource: "blogs", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { try? JSONDecoder().decode([Blog].self, from: $0) } print ("\(blogs!)")
Setiap orang dengan aman lupa tentang menggunakan operator dari "komposisi" fungsi untuk parsing JSON
, jika ada cara lain, lebih mudah dimengerti dan mudah untuk melakukan ini menggunakan protokol.Jika semuanya begitu mudah, maka kita dapat "mengunggah" JSON
data ke Model yang lebih kompleks. Misalkan kita memiliki file JSON
data yang memiliki nama user.json
dan terletak di direktori kita Resources.
. Ini berisi data tentang pengguna tertentu: { "email": "blob@pointfree.co", "id": 42, "name": "Blob" }
Dan kami memiliki Codable
pengguna User
dengan inisialisasi dari data json
: struct User: Codable { let email: String let id: Int let name: String init?(json: Data) { if let newValue = try? JSONDecoder().decode(User.self, from: json) { self = newValue } else { return nil } } }
Kami dapat dengan mudah mendapatkan pengguna baru newUser
dengan kode fungsional yang bahkan lebih sederhana: let newUser = Bundle.main.path(forResource: "user", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { User.init(json: $0) }
Jelas, newUser
akan ada JENIS Optional
, yaitu User?
:
Misalkan dalam direktori kami Resources
ada file lain dengan nama invoices.json
dan berisi data pada faktur pengguna ini. [ { "amountPaid": 1000, "amountDue": 0, "closed": true, "id": 1 }, { "amountPaid": 500, "amountDue": 500, "closed": false, "id": 2 } ]
Kami dapat memuat data ini persis seperti yang kami lakukan dengan User
. Mari mendefinisikan struktur sebagai Model Faktur struct Invoice
... struct Invoice: Codable { let amountDue: Int let amountPaid: Int let closed: Bool let id: Int }
... dan decode JSON
array faktur yang disajikan di atas invoices
, hanya mengubah jalur file dan logika decoding decode
: let invoices = Bundle.main.path(forResource: "invoices", ofType: "json") .map( URL.init(fileURLWithPath:) ) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) }
invoices
akan menjadi [Invoice]?
:
Sekarang kami ingin menghubungkan pengguna user
bersama dengan fakturnya invoices
, jika faktanya tidak sama nil
, dan menyimpan, misalnya, dalam struktur amplop UserEnvelope
yang dikirim ke pengguna bersama dengan fakturnya: struct UserEnvelope { let user: User let invoices: [Invoice] }
Alih-alih melakukan dua kali if let
... if let newUser = newUser, let invoices = invoices { }
... mari kita tulis analog fungsional ganda if let
sebagai Generic
fungsi tambahan zip
yang mengubah dua Optional
nilai menjadi sebuah Optional
tuple: func zip<A, B>(_ a: A?, _ b: B?) -> (A, B)? { if let a = a, let b = b { return (a, b) } return nil }
Sekarang kita tidak punya alasan untuk menetapkan sesuatu ke variabel newUser
dan invoices
, kita hanya membangun semuanya ke dalam fungsi baru kita zip
, menggunakan penginisialisasi UserEnvelope.init
dan semuanya akan bekerja! let userEnv = zip( Bundle.main.path(forResource: "user", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { User.init(json: $0) }, Bundle.main.path(forResource: "invoices", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) } ).flatMap (UserEnvelope.init) print ("\(userEnv!)")
Dalam satu ekspresi tunggal, seluruh algoritme untuk mengirimkan JSON
data ke data yang kompleks
dalam bentuk struktur dikemas struct UserEnvelope
.zip
, , . user
, JSON
, invoices
, JSON
. .map
, , «» .flatMap
, , , .
Operasi zip
, map
dan flatMap
mewakili semacam bahasa khusus domain (DSL) untuk konversi data.Kami selanjutnya dapat mengembangkan demo ini untuk mewakili secara asinkron membaca konten file sebagai fungsi khusus yang dapat Anda lihat di pointfree.co .Saya bukan penggemar fanatik Pemrograman Fungsional di mana-mana dan dalam segala hal, tetapi penggunaan moderat menurut saya disarankan.Kesimpulan
Saya memberi contoh berbagai pemrograman fungsional fitur Swf
t «keluar dari kotak", berdasarkan pada penggunaan fungsi tingkat tinggi map
, flatMap
, reduce
, filter
dan yang lainnya untuk urutan Sequence
, Optional
dan Result
. Mereka bisa menjadi "workhorses" dari pembuatan kode, ,
terutama jika nilai
- struktur struct
dan enumerasi terlibat di sana enum
. Pengembang iOS
aplikasi harus memiliki alat ini.Semua demo yang dikompilasi Playground
dapat ditemukan di Github . Jika Anda memiliki masalah dengan peluncurannya Playground
, Anda dapat melihat artikel ini:Bagaimana cara menyingkirkan kesalahan "beku" Xcode Playground dengan pesan "Launching Simulator" dan "Running Playground".Referensi:
Functional Programming in Swift: An Introduction.An Introduction to Functional Programming in Swift.The Many Faces of Flat-Map: Part 3Inside the Standard Library: Sequence.map()Practical functional programming in Swift