ฟังก์ชั่น (ECMAScript)
สิ่งที่คุณต้องมีคือนิยามฟังก์ชันและการเรียกใช้ฟังก์ชัน คุณไม่ต้องการการแยกย่อยเงื่อนไขเงื่อนไขตัวดำเนินการหรือฟังก์ชันบิวด์อิน ฉันจะสาธิตการใช้งานโดยใช้ ECMAScript
อันดับแรกให้กำหนดสองฟังก์ชั่นที่เรียกว่าและtrue
false
เราสามารถกำหนดพวกเขาในแบบที่เราต้องการพวกเขาเป็นแบบอย่างโดยพลการ แต่เราจะกำหนดพวกเขาในวิธีพิเศษมากซึ่งมีข้อได้เปรียบบางประการที่เราจะเห็นในภายหลัง:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
tru
เป็นฟังก์ชั่นที่มีสองพารามิเตอร์ซึ่งเพียงละเว้นอาร์กิวเมนต์ที่สองและส่งกลับค่าแรก fls
เป็นฟังก์ชั่นที่มีพารามิเตอร์สองตัวที่ไม่ต้องสนใจอาร์กิวเมนต์แรกและส่งคืนพารามิเตอร์ตัวที่สอง
ทำไมเราเข้ารหัสtru
และfls
วิธีนี้ ด้วยวิธีนี้ฟังก์ชั่นทั้งสองไม่เพียงแสดงถึงแนวคิดทั้งสองtrue
และfalse
ในเวลาเดียวกันพวกเขายังแสดงแนวคิดของ "ตัวเลือก" ในคำอื่น ๆ พวกเขายังเป็นif
/ then
/ else
แสดงออก! เราประเมินif
สภาพและส่งผ่านthen
บล็อกและelse
บล็อกเป็นอาร์กิวเมนต์ หากเงื่อนไขประเมินtru
เป็นจะส่งคืนthen
บล็อกหากประเมินfls
เป็นเงื่อนไขจะส่งคืนelse
บล็อก นี่คือตัวอย่าง:
tru(23, 42);
// => 23
สิ่งนี้จะส่งคืน23
และสิ่งนี้:
fls(23, 42);
// => 42
ผลตอบแทน42
เช่นเดียวกับที่คุณคาดหวัง
มีริ้วรอย แต่:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
มันพิมพ์ทั้ง then branch
และelse branch
! ทำไม?
มันส่งคืนค่าส่งคืนของอาร์กิวเมนต์แรก แต่ประเมินค่าอาร์กิวเมนต์ทั้งสองเนื่องจาก ECMAScript มีความเข้มงวดและมักจะประเมินอาร์กิวเมนต์ทั้งหมดไปยังฟังก์ชันก่อนเรียกฟังก์ชัน IOW: มันประเมินค่าอาร์กิวเมนต์แรกซึ่งก็คือconsole.log("then branch")
ส่งคืนundefined
และมีผลข้างเคียงของการพิมพ์then branch
ไปยังคอนโซลและประเมินผลอาร์กิวเมนต์ที่สองซึ่งส่งกลับundefined
และพิมพ์ไปยังคอนโซลเป็นผลข้างเคียง undefined
จากนั้นก็จะส่งกลับคนแรก
ใน calcul-แคลคูลัสซึ่งการเข้ารหัสนี้ถูกประดิษฐ์ขึ้นนั่นไม่ใช่ปัญหา: calcul-แคลคูลัสนั้นบริสุทธิ์ซึ่งหมายความว่ามันไม่มีผลข้างเคียงใด ๆ ดังนั้นคุณจะไม่สังเกตเห็นว่าอาร์กิวเมนต์ที่สองได้รับการประเมินเช่นกัน นอกจากนี้ calcul-แคลคูลัสขี้เกียจ (หรืออย่างน้อยก็มักจะถูกประเมินภายใต้คำสั่งปกติ) ความหมายมันไม่ได้ประเมินอาร์กิวเมนต์ที่ไม่ต้องการ ดังนั้น IOW: ในλ-แคลคูลัสอาร์กิวเมนต์ที่สองจะไม่ถูกประเมินและถ้าเป็นเราจะไม่สังเกตเห็น
อย่างไรก็ตาม ECMAScript นั้นเข้มงวดมากเช่นจะประเมินอาร์กิวเมนต์ทั้งหมดเสมอ ดีจริงไม่ได้เสมอที่: if
/ then
/ else
ตัวอย่างเช่นเพียงประเมินthen
สาขาถ้าเงื่อนไขเป็นtrue
และมีเพียงประเมินสาขาถ้าเงื่อนไขเป็นelse
และเราต้องการที่จะทำซ้ำพฤติกรรมนี้กับเราfalse
iff
โชคดีที่แม้ว่า ECMAScript จะไม่ขี้เกียจ แต่ก็มีวิธีที่จะชะลอการประเมินผลของชิ้นส่วนของรหัส แต่วิธีเดียวกันกับที่เกือบทุกภาษาอื่นจะทำได้: ห่อไว้ในฟังก์ชั่นและถ้าคุณไม่เคยเรียกใช้ฟังก์ชั่นนั้น ไม่เคยถูกประหารชีวิต
ดังนั้นเราจึงตัดทั้งสองบล็อกในฟังก์ชันและเมื่อสิ้นสุดการเรียกฟังก์ชันที่ส่งคืน:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
พิมพ์then branch
และ
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
else branch
พิมพ์
เราสามารถใช้แบบดั้งเดิมif
/ then
/ else
ด้วยวิธีนี้:
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
อีกครั้งเราจำเป็นต้องมีการตัดฟังก์ชั่นพิเศษบางอย่างเมื่อเรียกใช้iff
ฟังก์ชั่นและวงเล็บเรียกฟังก์ชันพิเศษในคำจำกัดความของiff
ด้วยเหตุผลเดียวกันกับข้างต้น:
const iff = (cnd, thn, els) => cnd(thn, els)();
iff(tru, () => console.log("then branch"), () => console.log("else branch"));
// then branch
iff(fls, () => console.log("then branch"), () => console.log("else branch"));
// else branch
or
ตอนนี้เรามีทั้งสองคำจำกัดความของเราสามารถดำเนินการได้ อันดับแรกเราดูที่ตารางความจริงสำหรับor
: ถ้าตัวถูกดำเนินการตัวแรกเป็นความจริงผลลัพธ์ของการแสดงออกจะเหมือนกับตัวถูกดำเนินการแรก มิฉะนั้นผลลัพธ์ของนิพจน์คือผลลัพธ์ของตัวถูกดำเนินการที่สอง กล่าวโดยย่อ: ถ้าตัวถูกดำเนินการแรกคือtrue
เราส่งคืนตัวถูกดำเนินการแรกมิฉะนั้นเราจะส่งคืนตัวถูกดำเนินการที่สอง:
const orr = (a, b) => iff(a, () => a, () => b);
ตรวจสอบว่ามันใช้งานได้:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
ที่ดี! อย่างไรก็ตามคำจำกัดความนั้นดูน่าเกลียดเล็กน้อย จำไว้tru
และfls
ทำตัวเหมือนมีเงื่อนไขด้วยตัวเองแล้วดังนั้นจึงไม่จำเป็นต้องทำiff
ดังนั้นจึงรวมฟังก์ชันทั้งหมดไว้ด้วย:
const orr = (a, b) => a(a, b);
ที่นั่นคุณมี: or
(รวมถึงตัวดำเนินการบูลีนอื่น ๆ ) กำหนดโดยไม่มีอะไรนอกจากนิยามฟังก์ชันและการเรียกใช้ฟังก์ชันในเพียงไม่กี่บรรทัด:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els,
orr = (a , b ) => a(a, b),
nnd = (a , b ) => a(b, a),
ntt = a => a(fls, tru),
xor = (a , b ) => a(ntt(b), b),
iff = (cnd, thn, els) => cnd(thn, els)();
น่าเสียดายที่การใช้งานนี้ค่อนข้างไร้ประโยชน์: ไม่มีฟังก์ชั่นหรือตัวดำเนินการใน ECMAScript ที่ส่งคืนtru
หรือfls
พวกเขาทั้งหมดส่งคืนtrue
หรือfalse
ดังนั้นเราจึงไม่สามารถใช้พวกเขากับฟังก์ชั่นของเรา แต่ยังมีอีกมากที่เราสามารถทำได้ ตัวอย่างเช่นนี่เป็นการใช้งานของรายการที่ลิงก์โดยลำพัง:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
วัตถุ (Scala)
คุณอาจสังเกตเห็นสิ่งที่แปลกประหลาด: tru
และfls
มีบทบาทสองอย่างพวกเขาทำหน้าที่ทั้งสองเป็นค่าข้อมูลtrue
และfalse
แต่ในเวลาเดียวกันพวกเขายังทำหน้าที่เป็นนิพจน์เงื่อนไข พวกมันคือข้อมูลและพฤติกรรมรวมเข้าเป็นหนึ่งเดียว ... อืม ... "สิ่งของ" ... หรือ (กล้าพูด) วัตถุ !
แน่นอนtru
และfls
เป็นวัตถุ และหากคุณเคยใช้ Smalltalk, Self, Newspeak หรือภาษาเชิงวัตถุอื่น ๆ คุณจะสังเกตเห็นว่าพวกเขาใช้ booleans ในลักษณะเดียวกัน ฉันจะแสดงให้เห็นถึงการดำเนินการดังกล่าวใน Scala:
sealed abstract trait Buul {
def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): T
def &&&(other: ⇒ Buul): Buul
def |||(other: ⇒ Buul): Buul
def ntt: Buul
}
case object Tru extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): U = thn
override def &&&(other: ⇒ Buul) = other
override def |||(other: ⇒ Buul): this.type = this
override def ntt = Fls
}
case object Fls extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): V = els
override def &&&(other: ⇒ Buul): this.type = this
override def |||(other: ⇒ Buul) = other
override def ntt = Tru
}
object BuulExtension {
import scala.language.implicitConversions
implicit def boolean2Buul(b: ⇒ Boolean) = if (b) Tru else Fls
}
import BuulExtension._
(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3
BTW นี้เป็นสาเหตุที่การแทนที่เงื่อนไขด้วย Polymorphism Refactoring ทำงานได้ตลอดเวลา: คุณสามารถแทนที่เงื่อนไขใด ๆ ในโปรแกรมของคุณด้วยการส่งข้อความ polymorphic เพราะในขณะที่เราเพิ่งแสดง ภาษาเช่น Smalltalk, Self และ Newspeak เป็นข้อพิสูจน์ที่มีอยู่เพราะภาษาเหล่านั้นไม่มีเงื่อนไข (พวกมันไม่มีลูป, BTW หรือโครงสร้างการควบคุมภาษาในตัวใด ๆยกเว้นการส่งข้อความแบบ polymorphic หรือการเรียกเมธอดเสมือนจริง)
การจับคู่รูปแบบ (Haskell)
นอกจากนี้คุณยังสามารถกำหนดor
โดยใช้การจับคู่รูปแบบหรือบางอย่างเช่นนิยามฟังก์ชันบางส่วนของ Haskell:
True ||| _ = True
_ ||| b = b
แน่นอนการจับคู่รูปแบบเป็นรูปแบบของการดำเนินการตามเงื่อนไข แต่แล้วอีกครั้งดังนั้นการจัดส่งข้อความเชิงวัตถุ