(Post 29/08/2008) Trong những năm gần đây,
các ngôn ngữ lập trình của Microsoft không những xuất hiện nhiều kỹ thuật
mới mà còn có sự thay đổi nhanh chóng về cú pháp, câu lệnh... Những sự
thay đổi đó có nguồn gốc từ mô hình lập trình đã có từ lâu: lập trình
“hàm số” hay Functional Programming.
Trong những năm gần đây, các ngôn ngữ lập trình của Microsoft
không những xuất hiện nhiều kỹ thuật mới mà còn có sự thay đổi nhanh chóng
về cú pháp, câu lệnh. Cách viết các đoạn mã chương trình ngắn gọn và dễ
quản lý hơn trước đây rất nhiều và đặc biệt, các dòng lệnh đôi khi mang
tính ước lượng giá trị của các biểu thức toán học hơn là đơn thuần thi
hành một tập các dòng lệnh. Những sự thay đổi đó có nguồn gốc từ mô hình
lập trình đã có từ lâu: lập trình “hàm số” hay Functional Programming
(FP).
ĐÔI NÉT VỀ LẬP TRÌNH FP
Mô hình FP xuất hiện sớm hơn so với lập trình mệnh lệnh
(Imperative Programming) và lập trình hướng đối tượng (Object Oriented
Programming). Tuy nhiên, các ngôn ngữ thông dụng như Java, C++ hay VB.Net,
C# thích hợp cho lập trình OOP hơn là FP. Đối với các ngôn ngữ này, biến
(variable) là phương tiện lưu dữ liệu và giá trị của biến bị truy xuất
và thay đổi liên tục. Biến toàn cục (global) còn có thể được truy cập
và thay đổi giá trị từ một class khác không định nghĩa nó. Chính điều
này đôi khi gây ra những lỗi được gọi là “hiệu ứng lề” trong những ứng
dụng có nhiều luồng (threads) xử lý đồng thời do sự chia sẻ biến giữa
các luồng. Ngược lại, trong lập trình FP thuần túy, khái niệm biến không
tồn tại mà chỉ có “identifier”. Các identifier khi đã được gán một giá
trị thì không thể thay đổi được nữa. Đây là một điểm quan trọng trong
lập trình FP: hạn chế những tác động từ bên ngoài làm thay đổi giá trị
identifier hay giá trị trả về của hàm. Hàm chính là cơ sở trong lập trình
FP, cho nên các đoạn mã chương trình có dạng hàm mang các biến số để ước
lượng các giá trị biểu thức toán học.
Trong .Net, hàm ước lượng độ dài chuỗi string.Length()
cho kết quả trả về hoàn toàn phụ thuộc vào trạng thái của chuỗi lúc đang
gọi. Ngược lại, hàm trong lập trình FP phải luôn luôn trả về một giá trị
duy nhất nếu các biến số của hàm là giống nhau. Hàm chỉ thay đổi giá trị
trả về khi các biến số của hàm thay đổi.
Chính nhờ những ưu điểm như không chịu ảnh hưởng từ bên
ngoài hay giá trị của hàm là duy nhất nên lập trình FP đã giải quyết được
nhiều vấn đề phức tạp trong lập trình đa luồng mà trước đây người lập
trình phải đối mặt như tranh chấp dữ liệu, đồng bộ dữ liệu hay tình trạng
“deadlock” trong trường hợp các luồng phải xử lý tuần tự.
Hiện nay, phần mềm đứng trước yêu cầu phải tận dụng được
sức mạnh của các bộ xử lý đa nhân và FP được xem là mô hình thích hợp
cho yêu cầu này.
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:
Delegate |
Anonymous 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< A, 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);
LẬP TRÌNH FP TRONG C# 3.0
Người lập trình đã dễ dàng tiếp cận với lập trình FP
do nhiều kỹ thuật trong C# 3.0 có điểm chung với FP. Đối với người lập
trình OOP, cảm nhận đầu tiên khi làm việc với C# 3.0 là sự thay đổi về
cú pháp lập trình với các kỹ thuật như Anonymous type, Lambda Expression
hay Lazy evaluation. Kết hợp các kỹ thuật này lại với nhau sẽ giảm thiểu
số lượng công việc và áp lực công việc cho người lập trình, đồng thời
làm tăng tính hiệu quả của sản phẩm do code nhỏ, gọn,
Type Inference – Anonymous type:
Type Inference: Được dùng để khai báo
biến với từ khóa “var” mà không cần định nghĩa kiểu dữ liệu. Trình biên
dịch sẽ tự động suy luận kiểu bằng cách tham chiếu đến giá trị được gán
cho biến. Tuy nhiên kiểu khai báo này chỉ được sử dụng trong phạm vi local.
var Name =”An”;
var Age = “1/1/1980”;
var Numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8,
9, 10};
Anonymous type: được xây dựng trên khái
niệm “Tuple” - tập một dãy dữ liệu. Thí dụ dưới đây có 3 Tuple.
Name:An
salary:300
City: Ho Chi Minh
Anonymous type dùng để định nghĩa một kiểu dữ liệu mới
nhưng không cần chỉ rõ tên và cấu trúc dữ liệu.
var myInfo = new {Name =”An”, Age=”1/1/1980”, Salary=300};
var mySalary = myInfo.Salary * 2;
Biến myInfo được khởi tạo với một kiểu dữ liệu hoàn toàn
mới nhưng không định nghĩa cấu trúc rõ ràng. Phép toán “var mySalary =
myInfo.Salary*2” không gây ra lỗi bởi trình biên dịch hiểu rõ cấu trúc
kiểu dữ liệu của myInfo. Một trong những ưu điểm của Anonymous type là
kết hợp với Type Inference trong việc tham chiếu dữ liệu.
Ứng dụng Type Inference – Anonymous type trong việc tham
chiếu dữ liệu: các dự án ít hay nhiều đều liên quan đến tương tác với
dữ liệu và người lập trình phải viết những đoạn mã để lọc một tập dữ liệu
mới từ tập dữ liệu ban đầu. Tập dữ liệu mới có thể ít field hơn hay có
thể kết hợp với tập dữ liệu khác nhiều field hơn. Công việc này được gọi
là tham chiếu hay “Projection”. Trước đây công việc này thường tốn khá
nhiều thời gian do phải định nghĩa cấu trúc dữ liệu, viết mã lưu dữ liệu
vào cấu trúc mới ... mà đôi khi cấu trúc dữ liệu mới chỉ sử dụng trong
một hàm. Amonumous type trở nên rất hiệu quả trong những yêu cầu như thế.
Với tập dữ liệu ở thí dụ 4, yêu cầu tạo một danh sách những người tên
“Huy” và danh sách mới này với chỉ lấy hai field Name và Salary và thêm
một field mới Allowance có thể xử lý như sau:
Thí dụ 8:
var searchList = from r in people
where r.Name == “Huy”
select new { r.Name, r.Salary, Allowance = r.Salary * 0.7 };
Kiểu dữ liệu mới “searchList” với 3 field “Name, Salary,
Allowance” được tạo mà không cần đòi hỏi quá nhiều thời gian cho việc
định nghĩa cấu trúc, viết code hay phát hiện lỗi. Tuy nhiên “Anonymous
type” chỉ sử dụng trong pham vi local.
Lambda Expression: Anonymous method
hỗ trợ cách viết inline code nhưng chưa có sự thay đổi đáng kể về cú pháp
câu lệnh. Lambda Expression là sự phát triển của Anonymous method, giúp
giải quyết gánh nặng của người lập trình trong việc viết các đoạn mã.
Có thể hiểu Lambda Expression như một hàm mang các biến số. Cú pháp “x
=> x+1” giống như hàm có một biến “x” và giá trị trả về của hàm là
“x+1”. Lambda Expression được xây dựng trên nền của lý thuyết “Lambda
calculus” được phát minh trong thập niên 1930. Kí tự Lambda (?) của người
Hy Lạp được dùng đặt phía trước các biến số của hàm. Thí dụ dưới đây biểu
diễn hàm một biến và hàm hai biến bằng Lambda Expression và cú pháp trong
ngôn ngữ C# 3.0.
f(x) = x f(x,y) = x + y
Lambda Expression x ? x (x,y) ? x + y
C# 3.0 x => x (x,y) => x + y
Nhờ vào cú pháp đơn giản, nên dòng lệnh cũng đơn giản.
Thí du 9:
Func Add = (x, y) => x + y;
Lambda Expression có thể không có một tham số (parameter) nào hoặc có
nhiều parameter và các parameter này cũng có thể được khai báo kiểu hoặc
không cần khai báo kiểu.
n => (n-1)*(n-2)
x => (x % 2) == 0
(int x) => (x % 2) == 0
p => p.Name == “Huy” && p.Age > 25
Các kỹ thuật Closure hay Currying đều có thể sử dụng
trong Lambda Expression.
Closure trong Lambda Expression là khả năng hiểu và kiểm
soát biến không nằm trong phạm vi của biểu thức.
Thí dụ 10:
int x = 99;
Func add = y => y + x;
int firstResult = add(1); // 99 + 1 = 100
int secondResult = add(2); // 99 + 2 = 101
Tuy “y” không là biến local và nó cũng không là parameter
của method nhưng chương trình không báo lỗi và nó được xem như là một
biến “tự do”. Kết quả của biểu thức firstResult là 100 và trong trường
hợp này Closure có vai trò lưu trạng thái giá trị một biến để có thể sử
dụng lại sau.
Currying trong Lambda Expression cho hàm f(x,y):
static Func f(int x)
{ Func add = (y, z) => y + z;
return y => add(x, y);
}
var Currying = f(1);
int three = Currying(2); // = 1 + 2
int four = Currying(3); // = 1 + 3
Thật khó để thấy ứng dụng của Currying vào thực tế. Tuy
nhiên nó có thể thích hợp trong khoa học máy tính cho việc chia nhỏ các
hàm toán học có n biến chứa các phép toán phức tạp thành n-1 hàm đơn giản
hơn.
Query Expression: Đây là sự hỗ trợ ngôn ngữ LINQ cho
C# 3.0 trong việc xây dựng những câu truy vấn inline. Ngoài những câu
lệnh trông giống như ngôn ngữ SQL “Select”, “From”, “Where” (thí dụ 8)
thì việc ứng dụng “Lambda Expression” vào trong “Query Expression” sẽ
làm đơn giản đi công việc truy vấn dữ liệu. Các câu lệnh truy vấn trở
nên ngắn gọn và dễ hiểu.
Thí dụ 8 được viết lại bằng “Lambda Expression”:
Thí dụ 11:
var searchList = people
.Where(p => p.Name == “Huy”)
.Select(p => new { p.Name, p.Salary, allowance = p.Salary * 0.7 });
Một thí dụ tìm tuổi lớn nhất trong danh sách, nếu trước
đây người lập trình phải viết một stored procedure hay một đoạn mã so
sánh các phần tử trong danh sách thì với C# 3.0 chỉ cần một lệnh:
int maxAge = people.Max(p => p.Age);
Một kỹ thuật rất hữu ích trong Query Expression là Extension
Method. Trước đây, đối với các class thuộc hãng thứ ba hay những class
vì lý do nào đó được khai báo đóng kín, người lập trình thường gặp nhiều
khó khăn do không được phép thay đổi hay thêm những method cho phù hợp
với yêu cầu mới. Thí dụ class Integer của C#, người lập trình không thể
thêm method Square như lệnh gọi dưới đây:
Thí dụ 12:
int myValue = 2;
myValue.Square();
Class Integer của C# hoàn toàn không có method Square.
Nhờ kỹ thuật Extention Method, người lập trình có thể làm việc với những
yêu cầu như thế. Giả sử class ListPerson chỉ cung cấp duy nhất hàm tìm
kiếm theo tên và không thể thừa kế hay thêm mới vào class này. Nếu có
một yêu cầu tìm kiếm theo tuổi thì người lập trình không thể tự giải quyết
vấn đề. Dùng kỹ thuật Extention Method định nghĩa một class mới dạng static
và bên trong class này định nghĩa một static method với từ khóa this cho
biến số đầu tiên của method. Method Square được định nghĩa:
public static int Square(this int myNumber)
{ return myNumber * myNumber; }
Trở lại yêu cầu tìm kiếm theo tuổi trong danh sách,
tạo một method mới tên MyQuery:
namespace MyExtentionMethod
{ delegate R Func(T t);
static class PersonExtensions
{ public static IEnumerable MyQuery(this IEnumerable
sequence, Func predicate)
{ foreach (T item in sequence)
if (predicate(item))
yield return item; }
}
}
Tìm những nhân viên có tuổi lớn hơn 29 hay có lương lớn
hơn 400:
ListPerson person = new ListPerson(people);
var myListAge = person.MyQuery(p => p.Age >
29);
var myListSalary = person.MyQuery(p => p.Salary
> 400 );
Trông có vẻ như method MyQuery thuộc vào class ListPerson
(person.MyQuery) hơn là class mới định nghĩa PersonExtensions.
Lazy evaluation: Đây là kỹ thuật rất được các ngôn ngữ
lập trình quan tâm, nhưng Lazy evaluation trong C# 3.0 tự nhiên và đơn
giản hơn. Giả sử f(x,y) = x+y chỉ xảy ra khi thỏa điều kiện x >= 0
và y >= 0:
Thí dụ 13:
static Func Add = (x, y) => x + y;
static int MyLazyEvaluation(int x, int y, int function)
{ if (x <= 0 || y <= 0) return 0; else return function;}
int value = MyLazyEvaluation(-1, 2, Add(-1, 2));
Hàm MyLazyEvaluation đuợc truyền giá trị “-1” cho tham
số x, cho nên chương trình sẽ không thi hành hàm Add(-1,2).
Từ khóa yield trong vòng lặp cũng là Lazy evaluation.
Có thể kiểm tra tính Lazy evaluation của từ khóa yield bằng đoạn mã trong
danh sách dưới đây:
static IEnumerable Print(this IEnumerable person)
{ foreach (Person p in person)
{ Console.WriteLine(index.ToString()+”: Person: {0}”,
p.Name);
index += 1;
p.Name = p.Name.ToUpper();
yield return p; }
}
Đoạn mã kiểm tra tính Lazy valuation của thí dụ trên:
Console.WriteLine(“Before using ToUpper()”);
var printPerson = people.Print();
Console.WriteLine(“After using ToUpper()”);
foreach (Person pp in printPerson)
Console.WriteLine(“-- After Upper: {0}”, pp.Name);
Kết quả trên màn hình:
Before using ToUpper()
After using ToUpper()
1: Customer: An
After Upper: AN
2: Customer: Dung
After Upper: DUNG
......
Thí dụ trên cho thấy vòng lặp “foreach (Person p in person)”
trong hàm Print hoàn toàn không thi hành liên tục dòng lệnh ToUpper()
với từ khoá yield. Lệnh ToUpper() một person tiếp theo chỉ được gọi khi
nó thật sự được yêu cầu. Trong trường hợp này là dòng lệnh “foreach (Person
pp in printPerson)” của đoạn mã kiểm tra kết quả Lazy valuation.
Higher Order Function: Tuy là một kỹ
thuật rất cơ bản trong lập trình FP nhưng nó chỉ mới xuất hiện từ C# 2.0.
Higher Order Function xem hàm cũng là dạng dữ liệu cho nên parameter của
hàm cũng là một hàm. Trong các thí dụ trên, có một số ứng dụng Higher
Order Function như thí dụ 4, parameter của method FindAll là hàm SearchName
(people.FindAll(SearchName)). Thí dụ 10, parameter “function” trong hàm
MyLazyEvaluation(int x, int y, int function) có kiểu là integer, nhưng
nó lại được truyền vào là một hàm Add(-1, 2) (“MyLazyEvaluation(-1, 2,
Add(-1, 2))”) .
“Filter”, “Map” và “Reduce” được xem là ba Higher Order
Function rất hữu ích cho các phép toán trong dãy (List, Aray, IEnumerable).
Tuy nhiên Higher Order Function trong C# 2.0 chỉ hỗ trợ cho kiểu dữ liệu
List và Array.
Filter: Các method FindAll hay RemoveAll
được xem là các Filter và parameter của các hàm này là một hàm điều kiện
mà một khi các phần tử trong dãy thỏa điều kiện của hàm thì các phần tử
này sẽ được chọn (Find) hay bị xóa (Remove) khỏi danh sách. Thí dụ tìm
kiếm danh sách những người tên “An”:
people.FindAll(delegate(Person p) { return p.Name.Equals(“An”);
})
Tuy nhiên, nếu muốn tìm một danh sách những người tên
“Huy”, người lập trình lại phải viết thêm một Anonymous Delegate tương
tự như thế. Để tránh trường hợp này, sử dụng Higher Order Function bằng
cách định nghĩa hàm SearchName:
public static Predicate SearchName(string name)
{ return delegate(Person p) { return p.Name.Equals(name);
}; }
......
var AnList = people.FindAll(SearchName(“An”));
var HuyList = people.FindAll(SearchName(“Huy”));
Cách viết trong C# 3.0 ngắn gọn hơn:
var AnList = people.FindAll(p => p.Name.Equals(“An”));
hay
var person = people.Where(p=>p.Name.Equals(“An”));
Như vậy “Filter” trong C# 2.0 tương đồng với “Where”
trong C# 3.0.
Map: Dùng để tạo ra một dãy mới từ một
dãy ban đầu theo một phép toán tương ứng. Hàm ConvertAll() là một “Map”.
Parameter của hàm này cũng là một hàm sẽ ảnh hưởng lên từng phần tử của
dãy. Câu lệnh tạo một danh sách mới với mức lương nhân với hệ số “0.7”:
var allowanceSalary = people.ConvertAll(delegate(Person
p)
{ return p.Salary *= 0.7; });
viết lại với C# 3.0:
var allowanceSalary = people.ConvertAll(p => p.Salary
* 0.7);
hay
var allowanceSalary = people.Select(p => p.Salary
* 0.7);
Như vậy “Map” trong C# 2.0 tương đồng với “Select” trong
C# 3.0.
Reduce: Còn được hiểu như là “Fold”
trong lập trình FP. Đây là khả năng tính toán giá trị của dãy bằng cách
lướt qua tất cả các phần tử trong dãy. Tính tổng tiền lương trong danh
sách:
int total = people.Aggregate(0,
delegate(int currentSum, Person p)
{ return currentSum + p.Salary; });
C# 3.0:
var total = people.Select(p => p.Salary).Aggregate(0,
(x, y) => x + y);
.NET Framework 3.5 cung cấp một số hàm tính toán cho
dãy như Sum, Average, Count, Min, Max. Do đó có thể thấy rằng Filter hay
Map trong C# 2.0 không còn thích hợp cho C# 3.0 bởi vì Lambda Expression
đã hoàn toàn có thể thay thế và LINQ đang ngày càng phổ biến. Thí dụ tính
tổng số lương của những người có tuổi lớn hơn 29:
var total = people.Where(p => p.Age>29).Select(p
=> p.Salary).Sum();
hay
var total = (from p in people where (p.Age > 29)
select p.Salary).Sum();
KẾT LUẬN
Lập trình FP trong .Net tuy còn hạn chế, nhưng tiếp cận
với kỹ thuật này sẽ giúp người lập trình tiết kiệm rất nhiều thời gian
trong việc viết và quản lý code cũng như phát hiện lỗi, đồng nghĩa với
tiết kiệm chi phí và nhân sự cho dự án. Và hơn nữa, FP cho phép khai thác
sức mạnh của các bộ xử lý đa nhân.
Dũng Trần
(theo PC World VN) |