Pemikiran fungsional. Bagian 6

Kami melanjutkan serangkaian artikel tentang pemrograman fungsional dalam F #. Hari ini kita berbicara tentang asosiasi dan komposisi fungsi, serta membandingkan komposisi dan saluran pipa. Lihat di bawah kucing!




Asosiasi dan komposisi fungsi


Fungsi asosiatif


Misalkan ada rangkaian fungsi yang ditulis dalam satu baris. Dalam urutan apa mereka akan digabungkan?


Misalnya, apa fungsi ini artinya?


let F xyz = xyz 

Apakah ini berarti bahwa fungsi y harus diterapkan pada argumen z , dan kemudian hasilnya harus dilewatkan ke x ? Yaitu:


 let F xyz = x (yz) 

Atau apakah fungsi x diterapkan pada argumen y , setelah itu fungsi yang diperoleh sebagai hasilnya akan dievaluasi dengan argumen z ? Yaitu:


 let F xyz = (xy) z 

  1. Opsi kedua sudah benar.
  2. Penggunaan fungsi telah meninggalkan asosiatif .
  3. xyz artinya sama dengan (xy) z .
  4. Dan wxyz sama dengan ((wx) y) z .
  5. Ini seharusnya tidak terlihat luar biasa.
  6. Kita telah melihat bagaimana aplikasi parsial bekerja.
  7. Jika kita berbicara tentang x sebagai fungsi dengan dua parameter, maka (xy) z adalah hasil dari aplikasi parsial dari parameter pertama, diikuti dengan meneruskan argumen z ke fungsi perantara.

Jika Anda membutuhkan asosiatif yang benar, Anda dapat menggunakan tanda kurung atau pipa. Tiga entri berikut ini setara:


 let F xyz = x (yz) let F xyz = yz |> x //    let F xyz = x <| yz //    

Sebagai latihan, cobalah untuk menampilkan tanda tangan dari fungsi-fungsi ini tanpa perhitungan nyata.


Komposisi fungsi


Kami menyebutkan komposisi fungsi beberapa kali, tetapi apa arti istilah ini sebenarnya? Tampaknya menakutkan pada pandangan pertama, tetapi pada kenyataannya, semuanya cukup sederhana.


Katakanlah kita memiliki fungsi "f" yang memetakan tipe "T1" ke tipe "T2". Kami juga memiliki fungsi "g" yang mengubah tipe "T2" menjadi "T3". Kemudian kita bisa menghubungkan output "f" dan input "g", menciptakan fungsi baru yang mengubah tipe "T1" menjadi tipe "T3".



Sebagai contoh:


 let f (x:int) = float x * 3.0 // f  -  int->float let g (x:float) = x > 4.0 // g  -  float->bool 

Kita dapat membuat fungsi baru "h" yang mengambil output dari "f" dan menggunakannya sebagai input untuk "g".


 let h (x:int) = let y = f(x) g(y) //    g 

Sedikit lebih kompak:


 let h (x:int) = g ( f(x) ) // h    int->bool // h 1 h 2 

Sejauh ini, sangat sederhana. Ini menarik, kita dapat mendefinisikan fungsi baru "menulis", yang mengambil fungsi "f" dan "g" dan menggabungkannya tanpa mengetahui tanda tangan mereka.


 let compose fgx = g ( f(x) ) 

Setelah eksekusi, Anda dapat melihat bahwa kompiler dengan benar memutuskan bahwa " f " adalah fungsi dari tipe generik 'a ke tipe generik 'b , dan ' g ' terbatas pada input tipe 'b :


 val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c 

(Perhatikan bahwa komposisi operasi yang digeneralisasi dimungkinkan hanya karena masing-masing fungsi memiliki tepat satu parameter input dan satu output. Pendekatan ini tidak dimungkinkan dalam bahasa non-fungsional.)


Seperti yang dapat kita lihat, definisi ini digunakan untuk operator " >> ".


 let (>>) fgx = g ( f(x) ) 

Berkat definisi ini, fungsi-fungsi baru dapat dibangun berdasarkan fungsi yang ada menggunakan komposisi.


 let add1 x = x + 1 let times2 x = x * 2 let add1Times2 x = (>>) add1 times2 x // add1Times2 3 

Rekaman eksplisit sangat rumit. Tapi Anda bisa membuatnya lebih mudah dipahami.


Pertama, Anda dapat menyingkirkan parameter x , dan komposisi akan mengembalikan sebagian aplikasi.


 let add1Times2 = (>>) add1 times2 

Kedua, karena >> adalah operator biner, Anda dapat meletakkannya di tengah.


 let add1Times2 = add1 >> times2 

Menggunakan komposisi membuat kode lebih bersih dan jelas.


 let add1 x = x + 1 let times2 x = x * 2 //   let add1Times2 x = times2(add1 x) //   let add1Times2 = add1 >> times2 

Menggunakan operator komposisi dalam praktiknya


Operator komposisi (seperti semua operator infiks) memiliki prioritas lebih rendah daripada fungsi biasa. Ini berarti bahwa fungsi yang digunakan dalam komposisi dapat memiliki argumen tanpa menggunakan tanda kurung.


