การดูแลรักษารัฐโดยไม่ได้รับมอบหมาย


10

ฉันกำลังเรียนรู้การเขียนโปรแกรมฟังก์ชั่นและฉันมีปัญหาในการทำความเข้าใจว่าบางสถานการณ์ถูกนำไปใช้โดยไม่ได้รับมอบหมาย ปัญหาง่าย ๆ ดังต่อไปนี้ค่อนข้างจะทำให้ฉันสับสน

เขียนโปรแกรมที่รับเหตุการณ์เกี่ยวกับการเปลี่ยนแปลงในโครงสร้างข้อมูลที่กำหนดและส่งเหตุการณ์เมื่อโครงสร้างข้อมูลนี้ถึงสถานะที่แน่นอน

ดังนั้นฉันจึงมีสำเนาของโครงสร้างข้อมูลที่ฉันเก็บไว้

datastructure_copy::DataStructure 

ฉันมีกระแสของเหตุการณ์ที่ถูกไล่ออกเมื่อมีการเปลี่ยนแปลง:

datastructure_changes::Stream Change

ฉันมีฟังก์ชั่นที่ใช้การเปลี่ยนแปลงกับโครงสร้างข้อมูลและส่งคืนสำเนาใหม่:

apply_change::Change -> DataStructure -> DataStructure

และฉันมีภาคที่ตรวจสอบว่าสถานะข้อมูลได้ถึงสถานะที่ต้องการ

is_ready::DataStructure ->Boolean

กล่าวอีกนัยหนึ่งฉันต้องการบางสิ่งเช่น 'ลด' ที่ทำงานบนสตรีม

ฉันรู้ว่าวิธีหนึ่งในการดำเนินการนี้คือการคำนวณสถานะใหม่ทุกครั้งที่มีการเปลี่ยนแปลงมาถึงอย่างไรก็ตามสิ่งนี้ดูเหมือนจะไม่จริง ฉันเล่นกับ Monad ของรัฐ แต่ดูเหมือนว่าฉันจะแก้ปัญหาที่แตกต่าง

ดังนั้นมีวิธีอื่นในการทำเช่นนั้น?

โปรดทราบว่าคำถามของฉันเป็นแนวคิดอย่างหมดจดและฉันไม่คุ้นเคยอย่างลึกซึ้งกับ Haskell


ไม่ทางใดก็ทางหนึ่งคุณจะไม่เห็นคำว่า 'การมอบหมาย' (สังเกตความแตกต่างระหว่างการมอบหมายและการผูกมัด) ใน Haskell เพราะ'Haskell ไม่มีความคิดเรื่อง "การมอบหมาย", "สถานะไม่แน่นอน" หรือ "ตัวแปร" และเป็น "บริสุทธิ์" ภาษาการทำงาน' รัฐ Monad ควรเป็นสิ่งที่คุณกำลังมองหาคุณเพียงแค่ต้องเรียนรู้วิธีการใช้งาน หากคุณต้องการฉันสามารถให้คำตอบที่ครอบคลุมมากขึ้นในวันนี้
Francesco Gramano

คำตอบ:


2

ฉันรู้ว่าวิธีหนึ่งในการดำเนินการนี้คือการคำนวณสถานะใหม่ทุกครั้งที่มีการเปลี่ยนแปลงมาถึงอย่างไรก็ตามสิ่งนี้ดูเหมือนจะไม่จริง

หากการเปลี่ยนแปลงที่นำไปใช้เมื่อเหตุการณ์เกิดขึ้นไม่ได้กระจายในทางใดทางหนึ่งคุณจะต้องคำนวณสถานะใหม่ทุกครั้งที่มีเหตุการณ์เกิดขึ้นเนื่องจากสถานะสุดท้ายไม่ได้เป็นเพียงสถานะเริ่มต้นรวมถึงการเปลี่ยนแปลงต่อเนื่อง และแม้ว่าการเปลี่ยนแปลงนั้นเป็นแบบกระจายคุณมักต้องการเปลี่ยนสถานะเป็นสถานะถัดไปอย่างต่อเนื่องเนื่องจากคุณต้องการหยุดกระบวนการของคุณให้เร็วที่สุดเมื่อถึงสถานะที่กำหนดและเนื่องจากคุณต้องคำนวณสถานะถัดไปเพื่อพิจารณาว่า ใหม่เป็นรัฐที่ต้องการ

ในการเขียนโปรแกรมการทำงานการเปลี่ยนแปลงสถานะมักจะแสดงโดยการเรียกใช้ฟังก์ชันและ / หรือพารามิเตอร์ฟังก์ชัน

