Just algorithm!
Functional Programming ใน C#

คุณจะเขียน Program ยังไงไม่ให้มีการใช้ variable ครับ?
ปกติเวลาเราเขียน C# กันก่อนหน้านี้ เราก็รู้ว่าคำสั่งต่าง ๆ ใน C# Run ทีละบรรทัด 1,2,3,4 เรียงกันไป
ดังนั้นเราก็ต้องพึ่งพา variable ในการเก็บข้อมูลเพื่อทำงานในคำสั่งถัดไป
เช่น ถ้าเราต้องการค่า 10! (10*9*8*...*1) คุณจะทำอย่างไรครับ เขียนอย่างนี้ใช่ไหมครับ

int n = 1;
for (int m = 10; m > 1; m--)
    n *= m;
Console.WriteLine(n);
//3628800


เห็นไหมครับคุณประกาศตัวแปรตั้ง 2 ตัว m กับ n
แล้วก็ Code อีก 3-4 บรรทัด ซึ่งอ่านทั้งหมด ต้องเอามาคิดต่อว่านี่คือการทำ 10!
และไอ้ค่า m,n พวกนี้แหละครับที่คุณต้องคอยตาม Debug เวลา Program ทำงานผิดพลาด Smiley

Functional Programming (FP) จะคิดต่างกัน
FP จะไม่มองการทำงานเป็นชุดคำสั่ง
FP จะมองการทำงานเป็น Expression (นิยาม)
จากตัวอย่างข้างบนจะมองว่า 10! คือ Factorial ของ 10
นิยามของ Factorial คือ



ถ้าเป็นภาษา Haskell ซึ่งถือว่าเป็น Pure functional programming เขียนอย่างนี้ครับ

fac 0 = 1
fac n = n * fac (n-1)


เวลาจะใช้ Function ก็

fac 10

เห็นไหมครับไม่มี variable ที่ประกาศใช้เองใน Function
และไม่มีการ assign ค่า มีแต่ส่งผ่านค่าไป Function ถัดไป
แถม Code ก็เข้าใจง่าย เพราะเป็นนิยามของ Function นั้น ๆ Smiley

เนื่องจาก FP นี้เน้นการทำงานกับ Function มาก
จึงเกิด Concept ในการใช้งานขึ้นอย่างมากมาย
พื้นฐานการทำงาน โดยทั่วไปของ FP มีดังนี้ครับ

Recursion หรือ การที่ Function เรียก Function ตัวเอง เช่น Function Factorial ตามตัวอย่าง
ตัว C# Support การทำ Recursion ในระดับ member ของ Class ครับ
แต่ไม่ Support ในระดับ anonymous methods
แต่ก็สามารถแก้โดย assign ค่าเป็น null ก่อนได้ครับ

Func<int, int> fac = null;
fac = n => (n == 0) ? 1 : n * fac(n - 1);


อย่างไรก็ดีการทำแบบนี้มีจุดอ่อนคือ ค่า fac นี้ ถ้าไปเปลี่ยนค่ามัน การทำงานจะผิดทันทีครับ
ดูตัวอย่างและวิธีการแก้ไข ที่นี่

Pattern Matching หรือ การเปรียบเทียบ Input เพื่อเลือกการทำงานของ Function
เช่น ในตัวอย่าง fac 0 คืนค่าเป็น 1 แต่ถ้าเป็น fac n คืนค่าเป็น n * fac (n-1)
C# ไม่รองรับการทำ Pattern Matching ครับ Smiley
แต่ไม่เป็นไรครับผมมี Code มาแก้ให้พอถูไถไปได้ Smiley

public static Func<T, TResult> When<T, TResult>(this Func<T, TResult> func, Func<T, bool> predicate, Func<T, TResult> alternative) {
    return arg => predicate(arg) ? alternative(arg) : func(arg);
}
public static Func<T1, T2, TResult> When<T1, T2, TResult>(this Func<T1, T2, TResult> func, Func<T1, T2, bool> predicate, Func<T1, T2, TResult> alternative) {
    return (arg1, arg2) => predicate(arg1, arg2) ? alternative(arg1, arg2) : func(arg1, arg2);
}


