นี่เป็นฟังก์ชั่นที่บริสุทธิ์หรือเปล่า?


117

แหล่งที่มาส่วนใหญ่กำหนดฟังก์ชั่นที่บริสุทธิ์ว่ามีสองคุณสมบัติต่อไปนี้:

  1. ค่าส่งคืนจะเหมือนกันสำหรับอาร์กิวเมนต์เดียวกัน
  2. การประเมินผลไม่มีผลข้างเคียง

มันเป็นเงื่อนไขแรกที่เกี่ยวข้องกับฉัน ในกรณีส่วนใหญ่มันง่ายที่จะตัดสิน พิจารณาฟังก์ชั่น JavaScript ต่อไปนี้ (ดังแสดงในบทความนี้ )

บริสุทธิ์:

const add = (x, y) => x + y;

add(2, 4); // 6

ไม่บริสุทธิ์:

let x = 2;

const add = (y) => {
  return x += y;
};

add(4); // x === 6 (the first time)
add(4); // x === 10 (the second time)

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

ส่วนนี้ฉันได้รับ


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

(แก้ไข - ใช้constในบรรทัดแรกใช้letก่อนหน้าโดยไม่ตั้งใจ)

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => {
  return x * exchangeRate;
};

dollarToEuro(100) //90 today

dollarToEuro(100) //something else tomorrow

สมมติว่าเราดึงข้อมูลอัตราแลกเปลี่ยนจาก db และเปลี่ยนแปลงทุกวัน

ตอนนี้ไม่ว่ากี่ครั้งที่ผมเรียกใช้ฟังก์ชันนี้ในวันนี้100ก็จะให้ฉันเอาท์พุทเหมือนกันสำหรับการป้อนข้อมูล อย่างไรก็ตามมันอาจให้ผลลัพธ์ที่แตกต่างออกไปในวันพรุ่งนี้ ฉันไม่แน่ใจว่าสิ่งนี้ละเมิดเงื่อนไขแรกหรือไม่

IOW ฟังก์ชั่นของตัวเองไม่มีตรรกะใด ๆ ที่จะกลายพันธุ์ของอินพุต แต่มันขึ้นอยู่กับค่าคงที่ภายนอกที่อาจมีการเปลี่ยนแปลงในอนาคต ในกรณีนี้มันแน่นอนว่ามันจะเปลี่ยนทุกวัน ในกรณีอื่น ๆ มันอาจเกิดขึ้น; มันอาจจะไม่

เราสามารถเรียกฟังก์ชั่นดังกล่าวฟังก์ชั่นบริสุทธิ์ ถ้าคำตอบคือไม่เราจะสร้างมันขึ้นมาใหม่ได้อย่างไร?


6
ความบริสุทธิ์ของภาษาแบบไดนามิกเช่น JS นั้นเป็นหัวข้อที่ซับซ้อนมาก:function myNumber(n) { this.n = n; }; myNumber.prototype.valueOf = function() { console.log('impure'); return this.n; }; const n = new myNumber(42); add(n, 1);
zerkms

29
ความบริสุทธิ์หมายความว่าคุณสามารถแทนที่การเรียกใช้ฟังก์ชันด้วยค่าผลลัพธ์ที่ระดับรหัสโดยไม่เปลี่ยนพฤติกรรมของโปรแกรมของคุณ
บ๊อบ

1
หากต้องการดูเพิ่มเติมเกี่ยวกับสิ่งที่ก่อให้เกิดผลข้างเคียงและด้วยคำศัพท์เชิงทฤษฎีให้ดูcs.stackexchange.com/questions/116377/…
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

3
(x) => {return x * 0.9;}วันนี้ฟังก์ชั่น พรุ่งนี้คุณจะมีฟังก์ชั่นที่แตกต่างกันซึ่งจะบริสุทธิ์เช่น(x) => {return x * 0.89;}กัน โปรดสังเกตว่าแต่ละครั้งที่คุณเรียกใช้(x) => {return x * exchangeRate;}มันจะสร้างฟังก์ชั่นใหม่และฟังก์ชั่นนั้นบริสุทธิ์เพราะexchangeRateไม่สามารถเปลี่ยนได้
user253751

2
นี่เป็นฟังก์ชั่นที่ไม่บริสุทธิ์หากคุณต้องการทำให้มันบริสุทธิ์คุณสามารถใช้const dollarToEuro = (x, exchangeRate) => { return x * exchangeRate; }; สำหรับฟังก์ชั่นที่บริสุทธิ์Its return value is the same for the same arguments.ควรถือไว้เสมอ 1 วินาทีสอง 1 ทศวรรษ .. ในภายหลังไม่ว่าอะไรก็ตาม
Vikash Tiwari

คำตอบ:


133

dollarToEuro's ค่าตอบแทนขึ้นอยู่กับตัวแปรภายนอกที่ไม่ได้เป็นอาร์กิวเมนต์; ดังนั้นฟังก์ชั่นจึงไม่บริสุทธิ์

ในคำตอบคือไม่เราจะปรับฟังก์ชั่นให้บริสุทธิ์ได้อย่างไร

exchangeRateเลือกหนึ่งคือการส่งผ่าน ด้วยวิธีนี้ทุกครั้งที่มีการโต้แย้ง(something, somethingElse)ผลลัพธ์จะรับประกันว่าsomething * somethingElse:

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

โปรดทราบว่าสำหรับการเขียนโปรแกรมที่ใช้งานได้คุณควรหลีกเลี่ยงlet- ใช้constเพื่อหลีกเลี่ยงการมอบหมายใหม่


