การประเมินความเกียจคร้าน
กระตือรือร้น vs เข้มงวด vs ไม่เข้มงวด vs ขี้เกียจ — ทั้งหมดหมายความว่าอย่างไร?
แทะ: อาหารชิ้นเล็ก ๆ ที่ถูกกัด ในการคำนวณ: ข้อมูลครึ่งไบต์ ทุกคำที่แทะจะอธิบายแนวคิดด้านวิทยาการคอมพิวเตอร์หรือวิศวกรรมซอฟต์แวร์ได้ภายในห้านาที
ทุกภาษาโปรแกรมจำเป็นต้องเลือกว่าจะประเมินนิพจน์ในลำดับใด เกือบทั้งหมดใช้การประเมินอย่างเข้มงวด : ก่อนประเมินนิพจน์ นิพจน์ย่อยทั้งหมดจะได้รับการประเมินก่อน ตัวอย่างเช่น อาร์กิวเมนต์ของฟังก์ชันจะถูกประเมินก่อนฟังก์ชัน หลายคนใช้การประเมินความกระตือรือร้นเป็นคำพ้องความหมายสำหรับการประเมินอย่างเข้มงวด
ส่วนน้อยที่เด่นชัดที่สุดคือ Haskell ใช้ การประเมิน แบบไม่เข้มงวด การประเมินแบบไม่เข้มงวดถูกกำหนดโดยสิ่งที่ไม่ใช่: กลยุทธ์การประเมินใดๆ ที่ไม่ประเมินนิพจน์ย่อยทั้งหมดก่อนหรือไม่ประเมินเลย ถือว่าไม่เข้มงวด การประเมินแบบขี้เกียจเป็นกลยุทธ์เฉพาะของการประเมินแบบไม่เข้มงวด
มาลองขจัดความสับสนด้วยการประเมินแบบขี้เกียจ
การประเมินผลที่เข้มงวด
เช่นเดียวกับภาษาส่วนใหญ่ Python ใช้การประเมินอย่างเข้มงวดสำหรับแอปพลิเคชันฟังก์ชัน:
def log(value):
if log_level == INFO:
print(value)
log(42 + 33)
วิธีง่ายๆ ในการระบุการประเมินที่เข้มงวดคือการจินตนาการว่าจะเกิดอะไรขึ้นหากนิพจน์ย่อยหนึ่งเข้าสู่การวนซ้ำไม่สิ้นสุด หากนิพจน์ระดับบนสุดไม่เคยได้รับการประเมิน แสดงว่าการประเมินนั้นเข้มงวด
การประเมินแบบเข้มงวดไม่ได้จำกัดว่านิพจน์ย่อยของลำดับใดจะได้รับการประเมิน เพียงแต่ว่าทั้งหมดจะได้รับการประเมินก่อนนิพจน์ระดับบนสุด สามารถเรียงจากซ้ายไปขวาได้เหมือนใน Python ขวาไปซ้ายเหมือนใน OCaml หรือไม่ได้กำหนดเช่นใน C.
การประเมินผลที่ไม่เข้มงวด
การประเมินแบบไม่เคร่งครัดไม่ใช่เรื่องผิดปกติอย่างที่อาจปรากฏในตอนแรกif...else...
ตัวอย่างเช่น การประเมินแบบไม่เข้มงวด
ลองใช้เคล็ดลับของเราเพื่อค้นหาการประเมินที่เข้มงวด โปรแกรม Python ต่อไปนี้ยุติลงแม้ว่าelse
สาขาจะวนซ้ำตลอดไป:
if True:
print("I am True")
else:
while True:
print("I never terminate")
การดำเนินการบูลีนลัดวงจรก็ไม่เข้มงวดเช่นกัน or
นิพจน์ต่อไปนี้ สิ้นสุดลง
True or infinite_loop()
นั่นเป็นสิ่งที่ดีสำหรับบิวด์อิน แต่ถ้าคุณต้องการบางสิ่งที่ถูกต้อง...
เราสามารถจำลองการประเมินที่ไม่เข้มงวดโดยใช้thunks :ฟังก์ชันโดยไม่มีข้อโต้แย้ง Thunks เลื่อนการประเมินจนกว่าจะจำเป็น
def log(value_thunk):
if log_level == INFO:
print(value_thunk())
log(lambda: 42 + 33)
ขี้เกียจประเมิน
ปัญหาที่น่ารำคาญของ thunks คือพวกมันถูกประเมินใหม่ทุกครั้งที่ถูกเรียก
def log(value_thunk):
if log_level == INFO:
print(value_thunk())
send_to_log_aggregator(value_thunk())
- ไม่เคยทำงานที่ไม่จำเป็น โดยเลื่อนการประเมินจนกว่าจะจำเป็น
- มันไม่เคยทำงานซ้ำ โดยแคชผลลัพธ์แรก
ภาษาโปรแกรมเชิงฟังก์ชัน Haskell หลีกหนีจากการใช้การประเมินแบบขี้เกียจโดยค่าเริ่มต้นเพราะมันบริสุทธิ์: ฟังก์ชันของ Haskell ไม่มีผลข้างเคียง
ภาษาที่เข้มงวดเช่น OCaml, F# และ C# รองรับการประเมินแบบ Lazy โดยใช้lazy
คีย์เวิร์ดหรือLazy
วัตถุ
นี่คือตัวอย่างการใช้งานLazy
วัตถุใน Python:
class Lazy:
def __init__(self, thunk):
self._thunk = thunk
self._is_cached = False
self._value = None
@property
def value(self):
if not self._is_cached:
self._value = self._thunk()
self._is_cached = True
return self._value
การประเมินอย่างเข้มงวดเป็นเรื่องปกติมากกว่าเพราะเข้าใจได้ง่ายกว่าและแก้ไขจุดบกพร่องได้ง่ายกว่า ที่กล่าวว่าการประเมินที่ไม่เข้มงวดนั้นขาดไม่ได้ในบางสถานการณ์
Power Seriousของ Doug McIlroy แสดงให้เห็นสิ่งนี้อย่างสวยงาม: การใช้งาน 10 บรรทัดที่สมบูรณ์ของชุดพลังงานที่ไม่มีที่สิ้นสุดใน Haskell
นี่คือหนึ่งบรรทัดจากนั้น:
series f = f : repeat 0
แสดง ถึงรายการ ที่series 13
ไม่มีที่สิ้นสุด [13, 0, 0, 0,...]
แต่เนื่องจากมันขี้เกียจ องค์ประกอบจึงถูกคำนวณเมื่อจำเป็นเท่านั้น วิธีหนึ่งในการขอองค์ประกอบคือการใช้ฟังก์ชันtake n
ซึ่งรับn
องค์ประกอบจากด้านหน้าของรายการ กล่าวอีกนัยหนึ่งให้take 4 (series 13)
ผลตอบแทน[13, 0, 0, 0]
ณ จุดนี้ คุณอาจคิดว่ารายการขี้เกียจเป็นเพียงตัววนซ้ำ เช่นเดียวกับรายการขี้เกียจ ตัววนซ้ำจะถูกคำนวณตามความต้องการและสามารถใช้เพื่อแสดงโครงสร้างข้อมูลที่ไม่สิ้นสุด นี่คือseries
ตัวสร้าง Python:
def series(f: int) -> Iterable[int]:
yield f
while True:
yield 0
myList = series 10 --list thunk is created
some = take 5 myList --eval first 5 elements
somemore = take 20 myList --eval 15 more elements
กลับสู่พลังที่จริงจัง Doug McIlroy ใช้รายการขี้เกียจเพื่อแสดงค่าสัมประสิทธิ์ของอนุกรมกำลังที่ไม่มีที่สิ้นสุด กล่าวอีกนัยหนึ่ง รายการ[1, 2, 3, 4, 0, ...]
แสดงถึงอนุกรมกำลัง 1 + 2 + 3² + 4³ + ...
นี่คือการเพิ่มสองชุดพลังงาน:
(f:ft) + (g:gt) = f+g : ft+gt
เดี๋ยวก่อน — นิยามแบบเรียกซ้ำโดยไม่มีกรณีฐาน? นั่นเป็นไปได้ด้วยการประเมินแบบขี้เกียจ เมื่อต้องการองค์ประกอบแรกของผลรวมของสองชุด นิพจน์f+g
จะต้องการf
และg
ซึ่งทริกเกอร์การประเมินองค์ประกอบแรกของรายการซ้ายและขวาผ่านการจับคู่รูปแบบขี้เกียจ(f:ft)
และ (g:gt)
การประเมินเพิ่มเติมft+gt
จะล่าช้าออกไปจนกว่าจะต้องมีองค์ประกอบเพิ่มเติม กระบวนการนี้จะเกิดขึ้นซ้ำเมื่อมีการขอองค์ประกอบที่สอง เป็นต้น
รายการที่ไม่สิ้นสุดแบบ Lazy ทำให้ทุกอย่างง่ายขึ้น: ไม่จำเป็นต้องตรวจสอบว่ารายการสิ้นสุดเมื่อใดเพราะไม่ได้ตรวจสอบ และไม่จำเป็นต้องใช้กรณีฐานแบบเรียกซ้ำเนื่องจากเราจะเรียกซ้ำเมื่อจำเป็นเท่านั้น
สรุป
แทะนี้กล่าวถึงคำศัพท์มากมาย
ฉันแบ่งกลยุทธ์การประเมิน ออก เป็นสองประเภท: แบบเข้มงวดและไม่เข้มงวด
ในการประเมินอย่างเข้มงวด นิพจน์ย่อยของนิพจน์ทั้งหมดจะได้รับการประเมินก่อน การประเมินแบบไม่เคร่งครัดครอบคลุมกลยุทธ์ทั้งหมดที่ทำอย่างอื่น รวมทั้งไม่ประเมินข้อโต้แย้งบางข้อเลย
การประเมินแบบเข้มงวดบางครั้งเรียกว่าการประเมินแบบกระตือรือร้น ซึ่งฟังดูคล้ายกับการประเมินแบบขี้เกียจ อย่างไรก็ตาม การประเมินแบบขี้เกียจเป็นเพียงหนึ่งในกลยุทธ์ที่ไม่เข้มงวดที่เป็นไปได้มากมาย การประเมินแบบ Lazy โดดเด่นกว่าเพราะมันแคชผลลัพธ์เพิ่มเติม แต่จะเป็นไปได้ก็ต่อเมื่อนิพจน์ไม่มีผลข้างเคียง
สุดท้าย การประเมินแบบเข้มงวดและไม่เข้มงวดมักจะใช้ร่วมกันในภาษาโปรแกรมเดียว และแม้แต่ในนิพจน์เดียวกัน ดังนั้นจุดตัดในภาพ ตัวอย่างคือif...else...
: การประเมินเงื่อนไขนั้นเข้มงวด แต่การประเมินทั้งสองอนุประโยคนั้นไม่เข้มงวด
ขอบคุณที่อ่าน! ฉันตั้งใจจะเขียนหนึ่งแทะทุกเดือน สำหรับข้อมูลเพิ่มเติมสมัครรับจดหมายข่าวของฉันและ ติดตามฉัน บนTwitter
เผยแพร่ครั้งแรกที่https://getcode.substack.com