เอา Code ด้านบนไปใส่ใน Class สำหรับทำ Extension Methods นะครับ
Code นี้ ยอมรับ 1-2 parameters นะครับ
เวลาใช้งาน เช่น จะเขียน Function Factorial

Func<int, int> fac = null;
fac = n => n * fac(n - 1);
fac = fac.When(n => n == 0, n => 1);


เวลาประกาศ Function ให้ทำส่วนที่เป็น default ก่อน
เช่นใน Code ด้านบนก็ประกาศ Function ของ factorial ก่อน
แล้วค่อยกำหนดว่า ถ้า n เป็น 0 แล้วค่อยส่ง 1 กลับไป

Higher-order function คือ Function ที่มี Input เป็น Function ไม่ก็ Output เป็น Function หรือทั้ง 2 อย่าง
C# รองรับครับ แต่ตอนประกาศ method จะดูแล้วซับซ้อนนิดนึง
เช่น จะทำ Function สำหรับทำ Derivative
มี Input เป็น Function คณิตศาสตร์นึง และ Output เป็นอีก Function นึงที่เกิดจากการ Diff

Func<Func<double, double>, Func<double, double>> diff =
    f => x => (f(x + 0.000001) - f(x)) / 0.000001;

var cos = diff(Math.Sin);
Console.WriteLine(cos(Math.PI));
//-1
//diff ของ sin ได้ cos, cos(pi) ได้ -1

var inverse = diff(Math.Log);
Console.WriteLine(inverse(2));
//0.5
//diff ของ ln ได้ inverse, inverse(2) ได้ 0.5


ดูบรรทัดแรกสิครับงงไหมครับ
มันเป็นการประกาศว่านี่คือ Function ที่ Input เป็น Function นึง และ Output เป็นอีก Function นึง
ซึ่ง C# ประกาศทียาวมาก ซึ่งถ้าไปเทียบกับ Haskell แล้วเวลาประกาศ แค่นี้เองครับ

diff :: (Double -> Double) -> (Double -> Double)

นี่ขนาด Code ของ Haskell ไม่ได้ลงสีนะครับ ยังอ่านง่ายเลย Smiley

สุดท้ายครับเรื่องของ Currying กับ Partial Function Application
Currying คือการทำให้ Function ที่รับหลาย ๆ Parameters
แตกออกเป็น Function ที่รับแค่ Parameter เดียว หลาย ๆ Function ซ้อนกันไป
ดูตัวอย่าง

Func(a1,a2) => Func(a1)(a2)

จะเห็นว่า a1 กับ a2 แยกออกจากกันแล้ว
ทีนี้เราสามารถ assign ค่า a1 ก่อน เราก็จะได้ Func(a2) มา
การ assign ค่าบางส่วนเพื่อให้ได้ Function ใหม่นี่แหละครับ เรียกว่า Partial Function Application
การทำ Curry และ Partial ใน C# เพิ่ม Code ตามนี้ครับ

public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult< func) {
    return arg1 => arg2 => func(arg1, arg2);
}
public static Func<T1, T2, TResult> Uncurry<T1, T2, TResult>(this Func<T1, Func<T2, TResult>> func) {
    return (arg1, arg2) => func(arg1)(arg2);
}
public static Func<T2, TResult> Partial<T1, T2, TResult>(this Func<T1, T2, TResult> func, T1 arg1) {
    return arg2 => func(arg1, arg2);
}


เอา Code ด้านบนไปใส่ใน Class สำหรับ Extension Methods นะครับ
ทีนี้มาลองดูตัวอย่างกัน

Func<Func<double, double>, Func<double, double>> diff =
    f => x => (f(x + 0.000001) - f(x)) / 0.000001;

var diffAndDo = diff.Uncurry();
Console.WriteLine(diffAndDo(Math.Sin, Math.PI));
//-1

var anotherDiff = diffAndDo.Curry();
var cos = anotherDiff(Math.Sin);
Console.WriteLine(cos(Math.PI));
//-1

var inverse = diffAndDo.Partial(Math.Log);
Console.WriteLine(inverse(2));
//0.5


Method ชื่อ Uncurry ทำให้ Func(a1)(a2) เป็น Func(a1,a2)
โดยปกติ diff เราต้อง assign function 2 ครั้ง
แต่ Uncurry ทำให้เราสามารถ assign Math.Sin กับ Math.PI พร้อมกันใน Function เดียวได้

