Apakah fungsi generator valid dalam pemrograman fungsional?

17

Pertanyaannya adalah:

  • Apakah generator melanggar paradigma pemrograman fungsional? Mengapa atau mengapa tidak?
  • Jika ya, dapatkah generator digunakan dalam pemrograman fungsional dan bagaimana caranya?

Pertimbangkan yang berikut ini:

function * downCounter(maxValue) {
  yield maxValue;
  yield * downCounter(maxValue > 0 ? maxValue - 1 : 0);
}

let counter = downCounter(26);
counter.next().value; // 26
counter.next().value; // 25
// ...etc

The downCounterMetode muncul bernegara. Selain itu, panggilan downCounterdengan input yang sama, akan selalu menghasilkan output yang sama. Namun, pada saat yang sama, panggilan next()tidak menghasilkan hasil yang konsisten.

Saya tidak yakin apakah generator merusak paradigma pemrograman fungsional atau tidak karena dalam contoh ini counteradalah objek generator dan pemanggilan next()akan menghasilkan hasil yang sama seperti objek generator lain yang dibuat dengan sama persis maxValue.

Selain itu, memanggil someCollection[3]array akan selalu mengembalikan elemen keempat. Demikian pula, memanggil next()empat kali pada objek generator juga akan selalu mengembalikan elemen keempat.

Untuk lebih banyak konteks, pertanyaan-pertanyaan ini muncul saat mengerjakan kata pemrograman . Orang yang menjawab pertanyaan, mengajukan pertanyaan apakah generator dapat digunakan dalam pemrograman fungsional atau tidak.

Pete
sumber
2
Setiap program memiliki status. Pertanyaan sebenarnya adalah apakah itu memenuhi syarat sebagai keadaan fungsional , yang saya tafsirkan sebagai "keadaan tidak berubah," keadaan yang tidak berubah setelah ditugaskan. Saya mengklaim bahwa satu-satunya cara Anda dapat membuat generator mengembalikan sesuatu yang berbeda pada setiap panggilan adalah jika keadaan bisa berubah entah bagaimana terlibat.
Robert Harvey

Jawaban:

14

Fungsi generator tidak terlalu istimewa. Kita dapat menerapkan mekanisme serupa sendiri dengan menulis ulang fungsi generator dengan gaya berbasis panggilan balik:

function downCounter(maxValue) {
  return {
    "value": maxValue,
    "next": function () {
      return downCounter(maxValue > 0 ? maxValue - 1 : 0);
     },
  };
}

let counter = downCounter(26);
counter.value; //=> 26
counter.next().value; //=> 25

Jelas, downCounteritu murni dan fungsional karena mendapat. Tidak ada masalah di sini.

Protokol generator yang digunakan oleh JavaScript melibatkan objek yang bisa berubah. Ini tidak perlu, lihat kode di atas. Secara khusus, objek yang bisa berubah berarti kita kehilangan transparansi referensial - kemampuan untuk mengganti ekspresi dengan nilainya. Sementara dalam contoh saya, counter.next().valueakan selalu mengevaluasi ke 25mana pun itu terjadi dan seberapa sering kita mengulanginya, ini tidak terjadi dengan generator JS - pada satu titik itu 26, maka 25, dan itu bisa benar-benar nomor apa pun. Ini bermasalah jika kita meneruskan referensi ke generator ke fungsi lain:

counter.next().value; //=> 25
otherFunction(counter); // does this consume the counter?
counter.next().value; // what will this be? It depends on the otherFunction()

Jadi jelas, generator memegang keadaan dan karenanya tidak cocok untuk pemrograman fungsional "murni". Untungnya, Anda tidak harus melakukan pemrograman fungsional murni, dan mungkin lebih pragmatis. Jika generator membuat kode Anda lebih jelas, Anda harus menggunakannya tanpa hati nurani yang buruk. Lagipula, JavaScript bukanlah bahasa fungsional murni, tidak seperti misalnya Haskell.

Omong-omong, di Haskell tidak ada perbedaan antara mengembalikan daftar dan generator, karena menggunakan evaluasi malas:

downCounter :: Int -> [Int]
downCounter maxValue =
  maxValue : (downCounter (max 0 (maxValue - 1)))
-- invoke as "take n (downCounter 26)" to display n elements
amon
sumber