6
การไม่มีตัวแปรอิสระไม่ได้เป็นข้อกำหนดสำหรับฟังก์ชั่นที่จะบริสุทธิ์: ที่const add = x => y => x + y; const one = add(42);นี่ทั้งสองaddและoneเป็นฟังก์ชั่นที่บริสุทธิ์
zerkms

7
const foo = 42; const add42 = x => x + foo;<- นี่คือฟังก์ชั่นบริสุทธิ์อื่นซึ่งใช้ตัวแปรฟรีอีกครั้ง
zerkms

8
@zerkms - ฉันมีความกระตือรือร้นที่จะเห็นคำตอบของคุณสำหรับคำถามนี้ (แม้ว่าจะเป็นเพียงแค่ rewords CertainPerformance ของการใช้คำศัพท์ที่แตกต่างกัน) ฉันไม่คิดว่ามันจะเป็นการทำซ้ำและมันจะส่องสว่างโดยเฉพาะอย่างยิ่งเมื่ออ้างถึง (ควรมีแหล่งข้อมูลที่ดีกว่าบทความ Wikipedia ด้านบน แต่ถ้านั่นคือทั้งหมดที่เราได้รับก็ยังคงเป็นผู้ชนะ) (มันจะง่ายต่อการอ่านความคิดเห็นนี้ในแง่ลบบางอย่างเชื่อใจฉันว่าฉันเป็นของแท้ฉันคิดว่าคำตอบดังกล่าวจะดีมากและอยากจะอ่านมัน)
TJ Crowder

17
ฉันคิดว่าทั้งคุณและ @zerkms นั้นผิด คุณดูเหมือนจะคิดว่าฟังก์ชั่นในตัวอย่างในคำตอบของคุณคือไม่บริสุทธิ์เพราะมันขึ้นอยู่กับตัวแปรฟรีdollarToEuro exchangeRateนั่นไร้สาระ ความบริสุทธิ์ของฟังก์ชั่นนั้นไม่เกี่ยวข้องกับว่ามันมีตัวแปรอิสระหรือไม่ อย่างไรก็ตาม zerkms ก็ผิดเพราะเขาเชื่อว่าdollarToEuroฟังก์ชั่นนั้นไม่บริสุทธิ์เพราะมันขึ้นอยู่กับexchangeRateว่ามาจากฐานข้อมูลใด เขาบอกว่ามันไม่บริสุทธิ์เพราะ "มันขึ้นอยู่กับ IO แบบเคลื่อนย้าย"
Aadit M Shah

9
(ต่อ) อีกครั้งที่ไร้สาระเพราะมันแสดงให้เห็นว่าdollarToEuroไม่บริสุทธิ์เพราะexchangeRateเป็นตัวแปรอิสระ มันแสดงให้เห็นว่าถ้าexchangeRateไม่ได้เป็นตัวแปรอิสระเช่นถ้ามันเป็นข้อโต้แย้งก็dollarToEuroจะบริสุทธิ์ ดังนั้นจึงแสดงว่าdollarToEuro(100)ไม่บริสุทธิ์ แต่dollarToEuro(100, exchangeRate)บริสุทธิ์ มันไร้สาระอย่างชัดเจนเพราะในทั้งสองกรณีคุณขึ้นอยู่กับexchangeRateว่ามาจากฐานข้อมูลใด ข้อแตกต่างเพียงอย่างเดียวคือว่าexchangeRateเป็นตัวแปรอิสระภายในdollarToEuroฟังก์ชันหรือไม่
Aadit M Shah

76

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

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

const fib = (() => {
    const memo = [0, 1];

    return n => {
      if (n >= memo.length) memo[n] = fib(n - 1) + fib(n - 2);
      return memo[n];
    };
})();

console.log(fib(100));

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

ตอนนี้ให้พิจารณาฟังก์ชั่นต่อไปนี้

const greet = name => {
    console.log("Hello %s!", name);
};

greet("World");
greet("Snowman");

เป็นgreetฟังก์ชั่นที่บริสุทธิ์หรือไม่บริสุทธิ์? โดยวิธีการกล่องดำของเราถ้าเราให้มันเข้าเดียวกัน (เช่นWorld) แล้วมันจะพิมพ์ผลลัพธ์เดียวกันไปยังหน้าจอ (เช่นHello World!) ในแง่นั้นมันไม่บริสุทธิ์หรือ ไม่มันไม่ใช่. เหตุผลที่ไม่บริสุทธิ์คือเพราะเราพิจารณาการพิมพ์ผลข้างเคียงบนหน้าจอ หากกล่องดำของเราสร้างผลข้างเคียงมันก็ไม่บริสุทธิ์

ผลข้างเคียงคืออะไร? นี่คือที่แนวคิดของการอ้างอิงโปร่งใสมีประโยชน์ หากฟังก์ชันมีความโปร่งใสในการอ้างอิงเราสามารถแทนที่แอปพลิเคชันของฟังก์ชันนั้นด้วยผลลัพธ์ของมัน โปรดทราบว่านี้ไม่ได้เป็นเช่นเดียวกับอินไลน์ฟังก์ชั่น

ในฟังก์ชั่นอินไลน์เราแทนที่แอพพลิเคชั่นของฟังก์ชั่นด้วยเนื้อความของฟังก์ชั่นโดยไม่ต้องเปลี่ยนซีแมนทิกส์ของโปรแกรม อย่างไรก็ตามฟังก์ชันโปร่งใสที่อ้างอิงได้สามารถถูกแทนที่ด้วยค่าที่ส่งคืนได้โดยไม่ต้องเปลี่ยนความหมายของโปรแกรม ลองพิจารณาตัวอย่างต่อไปนี้