Curry ก็คือการแตกออกทำให้ Func(a1,a2) เป็น Func(a1)(a2) อีกครั้ง
ทีนี้เราสามารถทำ Partial Apply ได้ ซึ่งทำให้เกิดอีก Function ชื่อ cos

Partial เป็น Method รวบรัด เราไม่ต้องทำ Curry ก่อน แล้วมาทำ Partial Apply 2 ต่อ
Method Partial จะให้โยน parameter แรกเข้าไปเลย ซึ่งทำให้เกิด Function ใหม่ทันที

จบแล้วครับพื้นฐานของ FP
Post หน้าจะเป็นเรื่องของ FP ในการใช้งานจริงครับ

* อ่านจบแล้ว กรุณา Comment ด้วยครับ เพื่อเป็น Feedback และเป็นกำลังใจ
* บทความนี้คัดลอกได้ แต่ต้องมี Link หรือ Url กลับมาที่หน้านี้ และเมื่อคัดลอกแล้วกรุณา Comment แจ้งด้วยครับ




Create Date : 29 กรกฎาคม 2551
Last Update : 9 สิงหาคม 2551 15:22:16 น. 8 comments
Counter : 3121 Pageviews.

 
เนื้อหาแน่น เยียมดีครับ


โดย: nano IP: 203.107.158.162 วันที่: 13 สิงหาคม 2551 เวลา:10:33:55 น.  

 
ชัดเจนดีค่ะ กำลังสอนเรื่องนี้อยู่ด้วย ขอตัวอย่างของ Logic Programming ด้วยสิคะ นะคะ


โดย: R-ree-rat IP: 58.137.131.2 วันที่: 11 พฤศจิกายน 2551 เวลา:16:15:48 น.  

 
Logic Programming ไม่รู้จักเลยครับ
รู้จักแต่ Constraint Programming แทนได้รึเปล่าครับ
เห็นว่ามีลูกผสมด้วยคือ Constraint Logic Programming
ซึ่งผมก็ไม่รู้ว่ามันต่างกันยังไง


โดย: chaowman วันที่: 19 พฤศจิกายน 2551 เวลา:10:15:56 น.  

 
(เขียนเป็นอัลกอรึทึ่มซักหน่อยน่าจะดี)
อยากให้มีตัวอย่างแบบนี้ในภาษา java บ้างจัง


โดย: programoo IP: 202.176.103.12 วันที่: 25 ธันวาคม 2551 เวลา:22:26:17 น.  

 
อ่านแล้วครับ งงมากมายเลยครับ แต่ก็ดีครับแนวนี้ผมชอบ


โดย: rsquare IP: 58.64.51.174 วันที่: 17 มกราคม 2552 เวลา:14:18:56 น.  

 
ดีค่ะ ให้ความรู้ดี


โดย: d IP: 202.176.136.111 วันที่: 2 กรกฎาคม 2553 เวลา:15:13:44 น.  

 
เรียนยังไงก็ไม่ค่อยเข้าใจ T-T แต่ะจะพยายาม ขอบคุณที่ให้ความรู้คะ ดีมากๆเลยคะ


โดย: ทับทิม IP: 125.25.235.233 วันที่: 13 กรกฎาคม 2553 เวลา:7:29:17 น.  

 
ผม print ไปอ่านเลยนะครับ
ถ้าจะให้จ่ายตังค่าเขียนบอกได้ เดี๋ยวมีถูกใจให้ทิปครับ อิอิ


โดย: กอล์ฟ IP: 110.164.240.231 วันที่: 19 ตุลาคม 2553 เวลา:18:44:52 น.  

ชื่อ :
Comment :
  *ใช้ code html ตกแต่งข้อความได้เฉพาะสมาชิก
 

chaowman
Location :
กรุงเทพฯ Thailand

[Profile ทั้งหมด]

ให้ทิปเจ้าของ Blog [?]
ฝากข้อความหลังไมค์
Rss Feed
Smember
ผู้ติดตามบล็อก : 8 คน [?]





New Comments
Group Blog
 
All Blogs
 
Friends' blogs
[Add chaowman's blog to your web]
Links
 

 Pantip.com | PantipMarket.com | Pantown.com | © 2004 BlogGang.com allrights reserved.