TL;DR

บันไดขั้นต่อไป เพื่อพา /me สร้าง isPrime(n): Predicates

Predicates

หลังจากพูดถึง Booleans ไปแล้ว สิ่งต่อมา ก็คือสิ่งที่เรียกว่า “Predicates” จะพูดว่ามันคืออะไรก็ค่อนข้างยาก ดูเหมือนว่าคำนี้ใช้ในหลายๆความหมายและหลายๆสาขาวิชา /me เองก็สับสนอยู่เหมือนกัน

แต่ในที่นี้ Predicates หมายถึง ของ 2 อย่าง คือ

  • Boolean-valued function : ฟังก์ชั่นที่ return boolean
  • Characteristic function : ฟังก์ชั่นที่อธิบายความสัมพันธ์หรือลักษณะเฉพาะบางอย่างของสิ่งที่เราสนใจ

ตัวอย่างที่สำคัญ และเป็นฟังก์ชั่นที่จำเป็นสำหรับ isPrime() ก็คือ IS_ZERO(n) ซึ่งมีหน้าที่เช็คว่าตัวเลข n เท่ากับ 0 หรือไม่

def IS_ZERO(n)
   if n == 0
      true
   else
      false
   end
end

IS_ZERO(n) ถือเป็นสะพานชิ้นสำคัญที่เชื่อมความสัมพันธ์ของ numbers ไปเป็น boolean แน่นอนว่า ทุกคนสามารถ implement IS_ZERO ได้อย่างง่ายได้ในภาษาโปรแกรมมิ่งทั่วๆไป แต่สำหรับเงื่อนไขที่ใช้เฉพาะ functions เราสามารถเขียนมันขึ้นมาได้อย่างไร?

ก่อนอื่น คือการพิจารณาตัวเลขที่เราสร้างขึ้นมากันอีกครั้ง

const ZERO  = (p)=>(x)=>x;
const ONE   = (p)=>(x)=>p(x);
const TWO   = (p)=>(x)=>p(p(x));
const THREE = (p)=>(x)=>p(p(p(x)));

จะเห็นว่า ZERO เป็นเพียงตัวเลขเดียวที่ไม่เรียก function p และ /me สามารถใช้คุณสมบัตินี้ในการสร้าง IS_ZERO โดยกำหนดให้ p เป็นฟังก์ชั่นที่จะ return FALSE เสมอ เมื่อไรก็ตามที่ตัวเลขนั้นไม่ใช่ ZERO มันจะให้ค่าเป็น FALSE และในทางกลับกัน เราสามารถกำหนดให้ x มีค่าเป็น TRUE เมื่อ ZERO ทำการ return x เท่ากับว่า มันเป็นการ return TRUE ด้วยเช่นกัน

const IS_ZERO = function(n){
    return n((x)=>FALSE)(TRUE);
}

// Arrow function
const IS_ZERO = (n)=> n((x)=>FALSE)(TRUE);

ลองทดสอบอีกครั้ง

>> to_boolean(IS_ZERO[ZERO])
=> true

>> to_boolean(IS_ZERO[THREE])
=> false

ลองเอา IS_ZERO ไปใช้ใน isPrime(n)

const isPrime = (p)=>{
   for i in range(TWO,p):
        IF(IS_ZERO(p%i))( return "Not Prime")(null)
    return p
}

หลังจากได้ส่วนประกอบสำคัญอย่าง IS_ZERO(n) มาแล้ว

บันไดขั้นต่อมา คือ เครื่องหมายเปรียบเทียบ(Comparison Operators) มากกว่า, น้อยกว่า, มากกว่าเท่ากับ และน้อยกว่าเท่ากับ

ความจริงแล้ว การเปรียบเทียบก็เสมอการหยิบแอ๊ปเปิ้ลและส้มออกจากถุงพร้อมๆกัน ใครโดนหยิบจนเหลือถุงเปล่าก่อนก็เป็นฝ่ายพ่ายแพ้และถือว่ามีจำนวนน้อยกว่าไปโดยปริยาย

หรือพูดเป็นสมการคณิตศาสตร์ได้ว่า m <= n ก็ต่อเมื่อ m - n <= 0

ดังนั้นการสร้าง LESS_THAN_OR_EQUAL(m, n) ก็หนีไม่พ้นการใช้ SUBTRACT และการเทียบเท่ากับ ZERO แต่โลกที่เราสร้างขึ้นนี้ไม่มีสิ่งที่เรียกว่า จำนวนลบ ดังนั้น การเทียบน้อยกว่า ZERO จึงไม่มีความหมายใดๆ (ทุกตัวเลขที่ต่ำกว่า 0 จะได้ค่าเท่ากับ 0)

เราจึงสามารถสร้าง LESS_THAN_OR_EQUAL(m, n) ได้ดังนี้

const LESS_THAN_OR_EQUAL = (m)=>(n)=>IS_ZERO(SUBTRACT(m)(n))

และเมื่อ /me พบว่า not LESS_THAN_OR_EQUAL คือ GREATER_THAN

const GREATER_THAN = (m)=>(n)=>NOT(IS_ZERO(SUBTRACT(m)(n)))

สิ่งที่น่าคิดต่อมาคือ แล้ว “น้อยกว่าเฉยๆ” และ “มากกว่าเท่ากับ” จะสร้างขึ้นมายังไง? แต่ /me ยังไม่อับจนหนทาง เรายังมีคุณสมบัติสุดท้าย

คุณสมบัติสลับด้าน เมื่อ x < y แล้ว y > x

โดยทั่วไปแล้วเรามักใช้สมการแก้ไขปัญหา สำหรับสมการ ไม่ว่าเราสลับด้านมันยังไง มันก็ยังคงได้สิ่งเดียวกัน แต่สำหรับอสมการ เรากลับได้สิ่งที่แตกต่างจากเดิม และนี้คือหนึ่งในคุณสมบัติเฉพาะของอสมการ และทำให้ /me เอามาใช้ในการสร้าง LESS_THAN(m, n) และ GREATER_THAN_OR_EQUAL(m, n) ดังนี้

const LESS_THAN = (m)=>(n)=>IS_ZERO(SUBTRACT(n)(m))
const GREATER_THAN_OR_EQUAL = (m)=>(n)=>NOT(IS_ZERO(SUBTRACT(n)(m)))
//เผื่อไม่สังเกต มันมีการสลับตัวแปรจากของเดิม

จบไปอีกหนึ่งตอน

ถึงแม้ว่าเรายังไม่เข้าใกล้เป้าหมาย isPrime(n) ของเราเลย แต่มันก็เป็นการวางรากฐานที่มั่นคง หลังจากนี้ เราจะสามารถลุยไปได้อย่างรวดเร็ว

สำหรับตอนหน้า /me จะพาไปเขียนฟังก์ชั่นที่เรายังค้างกันอยู่ DIVIDE และ MODULO ซึ่งต้องใช้สิ่งที่เราสร้างมาทั้งหมดไม่ว่าจะเป็น SUBTRACT, LESS_THAN_OR_EQUAL และ เครื่องมือชิ้นใหม่ที่เรียกว่า Y combinator และการทำ recursion

อ่านตอนอื่นๆได้ที่

ต้นฉบับ Programming with Nothing

แต่สามารถ ดูโค้ดส่วนต่างๆได้ที่ Github จะทยอย push ตามบล๊อคที่ทยอยเขียน