console.log("Hello %s!", "World");
console.log("Hello %s!", "Snowman");

ที่นี่เราได้นิยามคำนิยามของgreetและมันไม่ได้เปลี่ยนความหมายของโปรแกรม

ตอนนี้ให้พิจารณาโปรแกรมต่อไปนี้

undefined;
undefined;

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

ตอนนี้ลองพิจารณาอีกตัวอย่างหนึ่ง พิจารณาโปรแกรมต่อไปนี้

const main = async () => {
    const response = await fetch("https://time.akamai.com/");
    const serverTime = 1000 * await response.json();
    const timeDiff = time => time - serverTime;
    console.log("%d ms", timeDiff(Date.now()));
};

main();

เห็นได้ชัดว่าmainฟังก์ชั่นไม่บริสุทธิ์ อย่างไรก็ตามtimeDiffฟังก์ชั่นนั้นบริสุทธิ์หรือไม่บริสุทธิ์? แม้ว่าจะขึ้นอยู่กับserverTimeว่ามาจากการเรียกเครือข่ายที่ไม่บริสุทธิ์ แต่ก็ยังมีความโปร่งใสในการอ้างอิงเพราะมันจะส่งกลับผลลัพธ์ที่เหมือนกันสำหรับอินพุตเดียวกันและเพราะมันไม่มีผลข้างเคียงใด ๆ

zerkmsอาจไม่เห็นด้วยกับฉันในจุดนี้ ในคำตอบของเขาเขากล่าวว่าdollarToEuroฟังก์ชั่นในตัวอย่างต่อไปนี้ไม่บริสุทธิ์เพราะ“ มันขึ้นอยู่กับ IO แบบเคลื่อนย้ายได้”

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

ฉันต้องไม่เห็นด้วยกับเขาเพราะความจริงที่ว่าexchangeRateมาจากฐานข้อมูลนั้นไม่เกี่ยวข้อง มันเป็นรายละเอียดภายในและระเบียบวิธีกล่องดำของเราเพื่อกำหนดความบริสุทธิ์ของฟังก์ชั่นไม่สนใจรายละเอียดภายใน

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

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

โปรดทราบว่าเพียงเพราะฟังก์ชั่นขึ้นอยู่กับข้อมูลบางอย่างที่โหลดจากไฟล์ปรับแต่งฐานข้อมูลหรือการโทรเครือข่ายไม่ได้หมายความว่าฟังก์ชั่นนั้นไม่บริสุทธิ์

อย่างไรก็ตามลองพิจารณาตัวอย่างดั้งเดิมของคุณที่มีความหมายต่างกัน

let exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => {
  return x * exchangeRate;
};

dollarToEuro(100) //90 today

dollarToEuro(100) //something else tomorrow

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

อย่างไรก็ตามหากexchangeRateตัวแปรนั้นไม่ได้รับการแก้ไขและจะไม่ถูกแก้ไขในอนาคต (เช่นถ้าเป็นค่าคงที่) แม้ว่าจะถูกกำหนดเป็นletมันจะไม่ทำลายความโปร่งใสในการอ้างอิง ในกรณีdollarToEuroนั้นเป็นหน้าที่ที่แท้จริง

โปรดทราบว่าค่าของexchangeRateสามารถเปลี่ยนแปลงได้ทุกครั้งที่คุณเรียกใช้โปรแกรมอีกครั้งและจะไม่ทำลายความโปร่งใสในการอ้างอิง มันจะแบ่งความโปร่งใสในการอ้างอิงถ้ามันเปลี่ยนแปลงในขณะที่โปรแกรมกำลังทำงาน

ตัวอย่างเช่นหากคุณเรียกใช้timeDiffตัวอย่างของฉันหลายครั้งคุณจะได้รับค่าที่แตกต่างกันserverTimeและผลลัพธ์ที่แตกต่างกัน อย่างไรก็ตามเนื่องจากค่าserverTimeไม่เคยเปลี่ยนแปลงในขณะที่โปรแกรมกำลังทำงานอยู่timeDiffฟังก์ชันจึงบริสุทธิ์


3
นี่เป็นข้อมูลที่มาก ขอบคุณ และฉันก็ตั้งใจจะใช้constในตัวอย่างของฉัน
Snowman

3
หากคุณไม่ได้หมายความว่าจะใช้constแล้วdollarToEuroฟังก์ชั่นเป็นที่แน่นอนบริสุทธิ์ วิธีเดียวที่exchangeRateจะเปลี่ยนค่าของคือถ้าคุณรันโปรแกรมอีกครั้ง ในกรณีนั้นกระบวนการเก่าและกระบวนการใหม่จะแตกต่างกัน ดังนั้นจึงไม่ทำลายความโปร่งใสในการอ้างอิง มันเหมือนกับการเรียกฟังก์ชันสองครั้งด้วยอาร์กิวเมนต์ที่ต่างกัน ข้อโต้แย้งอาจจะแตกต่างกัน แต่ภายในฟังก์ชั่นค่าของข้อโต้แย้งยังคงที่
Aadit M Shah