Misalnya, jika fungsi "tambah" dan "waktu" memiliki parameter, mereka dapat diteruskan selama komposisi.


 let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 let add5Times3 = add 5 >> times 3 // add5Times3 1 

Selama input dan output fungsi yang sesuai cocok, fungsi tersebut dapat menggunakan nilai apa pun. Misalnya, pertimbangkan kode berikut yang menjalankan fungsi dua kali:


 let twice f = f >> f // ('a -> 'a) -> ('a -> 'a) 

Perhatikan bahwa kompilator telah menyimpulkan bahwa " f " menerima dan mengembalikan nilai dari jenis yang sama.


Sekarang pertimbangkan fungsi " + ". Seperti yang kita lihat sebelumnya, inputnya adalah int , tetapi outputnya sebenarnya (int->int) . Dengan demikian, " + " dapat digunakan dalam " twice ". Karena itu, Anda dapat menulis:


 let add1 = (+) 1 //  (int -> int) let add1Twice = twice add1 //    (int -> int) // add1Twice 9 

Di sisi lain, Anda tidak dapat menulis:


 let addThenMultiply = (+) >> (*) 

Karena input "*" harus berupa int , bukan fungsi int->int (yang merupakan output dari penambahan).


Tetapi jika Anda memperbaiki fungsi pertama sehingga hanya mengembalikan int , semuanya akan berfungsi:


 let add1ThenMultiply = (+) 1 >> (*) // (+) 1   (int -> int)   'int' // add1ThenMultiply 2 7 

Komposisi juga dapat dilakukan dalam urutan terbalik dengan cara " << ", jika perlu:


 let times2Add1 = add 1 << times 2 times2Add1 3 

Komposisi terbalik terutama digunakan untuk membuat kode lebih seperti bahasa Inggris ("seperti bahasa Inggris"). Sebagai contoh:


 let myList = [] myList |> List.isEmpty |> not //   myList |> (not << List.isEmpty) //    

Komposisi vs conveyor


Anda mungkin bingung oleh perbedaan kecil antara komposisi dan conveyor, seperti mereka mungkin terlihat sangat mirip.


Pertama, lihat definisi pipa:


 let (|>) xf = fx 

Semua ini memungkinkan Anda untuk menempatkan argumen fungsi sebelum itu, dan bukan setelahnya. Itu saja. Jika fungsi memiliki beberapa parameter, maka input harus menjadi parameter terakhir (dalam set parameter saat ini, dan tidak sama sekali). Contoh yang terlihat sebelumnya:


 let doSomething xyz = x+y+z doSomething 1 2 3 //      3 |> doSomething 1 2 //      

Komposisinya tidak sama dan tidak bisa menjadi pengganti pipa. Dalam contoh berikut, bahkan angka 3 bukanlah fungsi, jadi "output" tidak dapat dilewatkan ke doSomething :


 3 >> doSomething 1 2 //  // f >> g      g(f(x))     : doSomething 1 2 ( 3(x) ) //   3   ! // error FS0001: This expression was expected to have type 'a->'b // but here has type int 

Kompiler mengeluh bahwa nilai "3" harus berupa fungsi 'a->'b .


Bandingkan ini dengan definisi komposisi, yang membutuhkan 3 argumen, di mana dua yang pertama harus berfungsi.


 let (>>) fgx = g ( f(x) ) let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 

Upaya menggunakan saluran pipa alih-alih komposisi akan menghasilkan kesalahan kompilasi. Dalam contoh berikut, " add 1 " adalah fungsi int->int )), yang tidak dapat digunakan sebagai parameter kedua untuk " times 2 ".


 let add1Times2 = add 1 |> times 2 //  // x |> f      f(x)     : let add1Times2 = times 2 (add 1) // add1   'int' // error FS0001: Type mismatch. 'int -> int' does not match 'int' 

Kompiler mengeluh bahwa " times 2 " harus menerima parameter int->int , mis. menjadi fungsi (int->int)->'a .


Sumber Daya Tambahan


Ada banyak tutorial untuk F #, termasuk materi untuk mereka yang datang dengan pengalaman C # atau Java. Tautan berikut mungkin berguna saat Anda masuk lebih dalam ke F #:



Beberapa cara lain untuk mulai belajar F # juga dijelaskan.


Akhirnya, komunitas F # sangat ramah pemula. Ada obrolan yang sangat aktif di Slack, didukung oleh F # Software Foundation, dengan kamar pemula yang dapat Anda gabung dengan bebas . Kami sangat menyarankan Anda melakukan ini!


Jangan lupa untuk mengunjungi situs komunitas berbahasa Rusia F # ! Jika Anda memiliki pertanyaan tentang belajar bahasa, dengan senang hati kami akan membahasnya di ruang obrolan:



Tentang penulis terjemahan


Diterjemahkan oleh @kleidemos
Perubahan terjemahan dan editorial dilakukan oleh upaya komunitas pengembang F # berbahasa Rusia . Kami juga berterima kasih kepada @schvepsss dan @shwars karena telah menyiapkan artikel ini untuk dipublikasikan.

Source: https://habr.com/ru/post/id413195/


All Articles