apa yang bisa salah dalam konteks pemrograman fungsional jika objek saya bisa berubah?

9

Saya dapat melihat manfaat dari objek yang tidak dapat diubah dan tidak dapat diubah seperti objek yang tidak dapat diubah menghilangkan banyak masalah yang sulit dalam pemrograman multi-threaded karena keadaan bersama dan dapat ditulis. Sebaliknya, objek yang bisa berubah membantu menangani identitas objek daripada membuat salinan baru setiap waktu dan dengan demikian juga meningkatkan kinerja dan penggunaan memori terutama untuk objek yang lebih besar.

Satu hal yang saya coba pahami adalah apa yang salah dengan memiliki objek yang bisa berubah dalam konteks pemrograman fungsional. Seperti salah satu poin yang diceritakan kepada saya adalah bahwa hasil fungsi panggilan dalam urutan yang berbeda tidak deterministik.

Saya mencari contoh nyata nyata di mana sangat jelas apa yang bisa salah menggunakan objek yang bisa berubah dalam pemrograman fungsi. Pada dasarnya jika itu buruk, itu buruk terlepas dari OO atau paradigma pemrograman fungsional, kan?

Saya percaya di bawah pernyataan saya sendiri menjawab pertanyaan ini. Tetapi saya masih membutuhkan beberapa contoh agar saya bisa merasakannya lebih alami.

OO membantu mengelola ketergantungan dan menulis program yang lebih mudah dan dapat dipelihara dengan bantuan alat-alat seperti enkapsulasi, polimorfisme, dll.

Pemrograman fungsional juga memiliki motif yang sama dalam mempromosikan kode yang dapat dipelihara tetapi dengan menggunakan gaya yang menghilangkan kebutuhan untuk menggunakan alat dan teknik OO - salah satunya saya percaya adalah dengan meminimalkan efek samping, fungsi murni dll.

rahulaga_dev
sumber
1
@ Ruben saya akan mengatakan sebagian besar bahasa fungsional memungkinkan variabel dapat berubah, tetapi membuatnya berbeda untuk menggunakannya misalnya variabel bisa berubah memiliki tipe yang berbeda
jk.
1
Saya pikir Anda mungkin telah bercampur berubah dan tidak bisa berubah dalam paragraf pertama Anda?
jk.
1
@jk., tentu saja dia melakukannya. Diedit untuk memperbaikinya.
David Arno
6
@Ruben Pemrograman fungsional adalah sebuah paradigma. Karena itu tidak memerlukan bahasa pemrograman fungsional. Dan beberapa bahasa fp seperti F # memiliki fitur ini .
Christophe
1
@ Ruben tidak ada secara khusus saya memikirkan Mvars di haskell hackage.haskell.org/package/base-4.9.1.0/docs/… bahasa yang berbeda tentu saja memiliki solusi yang berbeda atau hackage.haskell.org/package/base-4.11.1.0 /docs/Data-IORef.html meskipun tentu saja Anda akan menggunakan keduanya dari dalam monads
jk.

Jawaban:

7

Saya pikir pentingnya ditunjukkan dengan membandingkan dengan pendekatan OO

misalnya, katakanlah kita memiliki objek

Order
{
    string Status {get;set;}
    Purchase()
    {
        this.Status = "Purchased";
    }
}

Dalam paradigma OO metode ini dilampirkan pada data, dan masuk akal jika data tersebut dimutasi oleh metode tersebut.

var order = new Order();
order.Purchase();
Console.WriteLine(order.Status); // "Purchased"

Dalam Paradigma Fungsional kami mendefinisikan hasil dalam hal fungsi. pesanan yang dibeli ADALAH hasil dari fungsi pembelian yang diterapkan pada suatu pesanan. Ini menyiratkan beberapa hal yang perlu kita pastikan

var order = new Order(); //this is a 'new order'
var purchasedOrder = purchase(order); // this is a 'purchased order'
Console.WriteLine(order.Status); // "New" order is still a 'new order'