3
เสียงนี้คล้ายกับทฤษฎีเล็กน้อยเกี่ยวกับสัมพัทธภาพ: ค่าคงที่ค่อนข้างคงที่เท่านั้นไม่ใช่อย่างแน่นอนซึ่งสัมพันธ์กับกระบวนการทำงาน เห็นได้ชัดว่าคำตอบที่ถูกต้องเท่านั้นที่นี่ +1
บ๊อบ

5
ฉันไม่เห็นด้วยกับ"ไม่บริสุทธิ์เพราะในที่สุดก็รวบรวมคำแนะนำเช่น" ย้ายค่านี้เป็น eax "และ" เพิ่มค่านี้ให้กับเนื้อหาของ eax "หากeaxล้าง - ผ่านโหลดหรือล้าง - รหัสยังคงกำหนดขึ้นโดยไม่คำนึงถึง มีอะไรเกิดขึ้นและเป็นสิ่งที่บริสุทธิ์มิฉะนั้นแล้วคำตอบที่ครอบคลุมมาก
3Dave

3
@Bergi: อันที่จริงในภาษาบริสุทธิ์ที่มีค่าไม่เปลี่ยนรูปแบบตัวตนที่ไม่เกี่ยวข้อง การอ้างอิงสองรายการที่ประเมินค่าเดียวกันนั้นเป็นการอ้างอิงสองรายการไปยังวัตถุเดียวกันหรือไปยังวัตถุต่าง ๆเท่านั้นที่สามารถสังเกตได้โดยการผ่าเหล่าวัตถุผ่านการอ้างอิงอย่างใดอย่างหนึ่งและสังเกตว่าค่านั้นเปลี่ยนแปลงหรือไม่เมื่อดึงผ่านการอ้างอิงอื่น ๆ หากไม่มีการกลายพันธุ์ตัวตนจะไม่เกี่ยวข้อง (ตามที่รวย Hickey จะพูด: Identity เป็นชุดของรัฐในช่วงเวลา.)
Jörg W Mittag

23

คำตอบของฉันคนเจ้าระเบียบ (ที่ "ฉัน" เป็นตัวฉันอย่างแท้จริงเนื่องจากฉันคิดว่าคำถามนี้ไม่มีคำตอบ "ถูกต้อง" ที่เป็นทางการเดียว):

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

ตัวอย่าง:

const add = (x, y) => x + y;

function myNumber(n) { this.n = n; };
myNumber.prototype.valueOf = function() {
    console.log('impure'); return this.n;
};

const n = new myNumber(42);

add(n, 1); // this call produces a side effect

คำตอบของนักปฏิบัตินิยม:

จากคำนิยามมากจากวิกิพีเดีย

ในการเขียนโปรแกรมคอมพิวเตอร์ฟังก์ชันบริสุทธิ์คือฟังก์ชันที่มีคุณสมบัติดังต่อไปนี้:

  1. ค่าส่งคืนจะเหมือนกันสำหรับอาร์กิวเมนต์เดียวกัน (ไม่มีการแปรผันของตัวแปรสแตติกท้องถิ่นตัวแปรที่ไม่ใช่โลคอลอาร์กิวเมนต์อ้างอิงที่ไม่แน่นอนหรืออินพุตสตรีมจากอุปกรณ์ I / O)
  2. การประเมินผลไม่มีผลข้างเคียง (ไม่มีการกลายพันธุ์ของตัวแปรสแตติกท้องถิ่นตัวแปรที่ไม่ใช่ในท้องถิ่นข้อโต้แย้งอ้างอิงที่ไม่แน่นอนหรือสตรีม I / O)

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

ตอนนี้ฟังก์ชั่นของคุณ:

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

มันไม่บริสุทธิ์เพราะมันไม่ผ่านเกณฑ์ข้อกำหนด 2: มันขึ้นอยู่กับ IO แบบเคลื่อนย้าย

ฉันเห็นด้วยกับข้อความข้างต้นว่าผิดโปรดดูคำตอบอื่น ๆ สำหรับรายละเอียด: https://stackoverflow.com/a/58749249/251311

แหล่งข้อมูลอื่น ๆ ที่เกี่ยวข้อง:


4
@TJCrowder meเป็น zerkms ที่ให้คำตอบ
zerkms

2
ใช่กับ Javascript มันเป็นเรื่องเกี่ยวกับความเชื่อมั่นไม่รับประกัน
บ๊อบ

4
@bob ... หรือเป็นการบล็อกการโทร
zerkms

1
@zerkms - ขอบคุณ เพียงแค่ฉันแน่ใจ 100% ความแตกต่างที่สำคัญระหว่างคุณadd42และของฉันaddXล้วนๆที่xอาจมีการเปลี่ยนแปลงของฉันและคุณftไม่สามารถเปลี่ยนแปลงได้ (และดังนั้นadd42มูลค่าส่งคืนของจะไม่เปลี่ยนแปลงตามft)
TJ Crowder

5
ฉันไม่เห็นด้วยว่าdollarToEuroฟังก์ชั่นในตัวอย่างของคุณไม่บริสุทธิ์ ฉันอธิบายว่าทำไมฉันไม่เห็นด้วยกับคำตอบของฉัน stackoverflow.com/a/58749249/783743
Aadit M Shah

14

เช่นเดียวกับคำตอบอื่น ๆ ที่ได้กล่าวไปแล้ววิธีการใช้งานของdollarToEuroคุณ

let exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => { return x * exchangeRate; }; 

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

กุญแจสำคัญในที่นี้คือมีพารามิเตอร์หลายอย่างที่ต้องใช้ในการคำนวณการแปลงสกุลเงินและรุ่นทั่วไปที่แท้จริงdollarToEuroจะให้ทั้งหมดของพวกเขา พารามิเตอร์ที่ตรงที่สุดคือจำนวน USD ที่จะแปลงและอัตราแลกเปลี่ยน อย่างไรก็ตามเนื่องจากคุณต้องการรับอัตราแลกเปลี่ยนของคุณจากข้อมูลที่เผยแพร่ตอนนี้คุณมีพารามิเตอร์สามตัวที่จะให้:

  • จำนวนเงินที่จะแลกเปลี่ยน
  • อำนาจในอดีตที่จะปรึกษาหาอัตราแลกเปลี่ยน
  • วันที่ที่มีการทำธุรกรรม (เพื่อจัดทำดัชนีอำนาจทางประวัติศาสตร์)

ผู้มีอำนาจในอดีตที่นี่คือฐานข้อมูลของคุณและสมมติว่าฐานข้อมูลไม่ได้รับผลกระทบจะส่งคืนผลลัพธ์เดียวกันสำหรับอัตราแลกเปลี่ยนในแต่ละวันเสมอ ดังนั้นด้วยการรวมกันของพารามิเตอร์ทั้งสามนี้คุณสามารถเขียนเวอร์ชันทั่วไปแบบพอเพียงได้อย่างสมบูรณ์dollarToEuroซึ่งอาจมีลักษณะดังนี้:

function dollarToEuro(x, authority, date) {
    const exchangeRate = authority(date);
    return x * exchangeRate;
}

dollarToEuro(100, fetchFromDatabase, Date.now());

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

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

function dollarToEuro(x, date) {
    const exchangeRate = fetchFromDatabase(date);
    return x * exchangeRate;
}

dollarToEuro(100, Date.now());

@Snowman คุณยินดีต้อนรับ! ฉันอัปเดตคำตอบเล็กน้อยเพื่อเพิ่มตัวอย่างโค้ดเพิ่มเติม
TheHansinator

8

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

เมื่อคุณมีรหัสเช่นนี้:

let exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => {
  return x * exchangeRate;
};

dollarToEuro(100) //90 today

dollarToEuro(100) //something else tomorrow

หากexchangeRateไม่สามารถแก้ไขได้ในระหว่างการโทรทั้งสองไปdollarToEuro(100)เป็นไปได้ที่จะบันทึกผลลัพธ์ของการโทรครั้งแรกdollarToEuro(100)และปรับการโทรครั้งที่สองให้เหมาะสม ผลลัพธ์จะเหมือนกันดังนั้นเราสามารถจำค่าได้ตั้งแต่เมื่อก่อน

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

หากfetchFromDatabase()ตัวเองเป็นฟังก์ชั่นบริสุทธิ์ที่ประเมินค่าคงที่และexchangeRateไม่เปลี่ยนรูปเราสามารถพับค่าคงที่นี้ตลอดการคำนวณ คอมไพเลอร์ที่รู้ว่าเป็นกรณีนี้สามารถทำการหักแบบเดียวกับที่คุณทำในความคิดเห็นซึ่งdollarToEuro(100)ประเมินเป็น 90.0 และแทนที่นิพจน์ทั้งหมดด้วยค่าคงที่ 90.0

อย่างไรก็ตามหากfetchFromDatabase()ไม่ได้ทำ I / O ซึ่งถือว่าเป็นผลข้างเคียงชื่อของมันก็จะเป็นการละเมิดหลักการของความประหลาดใจอย่างน้อยที่สุด


8

ฟังก์ชั่นนี้ไม่บริสุทธิ์มันอาศัยตัวแปรภายนอกซึ่งเกือบจะเปลี่ยนไปอย่างแน่นอน

ฟังก์ชั่นจึงล้มเหลวในจุดแรกที่คุณทำมันจะไม่ส่งกลับค่าเดียวกันเมื่ออาร์กิวเมนต์เดียวกัน

ในการทำให้ฟังก์ชั่นนี้ "บริสุทธิ์" ส่งผ่านexchangeRateเป็นอาร์กิวเมนต์

นี่จะเป็นไปตามเงื่อนไขทั้งสองข้อ

  1. มันจะส่งกลับค่าเดิมเสมอเมื่อผ่านในมูลค่าและอัตราแลกเปลี่ยนเดียวกัน
  2. มันก็จะไม่มีผลข้างเคียง

รหัสตัวอย่าง:

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

dollarToEuro(100, fetchFromDatabase())

1
"ซึ่งเกือบจะเปลี่ยนไปอย่างแน่นอน" --- ไม่constเลย
zerkms

7

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

คุณสมบัติสองอย่างที่คุณให้นั้นเป็นผลมาจากความโปร่งใสในการอ้างอิง ตัวอย่างเช่นฟังก์ชันต่อไปf1นี้ไม่ถูกต้องเนื่องจากไม่ได้ผลเหมือนกันทุกครั้ง (คุณสมบัติที่คุณมีหมายเลข 1):

function f1(x, y) {
  if (Math.random() > 0.5) { return x; }
  return y;
}

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

สมมติว่าเราเขียนโค้ดที่เราเรียกใช้และได้รับค่าตอบแทนf1("hello", "world") "hello"หากเราทำการค้นหา / แทนที่การโทรทุกครั้งf1("hello", "world")และแทนที่ด้วย"hello"เราจะเปลี่ยนซีแมนทิกส์ของโปรแกรม (ตอนนี้การโทรทั้งหมดจะถูกแทนที่ด้วย"hello"แต่ตอนแรกประมาณครึ่งหนึ่งจะประเมิน"world") ดังนั้นการเรียกร้องให้f1ไม่อ้างอิงอย่างโปร่งใสจึงf1ไม่บริสุทธิ์

