ความแตกต่างระหว่าง State, ST, IORef และ MVar


91

ฉันกำลังทำงานผ่านเขียนเองโครงการใน 48 ชั่วโมง (ฉันถึงประมาณ 85hrs) และผมเคยไปเป็นส่วนหนึ่งเกี่ยวกับการเพิ่มและการกำหนดตัวแปร ในบทนี้มีการกระโดดข้ามแนวความคิดครั้งใหญ่และฉันหวังว่ามันจะทำในสองขั้นตอนโดยมีการปรับโครงสร้างที่ดีระหว่างนั้นแทนที่จะกระโดดตรงไปที่ทางออกสุดท้าย อย่างไรก็ตาม…

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

สิ่งเหล่านี้คืออะไรและแตกต่างกันอย่างไร?


โดยเฉพาะประโยคเหล่านี้ไม่สมเหตุสมผล:

แต่เราใช้คุณลักษณะที่เรียกว่าเธรดสถานะโดยให้ Haskell จัดการสถานะรวมให้เรา สิ่งนี้ช่วยให้เราปฏิบัติกับตัวแปรที่เปลี่ยนแปลงได้เหมือนกับที่เราทำในภาษาโปรแกรมอื่น ๆ โดยใช้ฟังก์ชันเพื่อรับหรือตั้งค่าตัวแปร

และ

โมดูล IORef ช่วยให้คุณสามารถใช้ตัวแปร stateful ภายใน monad

ทั้งหมดนี้ทำให้สายtype ENV = IORef [(String, IORef LispVal)]สับสน - ทำไมต้องเป็นครั้งที่สองIORef? อะไรจะพังถ้าฉันจะเขียนtype ENV = State [(String, LispVal)]แทน

คำตอบ:


119

The State Monad: รูปแบบของรัฐที่เปลี่ยนแปลงไม่ได้

State monad เป็นสภาพแวดล้อมที่ใช้งานได้จริงสำหรับโปรแกรมที่มีสถานะโดยใช้ API แบบง่ายๆ:

  • ได้รับ
  • ใส่

เอกสารในแพคเกจของ MTL

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

ST monad และ STRefs

ST monad เป็นลูกพี่ลูกน้องที่ถูก จำกัด ของ IO monad

อนุญาตให้ใช้สถานะที่เปลี่ยนแปลงได้ตามอำเภอใจนำไปใช้เป็นหน่วยความจำที่เปลี่ยนแปลงได้จริงบนเครื่อง API ถูกสร้างขึ้นอย่างปลอดภัยในโปรแกรมที่ไม่มีผลข้างเคียงเนื่องจากพารามิเตอร์ประเภท rank-2 ป้องกันไม่ให้ค่าที่ขึ้นอยู่กับสถานะที่เปลี่ยนแปลงไม่ได้จากการหลบหนีขอบเขตโลคัล

จึงช่วยให้สามารถควบคุมความผันแปรในโปรแกรมอื่น ๆ ที่บริสุทธิ์ได้

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

API หลัก:

  • Control.Monad.ST
  • runST - เริ่มการคำนวณเอฟเฟกต์หน่วยความจำใหม่
  • และSTRefs : ชี้ไปยังเซลล์ที่เปลี่ยนแปลงได้ (ในเครื่อง)
  • อาร์เรย์ที่ใช้ ST (เช่นเวกเตอร์) เป็นเรื่องปกติ

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

IORef: STRef ใน IO

นี่คือ STRefs (ดูด้านบน) ใน IO monad พวกเขาไม่มีการรับประกันความปลอดภัยเช่นเดียวกับ STRef เกี่ยวกับท้องที่

MVars: IORefs พร้อมล็อค

เช่นเดียวกับ STRefs หรือ IORefs แต่มีการล็อคเพื่อการเข้าถึงพร้อมกันอย่างปลอดภัยจากหลายเธรด IORefs และ STRefs ปลอดภัยเฉพาะในการตั้งค่าแบบมัลติเธรดเมื่อใช้atomicModifyIORef(การดำเนินการอะตอมเปรียบเทียบและสลับ) MVars เป็นกลไกทั่วไปสำหรับการแบ่งปันสถานะที่เปลี่ยนแปลงได้อย่างปลอดภัย

โดยทั่วไปใน Haskell ให้ใช้ MVars หรือ TVars (เซลล์ที่เปลี่ยนแปลงได้ตาม STM) มากกว่า STRef หรือ IORef


3
M ใน MVars และ T ใน TVars คืออะไร? ฉันเดาว่า "กลายพันธุ์", "ธุรกรรม" น่าสนใจว่า ST หมายถึง State Thread อย่างไร
CMCDragonkai

10
ทำไมคุณถึงบอกว่าMVarควรจะชอบมากกว่าSTRef? STRefรับประกันว่าเธรดเดียวเท่านั้นที่อาจกลายพันธุ์ได้ (และ IO ประเภทอื่นอาจไม่เกิดขึ้น) - แน่นอนว่าจะดีกว่าถ้าฉันไม่ต้องการการเข้าถึงพร้อมกันในสถานะที่เปลี่ยนแปลง
Benjamin Hodgson

