อินเทอร์เฟซที่ไม่มีผลข้างเคียงที่ด้านบนของไลบรารี stateful


16

ในการให้สัมภาษณ์กับ John Hughesที่ซึ่งเขาพูดเกี่ยวกับ Erlang และ Haskell เขามีดังต่อไปนี้ที่จะพูดเกี่ยวกับการใช้ห้องสมุด stateful ใน Erlang:

ถ้าฉันต้องการใช้ไลบรารี stateful ฉันมักจะสร้างอินเทอร์เฟซที่ไม่มีผลข้างเคียงเพื่อให้ฉันสามารถใช้อย่างปลอดภัยในส่วนที่เหลือของรหัสของฉัน

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


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

คำตอบ:


12

(ฉันไม่รู้ Erlang และฉันไม่สามารถเขียน Haskell ได้ แต่ฉันคิดว่าฉันสามารถตอบได้)

ในการสัมภาษณ์นั้นตัวอย่างของการสร้างห้องสมุดแบบสุ่มตัวเลขจะได้รับ นี่เป็นอินเทอร์เฟซที่เป็นไปได้:

# create a new RNG
var rng = RNG(seed)

# every time we call the next(ceil) method, we get a new random number
print rng.next(10)
print rng.next(10)
print rng.next(10)

5 2 7การส่งออกอาจจะ สำหรับคนที่ชอบความไม่เปลี่ยนรูปนี้เป็นสิ่งที่ผิดธรรมดา! มันควรจะเป็น5 5 5เพราะเราเรียกว่าเมธอดบนวัตถุเดียวกัน

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

let rng = RNG(seed)
let n : rng = rng in
  print n
  let n : rng = rng in
    print n
    let n : rng in
      print n

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

การใช้สิ่งนี้ในภาษาที่เป็นรัฐนั้นไม่ซับซ้อน ตัวอย่างเช่น:

import scala.util.Random
import scala.collection.immutable.LinearSeq

class StatelessRNG (private val statefulRNG: Random, bound: Int) extends LinearSeq[Int] {
  private lazy val next = (statefulRNG.nextInt(bound), new StatelessRNG(statefulRNG, bound))

  // the rest is just there to satisfy the LinearSeq trait
  override def head = next._1
  override def tail = next._2
  override def isEmpty = false
  override def apply(i: Int): Int = throw new UnsupportedOperationException()
  override def length = throw new UnsupportedOperationException()
}

// print out three nums
val rng = new StatelessRNG(new Random(), 10)
rng.take(3) foreach (n => println(n))

เมื่อคุณเพิ่มน้ำตาล syntactic เล็กน้อยเพื่อให้รู้สึกเหมือนเป็นรายการสิ่งนี้เป็นสิ่งที่ดีทีเดียว


1
สำหรับตัวอย่างแรกของคุณ: rnd.next (10) สร้างค่าที่แตกต่างกันทุกครั้งที่ไม่เกี่ยวข้องกับการเปลี่ยนแปลงไม่ได้มากเท่าที่มันเกี่ยวข้องกับนิยามของฟังก์ชัน: ฟังก์ชันต้องเป็น 1 ต่อ 1 (+1 แม้ว่าสิ่งที่ดี)
Steven Evers

ขอบคุณ! นั่นเป็นคำอธิบายและตัวอย่างที่ดีและชัดเจน
เบต้า

1

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

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

การทดสอบสารสีน้ำเงินที่มีประโยชน์ที่ฉันใช้:

  • ถ้าจำเป็นต้องเรียกใช้ฟังก์ชัน A ก่อนฟังก์ชัน B ดังนั้น A จะสร้างสถานะภายนอกที่ขึ้นอยู่กับ B
  • ถ้าฟังก์ชั่นที่ฉันกำลังเขียนไม่สามารถจดบันทึกได้มันจะขึ้นอยู่กับสถานะที่ไม่แน่นอนภายนอก (การบันทึกช่วยจำอาจไม่ใช่ความคิดที่ดีเนื่องจากความกดดันของหน่วยความจำ แต่ควรเป็นไปได้)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.