Apakah Anda mengharapkan order.Status == "Dibeli"?

Ini juga menyiratkan bahwa fungsi kita idempoten. yaitu. menjalankannya dua kali akan menghasilkan hasil yang sama setiap kali.

var order = new Order(); //new order
var purchasedOrder = purchase(order); //purchased order
var purchasedOrder2 = purchase(order); //another purchased order
var purchasedOrder = purchase(purchasedOrder); //error! cant purchase an order twice

Jika pesanan diubah oleh fungsi pembelian, PurchasingOrder2 akan gagal.

Dengan mendefinisikan hal-hal sebagai hasil dari fungsi itu memungkinkan kita untuk menggunakan hasil-hasil itu tanpa benar-benar menghitungnya. Yang dalam istilah pemrograman eksekusi ditangguhkan.

Ini bisa berguna dalam dirinya sendiri, tetapi begitu kita tidak yakin kapan suatu fungsi benar-benar akan terjadi DAN kita baik-baik saja tentang itu, kita dapat memanfaatkan pemrosesan paralel lebih banyak daripada yang bisa kita lakukan dalam paradigma OO.

Kita tahu bahwa menjalankan suatu fungsi tidak akan mempengaruhi hasil dari fungsi lain; jadi kita dapat meninggalkan komputer untuk menjalankannya dalam urutan apa pun yang dipilihnya, menggunakan sebanyak utas yang diinginkan.

Jika suatu fungsi mengubah inputnya, kita harus lebih berhati-hati tentang hal-hal seperti itu.

Ewan
sumber
terima kasih !! sangat membantu. Jadi implementasi pembelian baru akan terlihat seperti Order Purchase() { return new Order(Status = "Purchased") } sehingga status hanya baca bidang. ? Sekali lagi mengapa praktik ini lebih relevan dalam konteks paradigma pemrograman fungsi? Manfaat yang Anda sebutkan dapat dilihat dalam pemrograman OO juga, kan?
rahulaga_dev
di OO Anda akan mengharapkan objek. Pembelian () untuk memodifikasi objek Anda bisa menjadikannya tidak berubah, tetapi mengapa tidak beralih ke paradigma Fungsional penuh
Ewan
Saya pikir masalah harus divisualisasikan karena saya murni c # developer yang berorientasi objek. Jadi apa yang Anda katakan dalam bahasa yang mencakup pemrograman fungsional tidak akan memerlukan fungsi 'Pembelian ()' mengembalikan pesanan yang dibeli untuk dilampirkan dengan kelas atau objek apa pun, bukan?
rahulaga_dev
3
Anda dapat menulis fungsional c # mengubah objek Anda menjadi struct, membuatnya tidak berubah dan menulis Func <Order, Order> Purchase
Ewan
12

Kunci untuk memahami mengapa benda tidak bergerak bermanfaat tidak benar-benar terletak pada mencoba menemukan contoh nyata dalam kode fungsional. Karena sebagian besar kode fungsional ditulis menggunakan bahasa fungsional, dan sebagian besar bahasa fungsional tidak dapat diubah secara default, sifat dasar paradigma ini dirancang untuk menghindari apa yang Anda cari, agar tidak terjadi.

Hal utama yang harus ditanyakan adalah, apa manfaat dari kekekalan? Jawabannya adalah, ia menghindari kompleksitas. Katakanlah kita memiliki dua variabel, xdan y. Keduanya dimulai dengan nilai 1. ymeskipun berlipat ganda setiap 13 detik. Berapa nilai masing-masing dari mereka dalam waktu 20 hari? xakan 1. Itu mudah. Namun perlu upaya untuk berolahraga ykarena jauh lebih kompleks. Apa waktu dalam 20 hari? Apakah saya harus memperhitungkan daylight saving? Kompleksitas yversus xjauh lebih banyak.