@CMCDragonkai ฉันคิดเสมอว่า M ย่อมาจาก mutex แต่ฉันไม่พบเอกสารใด ๆ
Andrew Thaddeus Martin

37

IORefตกลงฉันจะเริ่มต้นด้วย IORefให้ค่าที่ไม่แน่นอนใน IO monad เป็นเพียงการอ้างอิงถึงข้อมูลบางส่วนและเช่นเดียวกับการอ้างอิงใด ๆ มีฟังก์ชันที่ช่วยให้คุณสามารถเปลี่ยนแปลงข้อมูลที่อ้างถึงได้ ใน Haskell IOทุกฟังก์ชั่นผู้ที่ทำงานใน คุณอาจคิดว่ามันเหมือนกับฐานข้อมูลไฟล์หรือที่เก็บข้อมูลภายนอกอื่น ๆ - คุณสามารถรับและตั้งค่าข้อมูลในนั้นได้ แต่การทำเช่นนั้นต้องผ่าน IO เหตุผล IO เป็นสิ่งจำเป็นที่ทุกคนเป็นเพราะ Haskell เป็นบริสุทธิ์ ; คอมไพเลอร์ต้องการวิธีที่จะทราบว่าข้อมูลใดอ้างอิงถึงในช่วงเวลาใดเวลาหนึ่ง (อ่านบล็อกโพสต์"คุณสามารถคิดค้น monads" ของ sigfpe )

MVarโดยพื้นฐานแล้ว s จะเหมือนกับ IORef ยกเว้นความแตกต่างที่สำคัญสองประการ MVarเป็นแบบดั้งเดิมที่เกิดขึ้นพร้อมกันดังนั้นจึงออกแบบมาสำหรับการเข้าถึงจากหลายเธรด ข้อแตกต่างประการที่สองคือ an MVarเป็นกล่องที่เต็มหรือว่างเปล่า ดังนั้นในกรณีที่มีจุดIORef IntเสมอInt(หรืออยู่ล่างสุด) MVar IntอาจมีIntหรือว่างก็ได้ หากเธรดพยายามอ่านค่าจากค่าว่างเธรดMVarจะบล็อกจนกว่าMVarเธรดจะถูกเติมเต็ม (โดยเธรดอื่น) โดยทั่วไปแล้วMVar aจะเทียบเท่าIORef (Maybe a)กับความหมายพิเศษที่มีประโยชน์สำหรับการทำงานพร้อมกัน

Stateเป็น monad ที่ให้สถานะที่ไม่แน่นอนไม่จำเป็นต้องใช้กับ IO อันที่จริงมันมีประโยชน์อย่างยิ่งสำหรับการคำนวณที่บริสุทธิ์ หากคุณมีอัลกอริทึมที่ใช้สถานะ แต่ไม่IOเป็นเช่นนั้นStatemonad มักจะเป็นโซลูชันที่สวยงาม

นอกจากนี้ยังมีรุ่น monad StateTหม้อแปลงของรัฐ สิ่งนี้มักใช้เพื่อเก็บข้อมูลการกำหนดค่าโปรแกรมหรือสถานะประเภท "เกมโลก" ในแอปพลิเคชัน

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


17

คนอื่น ๆ ได้ทำสิ่งสำคัญ แต่เพื่อตอบคำถามโดยตรง:

ทั้งหมดนี้ทำให้ประเภทเส้นENV = IORef [(String, IORef LispVal)] สับสน ทำไมต้อง IORef ที่สอง อะไรจะพังถ้าฉันทำtype ENV = State [(String, LispVal)]แทน?

Lisp เป็นภาษาที่ใช้งานได้ซึ่งมีสถานะไม่แน่นอนและขอบเขตคำศัพท์ ลองนึกภาพคุณปิดตัวแปรที่ไม่แน่นอน ตอนนี้คุณได้มีการอ้างอิงถึงตัวแปรนี้แขวนอยู่รอบ ๆ ภายในฟังก์ชั่นอื่น ๆ บาง - พูด (pseudocode ใน Haskell (printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)สไตล์) ตอนนี้คุณมีสองฟังก์ชัน - หนึ่งพิมพ์ x และอีกชุดหนึ่งกำหนดค่า เมื่อคุณประเมินprintItคุณต้องการค้นหาชื่อของ x ในสภาพแวดล้อมเริ่มต้นที่printItกำหนดไว้ แต่คุณต้องการค้นหาค่าที่ชื่อถูกผูกไว้ในสภาพแวดล้อมที่printItถูกเรียก (after setItอาจถูกเรียกกี่ครั้งก็ได้ ).

มีหลายวิธีที่ทำให้ IORef ทั้งสองทำสิ่งนี้ได้ แต่คุณต้องการมากกว่าประเภทหลังที่คุณเสนอซึ่งไม่อนุญาตให้คุณแก้ไขค่าที่ชื่อถูกผูกไว้ในรูปแบบที่กำหนดขอบเขตคำศัพท์ Google "ปัญหา funargs" สำหรับประวัติศาสตร์ที่น่าสนใจมากมาย

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