Mempertahankan Status tanpa tugas

10

Saya belajar pemrograman fungsional dan saya mengalami kesulitan memahami bagaimana beberapa skenario tertentu diimplementasikan tanpa menggunakan penugasan. Masalah sederhana berikut ini cukup meringkas kebingungan saya.

Tulis sebuah program yang menerima peristiwa tentang perubahan dalam struktur data yang diberikan dan memancarkan peristiwa ketika struktur data ini mencapai keadaan tertentu.

Jadi saya punya salinan datastructure yang saya pelihara

datastructure_copy::DataStructure 

Saya memiliki aliran peristiwa yang dipecat saat perubahan:

datastructure_changes::Stream Change

Saya memiliki fungsi yang menerapkan perubahan pada struktur data dan mengembalikan salinan baru itu:

apply_change::Change -> DataStructure -> DataStructure

Dan saya memiliki predikat yang memeriksa apakah status data telah mencapai kondisi yang diinginkan.

is_ready::DataStructure ->Boolean

Dengan kata lain saya membutuhkan sesuatu seperti 'mengurangi' yang berfungsi pada aliran.

Saya tahu bahwa salah satu cara untuk mengimplementasikan ini adalah menghitung ulang keadaan setiap kali ada perubahan, namun ini tampaknya tidak praktis. Saya bermain sedikit dengan State Monad, tetapi bagi saya sepertinya itu dimaksudkan untuk menyelesaikan masalah yang berbeda.

Jadi adakah cara lain untuk melakukan itu?

Perhatikan bahwa pertanyaan saya murni konseptual dan saya tidak terlalu akrab dengan Haskell.

Bobby Marinoff
sumber
Dengan satu atau lain cara Anda tidak akan pernah melihat 'tugas' (perhatikan perbedaan di antara tugas dan mengikat) di Haskell karena 'Haskell tidak memiliki gagasan "tugas", "keadaan bisa berubah", atau "variabel", dan merupakan "murni" bahasa fungsional "' . State Monad harus seperti yang Anda cari, Anda hanya perlu belajar cara menggunakannya. Jika Anda mau, saya bisa memberikan jawaban yang lebih komprehensif hari ini.
Francesco Gramano

Jawaban:

2

Saya tahu bahwa salah satu cara untuk mengimplementasikan ini adalah menghitung ulang keadaan setiap kali ada perubahan, namun ini tampaknya tidak praktis.

Jika perubahan yang diterapkan ketika suatu peristiwa terjadi tidak distributif, dengan satu atau lain cara, Anda harus menghitung ulang keadaan setiap kali suatu peristiwa terjadi, karena keadaan akhir tidak lain adalah keadaan awal, ditambah perubahan berturut-turut. Dan bahkan jika perubahannya bersifat distributif, Anda biasanya ingin mengubah keadaan secara berurutan menjadi yang berikutnya, karena Anda ingin menghentikan proses Anda secepat keadaan yang dicapai, dan karena Anda harus menghitung keadaan selanjutnya untuk menentukan apakah yang baru adalah negara yang dicari.

Dalam pemrograman fungsional, perubahan status biasanya diwakili oleh panggilan fungsi dan / atau parameter fungsi.

Karena Anda tidak dapat memprediksi kapan kondisi akhir akan dihitung, Anda tidak boleh menggunakan fungsi rekursif non-ekor. Aliran negara, di mana setiap negara didasarkan pada yang sebelumnya, bisa menjadi alternatif yang baik.

Jadi dalam kasus Anda, saya akan menjawab pertanyaan dengan kode berikut, di Scala:

import scala.util.Random

val initState = 0.0
def nextState(state: Double, event: Boolean): Double = if(event) state + 0.3 else state - 0.1 // give a new state
def predicate(state: Double) = state >= 1

// random booleans as events
// nb: must be a function in order to force Random.nextBoolean to be called for each  element of the stream
def events(): Stream[Boolean] = Random.nextBoolean #:: events()  

val states: Stream[Double] = initState #:: states.zip(events).map({ case (s,e) => nextState(s,e)}) // a stream of all the successive states

// stop when the state is >= 1 ;
// display all the states computed before it stopped
states takeWhile(! predicate(_)) foreach println 

Yang bisa memberi, misalnya (saya menyederhanakan output):

0.0
0.3
0.2
0.5
0.8

val states: Stream[Double] = ... adalah garis di mana negara berturut-turut dihitung.

Elemen pertama dari aliran ini adalah keadaan awal sistem. zipmenggabungkan aliran negara dengan aliran peristiwa ke dalam aliran tunggal pasangan elemen, masing-masing pasangan menjadi (negara, peristiwa). mapmengubah setiap pasangan menjadi nilai tunggal menjadi keadaan baru, dihitung sebagai fungsi dari keadaan lama dan peristiwa terkait. Karenanya, keadaan baru adalah keadaan yang dihitung sebelumnya, ditambah peristiwa terkait yang "mengubah" keadaan.

Jadi pada dasarnya, Anda mendefinisikan aliran negara yang berpotensi tidak terbatas, setiap negara bagian baru merupakan fungsi dari negara bagian yang terakhir dihitung, dan acara baru. Karena aliran malas di Scala (antara lain), hanya ada yang dihitung berdasarkan permintaan, sehingga Anda tidak harus menghitung status tidak berguna, dan Anda dapat menghitung sebanyak negara yang Anda inginkan.

Jika Anda hanya tertarik pada status pertama yang menghormati predikat, ganti baris kode terakhir dengan:

states find predicate get

Yang mengambil:

res7: Double = 1.1
mgoeminne
sumber
Bisakah Anda memberikan beberapa wawasan tentang garis yang melakukan keajaiban:val states: Stream[Double]...
Bobby Marinoff
Tentu. Silakan lihat edit saya.
mgoeminne
1

Anda mengatakan Anda memiliki 2 fungsi:

apply_change::Change -> DataStructure -> DataStructure
is_ready::DataStructure ->Boolean

dan jika saya mengerti Anda saat is_readyitu agak mahal sehingga Anda tidak ingin melakukan itu untuk setiap acara perubahan berulang kali.

Apa yang Anda butuhkan adalah fungsi mengambil DataStructure awal dan mengembunnya menjadi keadaan sederhana dan fungsi yang mengambil keadaan terkondensasi, Perubahan dan output keadaan terkondensasi baru.

Katakanlah DataStructure adalah triplet x,y,zdan Anda menunggu x, y dan z menjadi bilangan prima. Keadaan terkondensasi Anda kemudian bisa menjadi set yang x, y, z tidak prima Perubahan yang membuat x prime menghapus x dari set. Perubahan yang membuat x tidak prima menambahkan x ke set (jika tidak ada). Struktur Data siap maka set kosong.

Idenya adalah bahwa memperbarui keadaan kental jauh lebih murah daripada memperbarui DataStructure dan komputasi sudah dari awal.

Catatan: Pendekatan yang lebih baik lagi adalah melacak x, y, z mana yang diperiksa sebagai prima dan jika mereka di mana. Untuk setiap Perubahan Anda menandai bidang yang relevan sebagai tidak dicentang. Kemudian ketika is_ready dipanggil Anda memeriksa dan mengingat. Ini lebih baik jika Anda tidak memeriksa is_ready setelah setiap Perubahan karena x mungkin berubah beberapa kali dan Anda hanya memeriksa perdana sekali.

Goswin von Brederlow
sumber