Dan ini terjadi dalam kode nyata juga. Setiap kali Anda menambahkan nilai mutasi ke dalam campuran, itu menjadi nilai kompleks lain untuk Anda pegang dan hitung di kepala Anda, atau di atas kertas, ketika mencoba menulis, membaca, atau men-debug kode. Semakin banyak kompleksitas, semakin besar kemungkinan Anda membuat kesalahan dan memperkenalkan bug. Kode sulit untuk ditulis; sulit dibaca; susah di-debug: kodenya susah diperbaiki

Mutabilitas tidak buruk . Sebuah program dengan nol mutabilitas tidak dapat memberikan hasil, yang sangat tidak berguna. Bahkan jika ketidakstabilan adalah menulis hasil ke layar, disk atau apa pun, itu harus ada di sana. Yang buruk adalah kerumitan yang tidak perlu. Salah satu cara paling sederhana untuk mengurangi kompleksitas adalah membuat hal-hal yang tidak dapat diubah secara default dan hanya membuatnya dapat berubah ketika dibutuhkan, karena kinerja atau alasan fungsional.

David Arno
sumber
4
"salah satu cara paling sederhana untuk mengurangi kompleksitas adalah membuat hal-hal yang tidak dapat diubah secara default dan hanya membuatnya bisa berubah ketika dibutuhkan": Ringkasan yang sangat bagus dan ringkas.
Giorgio
2
@ DavidVrno Kompleksitas yang Anda gambarkan membuat kode sulit untuk dipikirkan. Anda juga menyentuh ini ketika Anda mengatakan "Kode ini sulit untuk ditulis; sulit dibaca; sulit untuk debug; ...". Saya suka objek abadi karena mereka membuat kode lebih mudah untuk dipikirkan, tidak hanya oleh saya sendiri, tetapi pengamat yang melihat tanpa mengetahui seluruh proyek.
membongkar-nomor-5
1
@RahulAgarwal, " Tapi mengapa masalah ini menjadi lebih menonjol dalam konteks pemrograman fungsional ". Tidak. Saya pikir mungkin saya bingung dengan apa yang Anda tanyakan karena masalahnya jauh lebih tidak menonjol di FP karena FP mendorong ketidakberdayaan sehingga menghindari masalah.
David Arno
1
@djechlin, " Bagaimana contoh 13 detik Anda menjadi lebih mudah untuk dianalisis dengan kode tidak berubah? " Itu tidak bisa: yharus bermutasi; itu syarat. Terkadang kita harus memiliki kode kompleks untuk memenuhi persyaratan yang kompleks. Poin yang saya coba sampaikan adalah bahwa kompleksitas yang tidak perlu harus dihindari. Nilai bermutasi secara inheren lebih kompleks daripada yang tetap, jadi - untuk menghindari kompleksitas yang tidak perlu - hanya nilai bermutasi saat Anda harus.
David Arno
3
Mutabilitas menciptakan krisis identitas. Variabel Anda tidak memiliki identitas tunggal lagi. Sebaliknya, identitasnya sekarang tergantung pada waktu. Jadi secara simbolis, alih-alih satu x, kita sekarang memiliki keluarga x_t. Kode apa pun yang menggunakan variabel itu sekarang harus mengkhawatirkan waktu juga, menyebabkan kompleksitas tambahan yang disebutkan dalam jawaban.
Alex Vong
8

apa yang bisa salah dalam konteks pemrograman fungsional

Hal-hal yang sama yang dapat salah dalam pemrograman non-fungsional: Anda bisa mendapatkan efek samping yang tidak diinginkan dan tidak diinginkan , yang merupakan penyebab kesalahan yang diketahui sejak ditemukannya bahasa pemrograman yang tercakup.

IMHO satu-satunya perbedaan nyata antara ini antara pemrograman fungsional dan non-fungsional adalah, dalam kode non-fungsional Anda biasanya akan mengharapkan efek samping, dalam pemrograman fungsional, Anda tidak akan.

Pada dasarnya jika itu buruk, itu buruk terlepas dari OO atau paradigma pemrograman fungsional, kan?

