Thứ Tư, ngày 30 tháng 12 năm 2009

// // Leave a Comment

EX2 - Lập trình FP trong C# 2.0



Xu hướng hỗ trợ lập trình FP trong .Net đã bắt đầu xuất hiện trong C# 2.0 với một số kỹ thuật như Anonymous method, Generics delegate, Currying hay Closure.

Anonymous method: Là một method không tên được định nghĩa với từ khóa “delegate”. Anonymous method trong C# 2.0 khắc phục những nhược điểm của “Delegate” trong các phiên bản C# 1.x. Nó cho phép “inline code” giúp giảm thiểu số dòng lệnh hay các hàm không cần thiết đôi khi chỉ gọi một lần. Thí dụ dưới đây so sách cách viết của hai phương pháp với hàm f(x,y) = x + y

Thí dụ 1:

DelegateAnonymous Method
delegate int Demo(int x, int y);
public static int Exec(int x,
int y)
{ Demo d = new Demo(AddNumber);
return d(x, y);
}
static int AddNumber(int x, int y)
{ return x + y; }
delegate int Demo(int x, int y);
public static int Exec(int x, int y)
{ Demo d = delegate(int a, int b)
{ return a + b; };
return d(x,y);
}

Bên trong từ khoá delegate của Anonymous Method là một hàm trả về giá trị của hàm f. Rõ ràng cách viết inline gọn hơn.


Generics delegate: để hiểu Generics delegate, trước tiên suy luận về cú pháp của hàm một biến “f: D ? R: f(x) = x” trong các ngôn ngữ C.

Nếu D và R cùng có kiểu integer: f: integer ? integer: f(x) = x

Trong các ngôn ngữ C, hàm f được mô tả dưới dạng: R f (D)

Và khi chuyển vào ngôn ngữ lập trình, cú pháp hàm f đuợc viết thành: int f(int)

Cũng với hàm f, thay vì biểu diễn dạng “R f (D)”, Generics delegate cho phép biến đổi về dạng mới “Func”. Và cú pháp trong ngôn ngữ lập trình trở thành: Func

Trở lại hàm hai biến f(x, y), đoạn mã chương trình dưới đây cộng hai biến x và y:

int add(int x, int y) { return x + y; }

Áp dụng Generics delegate cho hàm f(x,y): “Func” hay “Func”.

Func f = add ;

Thay thế hàm add bằng inline code (Anonymous Method), hàm f(x,y) viết lại thành:

Thí dụ 2:

Func f = delegate(int x, int y) { return x + y; };

Closure: là khả năng các đoạn mã bên trong delegate tham chiếu đến giá trị của những biến không nằm trong phạm vi khai báo của nó. Xét thí dụ dưới đây:


Thí dụ 3:

delegate int Demo(int x);
static int ClosureDemo(int x)
{ int y = 1;
Demo myClosure = delegate(int z) { return z + y; };
y = 99;
return myClosure(x);
}
ClosureDemo(1);

Biến “y = 1” và lệnh gán “y = 99” hoàn toàn nằm ngoài phạm vi “delegate(int z) {return z + y; }”. Kết quả trả về của hàm “ClosureDemo(1)” sẽ không phải là “1+1= 2” mà sẽ là “100”. “myClosure” sẽ không lấy giá trị “y=1” mà nó chỉ tham chiếu đến biến “y” khi cần. Do đó nó đảm bảo giá trị mới nhất luôn được sử dụng.

Ứng Dụng Closure giải quyết tranh chấp dữ liệu trong lập trình đa luồng:

Trong lập trình đa luồng xử lý đồng thời, người lập trình thường phải giải quyết những khó khăn khi phải dùng chung biến giữa các luồng. Giả sử có một yêu cầu tìm kiếm tên trong danh sách và đoạn mã chương trình dưới đây mô phỏng yêu cầu này:

Thí dụ 4:

static string g_Name;
static List people = new List
{ new Person{ Name=”An”, Age=41, Salary = 500},
new Person{ Name=”Huy”, Age=26, Salary = 300},
new Person{ Name=”Thanh”, Age=30, Salary = 400}, };
static bool SearchName(Person p) { return p.Name.Equals(g_Name); }
static List PersonListName(Person p)
{
g_Name = p.Name;
return people.FindAll(SearchName);
}

Chương trình sẽ tìm danh sách tên “Thanh” hoàn toàn chính xác nếu chỉ một luồng xử lý:
p.Name = “Thanh”;
list resultList = PersonListName(p);

Tuy nhiên, vấn đề xảy ra từ biến chia sẻ g_Name nếu ứng dụng là đa luồng xử lý. Luồng thứ nhất tìm tên “Thanh” ( g_Nam= “Thanh”), luồng thứ hai tìm tên “An” (g_Name=“An”). Do đó kết quả tìm kiếm của luồng thứ nhất có thể sai hoàn toàn do biến “g_Name” đã bị thay đổi. Nếu giải quyết bằng cách các luồng phải tuần tự đợi biến “g_Name” được giải phóng thì đó không là giải pháp hay, đôi khi còn gây ra tình trạng tắc nghẽn hay deadlock. Kỹ thuật Closure sẽ không dùng biến “g_Name” và kết hợp hai hàm SearchName, PersonListName thành một:

Thí dụ 5:

static List PersonListName(string name)
{ return people.FindAll(delegate(Person p)
{return p.Name.Equals(name); });
}

Hàm PersonListName hoàn toàn không sử dụng biến dùng chung biến g_Name, Closure được áp dụng lên biến name. Do đó không xảy ra lỗi dùng chung biến giữa các luồng.

Currying: Là phép biến đổi một hàm có hai hay nhiều biến số về một hàm đơn giản hơn, có một biến số. Kỹ thuật Currying dựa trên lý thuyết một hàm f(x, y) sẽ tồn tại một hàm f’(x) , khi đó (f’(x)) (y) = f(x, y). Điều này cũng đồng nghĩa với phép biến đổi:

Func ? Func<>

Hàm f(x,y) với kỹ thuật Currying được viết như sau:

Thí dụ 6:

Func> add = delegate(int x)
{

return delegate(int y)

{ return x + y; };

};
int result = add(1)(2);

Ứng dụng Curring trong Patial Application: “Partial application” là kỹ thuật truyền ít biến số hơn cho một hàm nhiều biến. Lệnh “int result = add(1)(2)” trong thí dụ 6 được thay thế bằng:

Thí dụ 7:
Func sum = add(1);
int three = sum(2);

Xem bài 1 tổng quan về lập trình FP trong .NET

Xem bài tiếp theo Lập trình hàm trong .NET

0 comments: