Sunday, July 19, 2015

ปัญหา Side effect

ปัญหา Side effect และ Referential Transparency

ในการเขียนโปรแกรมที่เกี่ยวข้องกับนิพจน์  ในบางครั้งก็อาจเกิดปัญหา Side effect (ผลกระทบซึ่งเราไม่ทราบ)  ปัญหานี้จะทำให้โปรแกรมเมอร์ debug โปรแกรมไม่ได้ หากไม่เข้าใจถึงผลกระทบที่เกิดขึ้น  ปัญหานี้มักเกี่ยวข้องกับเรียกใช้งานฟังก์ชั่นในโปรแกรมซึ่งเราเรียกว่าเกิด Functional side effect
นิพจน์ (expression)
    result  = A * B + myfunction( ) + 20;
 
คำถามคือ
1. เมื่อนิพจน์ (expression) มีการเรียกใช้ฟังก์ชั่น  โปรแกรมเมอร์ทราบหรือไม่ว่าภาษาการโปรแกรมนั้นการประเมินผลนิพจน์จะยังคงพิจารณาตาม Precedence rule และ Associativity หรือจะต้องพิจารณาการเรียกฟังก์ชั่นร่วมด้วย เช่น ต้องเรียกก่อนค่อยประเมินนิพจน์ตาม Precedence rule และ Associativity
คำตอบ  ถ้าไม่ทราบต้องไปดู Specification ของภาษา  แต่ละภาษาอาจไม่เหมือนกัน  ส่วนใหญ่ภาษามักกำหนดให้เรียกฟังก์ชั่นให้เสร็จก่อน  แล้วค่อยประเมินนิพจน์

2. ถ้าการเรียกฟังก์ชั่นมีการแก้ไขค่าของตัวแปรที่ปรากฎในนิพจน์  เช่น ตัวแปร A หรือ B (สมมติ A หรือ B เป็น Nonlocal variable ที่ฟังก์ชั่นสามารถเข้าถึงได้  ดังนั้นลำดับการเรียกฟังก์ชั่นก่อนและหลังจะมีผลต่อผลลัพธ์หรือไม่ ?
คำตอบ มีแน่นอน  สมมติว่าเรียกฟังก์ชั่นก่อน ดังนั้นค่า A และ B ที่อาจเกิดการแก้ไขจะเป็นค่าหลังจากเรียกฟังก์ชั่น  ถ้าเรียกฟังก์ชั่นภายหลัง (ทำตาม Precedence rule และ Associativity ตามปกติ) ค่า A และ B ในนิพจน์จะเป็นค่าก่อนเรียกฟังก์ชั่น และต้องจำไว้ว่าในบรรทัดถัดไปจะใช้ค่า A และ B จะเป็นค่าใหม่ที่ถูกแก้ไขแล้ว

สาเหตุจาก Functional Side Effect จึงมาจาก  2 กรณี

1. การเรียกฟังก์ชั่นในนิพจน์ที่มีการแก้ไขค่าของตัวแปร Nonlocal variable ในนิพจน์

#include <stdio.h>
int A = 20, B=5;
void main( ){  
    result  = A * B + myfunction( ) + 20;
   ...
}
int myfunction(  ){
      A  = A +10 ;
        ....
     return A; 
}

เมื่อโปรแกรมเมอร์ทั่วไปที่ไม่ทราบปัญหา Functional side effect จะเข้าใจว่าตัวแปร result จะมีค่าเป็น 150
    result (150)  = 20 * 5 + 30  + 20
แต่ความจริงแล้วเป็น 200
    result (200)  = 30 * 5 + 30 + 20    
 
2. การเรียกฟังก์ชั่นที่พารามิเตอร์ทำงานแบบ out mode หรือ inout mode ซึ่งค่าถูกส่งกลับไปยังเออร์กูเมนต์ที่ปรากฎในนิพจน์ เช่น

void main( ){
  int A = 20, B = 5; 
 result  = A * B + myfunction (&A) + 20; 

}
void myfunction(int *p){
      *p = *p + 10;
}
ความเข้าใจจะเหมือนกรณีแรก

วิธีแก้ไข
พยายามหลีกเลี่ยงการเขียนโค้ดในลักษณะนี้ หากไม่แน่ใจว่าภาษาการโปรแกรมจะมีลำดับการประเมินผลอย่างไร เมื่อมีการเรียกฟังก์ชั่นในนิพจน์ ?
อาจจัดเรียงนิพจน์ใหม่ เช่น

result = A * B + 20;
result = result + myfunction( );
หรือ
result = result =+ myfunction(&A);

ซึ่งเมื่อเรียงใหม่ค่า A ที่นำไปคูณกับ B จะมีค่าเท่ากับ 20 ตามความเข้าใจแรกเริ่ม

Referential transparency 

สมมติ
result1 = (myfunction (A) + B) / (myfunction  (A) - C);
temp = myfunction(A);
result2 = (temp + B) / (temp - C);

ถ้าการเรียกฟังก์ชั่นไม่มีการแก้ไขค่าตัวแปรใดๆ  (A , B, C) ที่เกี่ยวข้องกับนิพจน์  คำสั่งบรรทัดแรกและบรรทัดที่สาม น่าจะได้ผลลัพธ์เดียวกัน  เนื่องจากไม่เกิด Functional side effect
เราจะเรียกว่าโปรแกรมมีคุณสมบัติ Referential transparency
แต่ถ้าเกิดเรียกฟังก์ชั่นในบรรทัดแรกมีการแก้ไขค่า A, B หรือ C จากการเรียกฟังก์ชั่นแล้ว  ผลลัพธ์จากคำสั่งในบรรทัดแรก และบรรทัดสุดท้ายจะไม่เท่ากันทันที




No comments:

Post a Comment