อีกวิธีที่การเรียกใช้ฟังก์ชั่นสามารถมีซีแมนทิกส์ที่แตกต่างกันกับค่าคือการดำเนินการคำสั่ง ตัวอย่างเช่น:

function f2(x) {
  console.log("foo");
  return x;
}

ค่าส่งคืนของf2("bar")จะเป็น"bar"แต่ความหมายของค่า"bar"จะแตกต่างจากการโทรf2("bar")เนื่องจากหลังจะเข้าสู่คอนโซลด้วย การแทนที่อันที่หนึ่งกับอีกอันหนึ่งจะเปลี่ยนซีแมนทิกส์ของโปรแกรมดังนั้นจึงไม่โปร่งใสในการอ้างอิงและด้วยเหตุนี้จึงf2ไม่บริสุทธิ์

การdollarToEuroทำงานของคุณโปร่งใสหรือไม่ (และบริสุทธิ์) ขึ้นอยู่กับสองสิ่ง:

  • 'ขอบเขต' ของสิ่งที่เราพิจารณาว่ามีความโปร่งใสในการอ้างอิง
  • ไม่ว่าexchangeRateจะเปลี่ยนใน 'ขอบเขต' ที่เคย

ไม่มีขอบเขต "ที่ดีที่สุด" ที่จะใช้; โดยปกติเราจะคิดถึงการใช้งานโปรแกรมครั้งเดียวหรืออายุการใช้งานของโครงการ ลองจินตนาการว่าค่าที่ส่งคืนของทุกฟังก์ชั่นนั้นถูกแคช (เช่นตารางบันทึกในตัวอย่างที่ได้รับจาก @ aadit-m-shah): เมื่อใดที่เราจะต้องล้างแคชเพื่อรับประกันว่าค่าค้างจะไม่ยุ่งกับเรา ความหมาย?

หากexchangeRateใช้งานอยู่varมันสามารถเปลี่ยนระหว่างการโทรแต่ละครั้งเป็นdollarToEuro; เราจะต้องล้างผลลัพธ์ที่แคชไว้ระหว่างการโทรแต่ละครั้งดังนั้นจึงไม่มีความโปร่งใสในการอ้างอิงที่จะพูดถึง

โดยการใช้constเรากำลังขยายขอบเขตของ 'การเรียกใช้โปรแกรม: มันจะปลอดภัยที่จะแคชค่าตอบแทนของdollarToEuroจนกว่าโปรแกรมจะเสร็จสิ้น เราสามารถจินตนาการถึงการใช้แมโคร (ในภาษาอย่าง Lisp) เพื่อแทนที่การเรียกใช้ฟังก์ชันด้วยค่าที่ส่งคืน ความบริสุทธิ์จำนวนนี้เป็นเรื่องปกติสำหรับสิ่งต่าง ๆ เช่นค่าการตั้งค่าตัวเลือกบรรทัดคำสั่งหรือรหัสเฉพาะ หากเรา จำกัด ตัวเองให้คิดถึงการรันโปรแกรมหนึ่งครั้งเราจะได้รับประโยชน์สูงสุดจากความบริสุทธิ์ แต่เราต้องระมัดระวังในการวิ่ง (เช่นการบันทึกข้อมูลลงในไฟล์จากนั้นทำการโหลดในการทำงานอื่น) ฉันจะไม่เรียกฟังก์ชั่นเช่น "บริสุทธิ์" ในนามธรรมความรู้สึก (เช่นถ้าฉันได้เขียนความหมายในพจนานุกรม) แต่ไม่มีปัญหาเกี่ยวกับการรักษาพวกเขาเป็นที่บริสุทธิ์ในบริบท

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


4

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


2

เราสามารถเรียกฟังก์ชั่นดังกล่าวฟังก์ชั่นบริสุทธิ์ ถ้าคำตอบคือไม่เราจะสร้างมันขึ้นมาใหม่ได้อย่างไร?

ในขณะที่คุณตั้งข้อสังเกตรับรองสำเนาถูกต้อง"มันอาจจะให้ฉันเอาท์พุทที่แตกต่างกันในวันพรุ่งนี้" ที่ควรจะเป็นกรณีที่คำตอบจะดังก้อง"ไม่" โดยเฉพาะอย่างยิ่งหากพฤติกรรมที่คุณตั้งใจไว้dollarToEuroถูกตีความอย่างถูกต้องว่า:

const dollarToEuro = (x) => {
  const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;
  return x * exchangeRate;
};

อย่างไรก็ตามมีการตีความที่แตกต่างกันซึ่งจะถือว่าบริสุทธิ์:

const dollarToEuro = ( () => {
    const exchangeRate =  fetchFromDatabase();

    return ( x ) => x * exchangeRate;
} )();

dollarToEuro ข้างบนนั้นบริสุทธิ์


จากมุมมองของวิศวกรรมซอฟต์แวร์ก็จำเป็นที่จะประกาศการพึ่งพาของในฟังก์ชั่นdollarToEuro fetchFromDatabaseดังนั้นให้นิยามคำนิยามใหม่dollarToEuroดังนี้:

const dollarToEuro = ( x, fetchFromDatabase ) => {
  return x * fetchFromDatabase();
};