Tentu - efek samping yang tidak diinginkan adalah kategori bug, terlepas dari paradigma. Sebaliknya juga benar - efek samping yang sengaja digunakan dapat membantu untuk menangani masalah kinerja dan biasanya diperlukan untuk sebagian besar program dunia nyata ketika datang ke I / O dan berurusan dengan sistem eksternal - juga terlepas dari paradigma.

Doc Brown
sumber
4

Saya baru saja menjawab pertanyaan StackOverflow yang menggambarkan pertanyaan Anda dengan cukup baik. Masalah utama dengan struktur data yang bisa berubah adalah identitas mereka hanya valid pada satu waktu yang tepat, sehingga orang cenderung menjejalkan sebanyak mungkin ke titik kecil dalam kode di mana mereka tahu identitasnya konstan. Dalam contoh khusus ini, ia melakukan banyak logging di dalam for for loop:

for (elem <- rows map (row => s3 map row)) {
  val elem_str = elem.map(_.toString)

  logger.info("verifying the S3 bucket passed from the ctrl table for each App")
  logger.info(s"Checking on App Code: ${elem head}")

  listS3Buckets(elem_str(1), elem_str(2)) match {

    case Some(allBktsInfo) =>
      logger.info(s"App: ${elem_str head} provided the bucket name as: ${elem_str(3)}")
      if (allBktsInfo.exists(x => x.getName == elem_str(3))) {
        logger.info(s"Provided S3 bucket: ${elem_str(3)} exists")
        println(s"s3 ${elem_str(3)} bucket exists")
      } else {
        logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
        logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
        excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
        println(s"s3 bucket ${elem_str(3)} doesn't exists")
    }

    case None =>
      logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
      logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
      excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
}

Ketika Anda terbiasa dengan ketidakberdayaan, tidak ada rasa takut perubahan struktur data jika Anda menunggu terlalu lama, sehingga Anda dapat melakukan tugas yang secara logis terpisah pada waktu luang Anda, dengan cara yang jauh lebih terpisah:

val (exists, missing) = rows partition bucketExists
missing foreach {row =>
  logger.info(s"WARNING: Provided S3 bucket ${row("s3_primary_bkt_name")} doesn't exist")
  logger.info(s"WARNING: Dropping the App: ${row("app")} from backup schedule")
}
Karl Bielefeldt
sumber
3

Keuntungan menggunakan objek yang tidak dapat diubah adalah bahwa jika seseorang menerima referensi ke objek dengan yang akan memiliki properti tertentu ketika penerima memeriksanya, dan perlu memberikan beberapa kode lain referensi ke objek dengan properti yang sama, seseorang dapat dengan mudah lulus sepanjang referensi ke objek tanpa memperhatikan siapa lagi yang mungkin telah menerima referensi atau apa yang mungkin mereka lakukan terhadap objek [karena tidak ada yang bisa dilakukan orang lain terhadap objek], atau ketika penerima dapat memeriksa objek [karena semua properti akan sama terlepas dari kapan mereka diperiksa].

Sebaliknya, kode yang perlu memberi seseorang referensi ke objek yang bisa berubah yang akan memiliki properti tertentu ketika penerima memeriksanya (dengan asumsi penerima itu sendiri tidak mengubahnya) juga perlu tahu bahwa tidak ada hal lain selain penerima yang akan pernah berubah properti itu, atau tahu kapan penerima akan mengakses properti itu, dan tahu bahwa tidak ada yang akan mengubah properti itu sampai terakhir kali penerima akan memeriksanya.

Saya pikir ini sangat membantu, untuk pemrograman secara umum (bukan hanya pemrograman fungsional) untuk memikirkan objek yang tidak dapat berubah jatuh ke dalam tiga kategori:

  1. Objek yang tidak dapat tidak akan membiarkan apa pun mengubahnya, bahkan dengan referensi. Objek seperti itu, dan referensi padanya, berperilaku sebagai nilai , dan dapat dibagikan secara bebas.

  2. Objek yang akan membiarkan diri mereka diubah oleh kode yang memiliki referensi padanya, tetapi referensi yang tidak akan pernah terpapar ke kode apa pun yang benar - benar akan mengubahnya. Objek-objek ini merangkum nilai-nilai, tetapi mereka hanya dapat dibagikan dengan kode yang dapat dipercaya untuk tidak mengubahnya atau mengeksposnya ke kode yang mungkin dilakukan.

  3. Objek yang akan diubah. Objek-objek ini paling baik dilihat sebagai wadah , dan merujuk padanya sebagai pengidentifikasi .

Pola yang berguna adalah sering memiliki objek membuat wadah, mengisi dengan menggunakan kode yang dapat dipercaya untuk tidak menyimpan referensi sesudahnya, dan kemudian memiliki satu-satunya referensi yang pernah ada di mana saja di alam semesta dalam kode yang tidak akan pernah memodifikasi objek setelah itu dihuni. Sementara wadah mungkin dari jenis yang bisa berubah, itu mungkin beralasan tentang (*) seolah-olah itu tidak berubah, karena tidak ada yang akan benar-benar bermutasi. Jika semua referensi ke wadah disimpan dalam jenis pembungkus yang tidak dapat diubah yang tidak akan pernah mengubah isinya, pembungkus tersebut dapat diedarkan dengan aman seolah-olah data di dalamnya disimpan dalam benda yang tidak dapat diubah, karena referensi ke pembungkus dapat dibagikan dan diperiksa secara bebas di kapan saja.

(*) Dalam kode multi-ulir, mungkin perlu menggunakan "hambatan memori" untuk memastikan bahwa sebelum utas apa pun dapat melihat referensi ke pembungkus, efek dari semua tindakan pada wadah akan terlihat oleh utas itu, tetapi itu adalah kasus khusus yang disebutkan di sini hanya untuk kelengkapan.

supercat
sumber
terima kasih atas jawaban yang mengesankan !! Saya pikir mungkin sumber kebingungan saya adalah karena saya dari latar belakang c # dan saya belajar "menulis kode gaya fungsional di c #" yang terus di mana-mana mengatakan menghindari objek yang bisa berubah - tapi saya pikir bahasa yang menganut paradigma pemrograman fungsional mempromosikan (atau menegakkan - tidak yakin - jika menegakkan benar digunakan) kekekalan.
rahulaga_dev
@RahulAgarwal: Dimungkinkan untuk memiliki referensi ke suatu objek merangkum nilai yang maknanya tidak terpengaruh oleh keberadaan referensi lain untuk objek yang sama, memiliki identitas yang akan mengaitkan mereka dengan referensi lain ke objek yang sama, atau tidak sama sekali. Jika keadaan kata-nyata berubah, maka nilai atau identitas suatu objek yang terkait dengan keadaan itu bisa konstan, tetapi tidak keduanya - seseorang harus berubah. $ 50.000 adalah yang seharusnya melakukan apa.
supercat
1

Seperti yang telah disebutkan, masalah dengan keadaan dapat berubah pada dasarnya adalah subkelas dari masalah efek samping yang lebih besar , di mana tipe pengembalian fungsi tidak secara akurat menggambarkan fungsi yang sebenarnya dilakukan, karena dalam kasus ini, ia juga melakukan mutasi keadaan. Masalah ini telah diatasi oleh beberapa bahasa penelitian baru, seperti F * ( http://www.fstar-lang.org/tutorial/ ). Bahasa ini menciptakan Sistem Efek yang mirip dengan sistem tipe, di mana fungsi tidak hanya secara statis mendeklarasikan tipenya, tetapi juga efeknya. Dengan cara ini, penelepon fungsi menyadari bahwa mutasi negara dapat terjadi saat memanggil fungsi, dan efek yang disebarkan ke peneleponnya.

Aaron M. Eshbach
sumber