เนื่องจากคุณไม่สามารถคาดการณ์ได้ว่าสถานะสุดท้ายจะถูกคำนวณเมื่อใดคุณไม่ควรใช้ฟังก์ชันเรียกซ้ำแบบไม่หาง กระแสของรัฐซึ่งในแต่ละรัฐจะขึ้นอยู่กับก่อนหน้านี้อาจเป็นทางเลือกที่ดี

ดังนั้นในกรณีของคุณฉันจะตอบคำถามด้วยรหัสต่อไปนี้ใน 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 

ซึ่งสามารถให้เช่น (ฉันง่ายผลลัพธ์):

0.0
0.3
0.2
0.5
0.8

val states: Stream[Double] = ... เป็นบรรทัดที่คำนวณสถานะที่ต่อเนื่องกัน

องค์ประกอบแรกของสตรีมนี้คือสถานะเริ่มต้นของระบบ zipรวมกระแสของรัฐเข้ากับกระแสของเหตุการณ์ให้เป็นคู่ขององค์ประกอบคู่เดียวโดยแต่ละคู่จะเป็น (สถานะเหตุการณ์) mapแปลงแต่ละคู่ให้เป็นค่าเดียวที่เป็นสถานะใหม่ซึ่งคำนวณเป็นฟังก์ชันของสถานะเก่าและเหตุการณ์ที่เกี่ยวข้อง สถานะใหม่จึงเป็นสถานะที่คำนวณก่อนหน้านี้รวมทั้งเหตุการณ์ที่เกี่ยวข้องที่ "แก้ไข" รัฐ

โดยพื้นฐานแล้วคุณกำหนดกระแสที่ไม่มีที่สิ้นสุดของรัฐแต่ละรัฐใหม่เป็นหน้าที่ของรัฐที่คำนวณล่าสุดและเหตุการณ์ใหม่ เนื่องจากลำธารขี้เกียจใน Scala (ในหมู่อื่น ๆ ) มีการคำนวณตามความต้องการเท่านั้นดังนั้นคุณไม่จำเป็นต้องคำนวณสถานะที่ไร้ประโยชน์และคุณสามารถคำนวณสถานะได้มากเท่าที่คุณต้องการ

หากคุณสนใจเฉพาะในสถานะแรกที่เคารพภาคแสดงให้แทนที่บรรทัดสุดท้ายของรหัสโดย:

states find predicate get

สิ่งที่ดึง:

res7: Double = 1.1

คุณสามารถให้ข้อมูลเชิงลึกเกี่ยวกับบรรทัดที่ใช้เวทมนตร์:val states: Stream[Double]...
Bobby Marinoff

แน่ใจ โปรดดูที่การแก้ไขของฉัน
mgoeminne

1

คุณบอกว่าคุณมี 2 ฟังก์ชั่น:

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

และถ้าฉันเข้าใจคุณถูกต้องแล้วis_readyก็ค่อนข้างแพงดังนั้นคุณไม่ต้องการทำเช่นนั้นสำหรับทุกเหตุการณ์ที่เปลี่ยนแปลงไปเรื่อย ๆ

สิ่งที่คุณต้องการคือฟังก์ชั่นใช้ DataStructure เริ่มต้นและควบแน่นเข้าสู่สถานะที่เรียบง่ายและฟังก์ชั่นที่ใช้สถานะย่อการเปลี่ยนแปลงและส่งออกสถานะย่อใหม่

สมมติว่า DataStructure เป็น triplet x,y,zและคุณกำลังรอให้ x, y และ z เป็นจำนวนเฉพาะ สถานะย่อของคุณอาจเป็นเซตที่ x, y, z ไม่ได้เป็นไพร์ม การเปลี่ยนแปลงที่ทำให้ x prime ลบ x จากชุด การเปลี่ยนแปลงที่ทำให้ x not prime เพิ่ม x เข้ากับชุด (หากไม่มี) DataStructure พร้อมแล้วชุดจะว่างเปล่า

แนวคิดก็คือการปรับปรุงสถานะย่อมีราคาถูกกว่าการอัปเดตโครงสร้างข้อมูลและการคำนวณ is_ready ตั้งแต่เริ่มต้น

หมายเหตุ: วิธีที่ดีกว่านี้คือการติดตามว่า x, y, z ที่ใดที่ถูกตรวจสอบว่ามีความสำคัญและอยู่ที่ไหน สำหรับการเปลี่ยนแปลงทุกครั้งคุณจะตั้งค่าสถานะฟิลด์ที่เกี่ยวข้องตามที่ไม่ได้ตรวจสอบ จากนั้นเมื่อ is_ready ถูกเรียกให้คุณตรวจสอบและจดจำ นี่จะดีกว่าถ้าคุณไม่ได้ตรวจสอบ is_ready หลังจากการเปลี่ยนแปลงทุกครั้งเนื่องจาก x อาจเปลี่ยนแปลงได้หลายครั้งและคุณจะตรวจสอบเฉพาะ Prime ครั้งเดียว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.