ด้วยผลลัพธ์นี้ทำให้สมมติฐานที่fetchFromDatabaseใช้งานได้เป็นที่น่าพอใจจากนั้นเราสามารถสรุปได้ว่าการฉายภาพfetchFromDatabaseบนdollarToEuroจะต้องเป็นที่น่าพอใจ หรือคำสั่งที่ " fetchFromDatabaseบริสุทธิ์" หมายถึงdollarToEuroบริสุทธิ์ (ตั้งแต่fetchFromDatabaseเป็นพื้นฐานสำหรับการโดยปัจจัยเกลาของdollarToEurox

จากโพสต์ต้นฉบับฉันสามารถเข้าใจว่าfetchFromDatabaseเป็นเวลาฟังก์ชั่น มาปรับปรุงความพยายามในการปรับโครงสร้างใหม่เพื่อให้ความเข้าใจนั้นโปร่งใสดังนั้นจึงถือว่าfetchFromDatabaseเป็นหน้าที่บริสุทธิ์อย่างชัดเจน:

fetchFromDatabase = (timestamp) => {/ * ที่นี่จะใช้งาน * /};

ในที่สุดฉันจะ refactor คุณสมบัติดังนี้

const fetchFromDatabase = ( timestamp ) => { /* here goes the implementation */ };

// Do a partial application of `fetchFromDatabase` 
const exchangeRate = fetchFromDatabase.bind( null, Date.now() );

const dollarToEuro = ( dollarAmount, exchangeRate ) => dollarAmount * exchangeRate();

ดังนั้นdollarToEuroสามารถทดสอบหน่วยโดยเพียงแค่พิสูจน์ว่ามันถูกเรียกfetchFromDatabase(หรืออนุพันธ์ของมันexchangeRate)


1
นี่สว่างมาก +1 ขอบคุณ
Snowman

ในขณะที่ฉันพบว่าคำตอบของคุณมีข้อมูลมากขึ้นและบางทีการปรับปรุงที่ดีขึ้นสำหรับกรณีการใช้งานเฉพาะของdollarToEuro; ฉันได้กล่าวถึงใน OP ว่าอาจมีกรณีการใช้งานอื่น ฉันเลือก dollarToEuro ทันทีเพราะมันกระตุ้นสิ่งที่ฉันกำลังพยายามทำอยู่ แต่อาจมีบางสิ่งที่บอบบางน้อยกว่าซึ่งขึ้นอยู่กับตัวแปรอิสระที่อาจเปลี่ยนแปลงได้ แต่ไม่จำเป็นต้องเป็นหน้าที่ของเวลา ด้วยความที่อยู่ในใจฉันพบว่า refactor ที่ติดอันดับต้น ๆ จะสามารถเข้าถึงได้มากขึ้นและตัวช่วยสร้างที่สามารถช่วยผู้อื่นในการใช้งานที่คล้ายกัน ขอบคุณสำหรับความช่วยเหลือของคุณ
Snowman

-1

ฉันเป็น Haskell / JS สองภาษาและ Haskell เป็นหนึ่งในภาษาที่ทำเรื่องใหญ่เกี่ยวกับฟังก์ชั่นความบริสุทธิ์ดังนั้นฉันคิดว่าฉันจะให้มุมมองจาก Haskell เห็นมัน

ดังที่คนอื่น ๆ ได้กล่าวไว้ใน Haskell การอ่านตัวแปรที่ไม่แน่นอนนั้นโดยทั่วไปถือว่าไม่บริสุทธิ์ มีความแตกต่างระหว่างตัวแปรและคำจำกัดความในตัวแปรที่สามารถเปลี่ยนแปลงได้ในภายหลังคำจำกัดความจะเหมือนกันตลอดไป ดังนั้นหากคุณได้ประกาศconstแล้ว (สมมติว่ามันเป็นเพียงnumberและไม่มีโครงสร้างภายในที่ไม่แน่นอน) การอ่านจากสิ่งนั้นจะใช้คำจำกัดความซึ่งบริสุทธิ์ แต่คุณต้องการสร้างแบบจำลองอัตราแลกเปลี่ยนที่เปลี่ยนแปลงไปตามกาลเวลาและนั่นต้องมีความไม่แน่นอนบางอย่างจากนั้นคุณก็จะกลายเป็นสิ่งสกปรก

เพื่ออธิบายสิ่งต่างๆประเภทนั้นไม่บริสุทธิ์ (เราสามารถเรียกพวกเขาว่า“ผลกระทบ” และการใช้งานของพวกเขา“effectful” เมื่อเทียบกับ“บริสุทธิ์”) ใน Haskell เราทำในสิ่งที่คุณอาจเรียกmetaprogramming metaprogramming วันนี้มักจะหมายถึงมาโครซึ่งไม่ใช่สิ่งที่ฉันหมายถึง แต่เพียงความคิดในการเขียนโปรแกรมเพื่อเขียนโปรแกรมอื่นโดยทั่วไป

ในกรณีนี้ใน Haskell เราเขียนการคำนวณบริสุทธิ์ซึ่งคำนวณโปรแกรมที่มีประสิทธิภาพซึ่งจะทำสิ่งที่เราต้องการ ดังนั้นจุดรวมของแฟ้มแหล่งที่มา Haskell (อย่างน้อยหนึ่งที่อธิบายถึงโปรแกรมที่ไม่ห้องสมุด) คือการอธิบายคำนวณบริสุทธิ์สำหรับ effectful mainโปรแกรมที่-ผลิตโมฆะเรียกว่า จากนั้นงานของคอมไพเลอร์ Haskell ก็คือการใช้ไฟล์ต้นฉบับนี้ทำการคำนวณที่บริสุทธิ์นั้นและวางโปรแกรมที่มีประสิทธิภาพนั้นเป็นไฟล์ไบนารีที่สามารถเรียกทำงานได้ที่ใดที่หนึ่งบนฮาร์ดไดรฟ์ของคุณเพื่อทำงานในเวลาว่าง มีช่องว่างในคำอื่น ๆ ระหว่างเวลาที่การคำนวณบริสุทธิ์ทำงาน (ในขณะที่คอมไพเลอร์ทำให้ปฏิบัติการ) และเวลาเมื่อโปรแกรมที่มีประสิทธิภาพทำงาน (ทุกครั้งที่คุณเรียกใช้ปฏิบัติการ)

ดังนั้นสำหรับเราโปรแกรมที่มีประสิทธิภาพเป็นโครงสร้างข้อมูลจริงๆและพวกมันไม่ได้ทำสิ่งใดในตัวมันเองโดยถูกกล่าวถึง สำหรับตัวอย่างที่มีน้ำหนักเบามากของคลาส TypeScript ซึ่งอธิบายถึงโปรแกรมที่ไม่เปลี่ยนรูปแบบและบางสิ่งที่คุณสามารถทำได้กับมัน

export class Program<x> {
   // wrapped function value
   constructor(public run: () => Promise<x>) {}
   // promotion of any value into a program which makes that value
   static of<v>(value: v): Program<v> {
     return new Program(() => Promise.resolve(value));
   }
   // applying any pure function to a program which makes its input
   map<y>(fn: (x: x) => y): Program<y> {
     return new Program(() => this.run().then(fn));
   }
   // sequencing two programs together
   chain<y>(after: (x: x) => Program<y>): Program<y> {
    return new Program(() => this.run().then(x => after(x).run()));
   }
}

ที่สำคัญคือถ้าคุณมี Program<x>ผลข้างเคียงที่ไม่เกิดขึ้นและสิ่งเหล่านี้ล้วนเป็นหน้าที่ที่บริสุทธิ์ การแมปฟังก์ชันผ่านโปรแกรมไม่มีผลข้างเคียงใด ๆ ยกเว้นว่าฟังก์ชันนั้นไม่ใช่ฟังก์ชันบริสุทธิ์ การเรียงลำดับสองโปรแกรมไม่มีผลข้างเคียงใด ๆ เป็นต้น

ดังนั้นตัวอย่างของวิธีการใช้สิ่งนี้ในกรณีของคุณคุณอาจจะเขียนฟังก์ชั่นบางอย่างที่ส่งคืนโปรแกรมเพื่อรับผู้ใช้ด้วย ID และเปลี่ยนฐานข้อมูลและดึงข้อมูล JSON เช่น

// assuming a database library in knex, say
function getUserById(id: number): Program<{ id: number, name: string, supervisor_id: number }> {
    return new Program(() => knex.select('*').from('users').where({ id }));
}
function notifyUserById(id: number, message: string): Program<void> {
    return new Program(() => knex('messages').insert({ user_id: id, type: 'notification', message }));
}
function fetchJSON(url: string): Program<any> {
  return new Program(() => fetch(url).then(response => response.json()));
}

จากนั้นคุณสามารถอธิบายงาน cron เพื่อให้ URL และค้นหาพนักงานบางคนและแจ้งหัวหน้างานของพวกเขาในวิธีการทำงานอย่างหมดจดเป็น

const action =
  fetchJSON('http://myapi.example.com/employee-of-the-month')
    .chain(eotmInfo => getUserById(eotmInfo.id))
    .chain(employee => 
        getUserById(employee.supervisor_id)
          .chain(supervisor => notifyUserById(
            supervisor.id,
            'Your subordinate ' + employee.name + ' is employee of the month!'
          ))
    );

ประเด็นก็คือว่าทุกฟังก์ชั่นเดียวที่นี่เป็นฟังก์ชั่นที่บริสุทธิ์อย่างสมบูรณ์; ไม่มีอะไรเกิดขึ้นจริงจนกว่าฉันaction.run()จะทำให้มันเป็นจริง นอกจากนี้ฉันสามารถเขียนฟังก์ชั่นเช่น

// do two things in parallel
function parallel<x, y>(x: Program<x>, y: Program<y>): Program<[x, y]> {
    return new Program(() => Promise.all([x.run(), y.run()]));
}

และหาก JS สัญญาว่าจะยกเลิกเราสามารถมีสองรายการแข่งกันและรับผลลัพธ์แรกและยกเลิกรายการที่สอง (ฉันหมายความว่าเรายังทำได้ แต่มันก็ชัดเจนน้อยลงว่าจะทำอย่างไร)

ในกรณีของคุณเราสามารถอธิบายการเปลี่ยนแปลงของอัตราแลกเปลี่ยนด้วย

declare const exchangeRate: Program<number>;

function dollarsToEuros(dollars: number): Program<number> {
  return exchangeRate.map(rate => dollars * rate);
}

และexchangeRateอาจเป็นโปรแกรมที่ดูค่าที่ไม่แน่นอน

let privateExchangeRate: number = 0;
export function setExchangeRate(value: number): Program<void> {
  return new Program(() => { privateExchangeRate = value; return Promise.resolve(undefined); });
}
export const exchangeRate: Program<number> = new Program(() => {
  return Promise.resolve(privateExchangeRate); 
});

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

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


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

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