Tải bản đầy đủ (.docx) (9 trang)

Tư duy về lập trình hàm

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (166.65 KB, 9 trang )

Tư duy về lập trình hàm: Suy nghĩ theo lập
trình hàm, Phần 1
Học cách suy nghĩ như một người lập trình hàm
Neal Ford, Kiến trúc phần mềm, ThoughtWorks
Tóm tắt: Lập trình hàm gần đây đã tạo ra mối quan tâm lớn khi tự nhận là lỗi ít hơn và năng
suất cao hơn. Nhưng nhiều nhà phát triển đã thử và đã không hiểu điều gì làm cho các ngôn ngữ
lập trình hàm hấp dẫn đối với một số kiểu công việc. Việc học cú pháp của một ngôn ngữ lập
trình mới là dễ dàng, nhưng việc học để suy nghĩ theo một cách khác là khó. Trong bài đăng đầu
tiên của loạt bài trong mục Tư duy về lập trình hàm, Neal Ford giới thiệu một số khái niệm lập
trình hàm và thảo luận cách sử dụng chúng cả trong Java™ lẫn trong Groovy.
Xem thêm bài trong loạt bài này
Ngày: 22 06 2012
Mức độ: Trung bình
Bản gốc tiếng Anh tại đây
Hoạt động: 6124 lần đọc
Góp ý kiến: (Xem | Thêm bình luận - Đăng nhập)
Điểm trung bình (10 Biểu quyết)
Chấm điểm bài này
Giới thiệu về loạt bài này
Loạt bài này nhằm mục đích định hướng lại cách nhìn của bạn đối với tư duy về lập trình hàm,
giúp bạn xem xét các vấn đề chung theo các cách mới và tìm mọi cách để cải tiến việc viết mã
hàng ngày của bạn. Loạt bài này khám phá các khái niệm, các khung công tác lập trình hàm, cho
phép lập trình hàm trong phạm vi ngôn ngữ Java, các ngôn ngữ lập trình hàm chạy trên JVM và
một số khuynh hướng phát triển trong tương lai của thiết kế ngôn ngữ lập trình. Loạt bài này
được nhằm đến các nhà phát triển Java, những người biết về Java và cách hoạt động trừu tượng
của nó nhưng có rất ít hoặc chưa có chút kinh nghiệm nào về sử dụng một ngôn ngữ lập trình
hàm.
Chúng ta hãy tạm thời nói rằng bạn là một người đốn gỗ. Bạn có rìu tốt nhất trong rừng, nó giúp
cho bạn trở thành một người đốn gỗ năng suất nhất trong trại. Sau đó một ngày, một ai đó xuất
hiện và tán dương những ưu điểm của một mẫu hình chặt cây mới, đó là cái cưa xích. Anh chàng
bán hàng đầy thuyết phục, vì vậy bạn mua một cái cưa xích, nhưng bạn không biết nó hoạt động


như thế nào. Bạn cố gắng nhấc nó lên và cọ vào cái cây với một lực lớn, đó là cách mà các mẫu
hình chặt cây khác của bạn làm việc. Bạn sẽ nhanh chóng kết luận rằng cái cưa xích mới lạ này
chỉ là một mốt nhất thời và bạn quay trở lại chặt cây bằng rìu của mình. Sau đó, một ai đó đến và
chỉ cho bạn thấy cách chặt cây bằng cưa xích.
Chắc là bạn cũng có thể liên quan đến câu chuyện này, nhưng là với lập trình hàm thay cho cái
cưa xích. Vấn đề với một mẫu hình lập trình mới hoàn toàn không phải là việc học một ngôn ngữ
mới. Rốt cuộc thì cú pháp ngôn ngữ chỉ là các chi tiết. Phần nan giải là học suy nghĩ theo một
cách khác. Và tôi vào cuộc — tay chạy cưa xích và người lập trình hàm.
Chào mừng bạn đến với Tư duy về lập trình hàm. Loạt bài này khám phá chủ đề về lập trình
hàm, nhưng không phải là duy nhất về các ngôn ngữ lập trình hàm. Như tôi sẽ minh họa, việc
viết mã theo một cách "lập trình hàm" chạm đến thiết kế, các thỏa hiệp, các khối xây dựng có thể
sử dụng lại khác nhau và một loạt những hiểu biết khác. Càng nhiều càng tốt, tôi sẽ cố gắng để
trình bày các khái niệm lập trình hàm trong Java (hoặc các ngôn ngữ gần-với-Java) và chuyển
sang ngôn ngữ khác để chứng minh các khả năng còn chưa có trong ngôn ngữ Java. Tôi sẽ không
bỏ qua nền tảng sâu và nói về những điều rất hiện đại như monads (xem Tài nguyên) ngay bây
giờ (mặc dù chúng ta cũng sẽ đi tới đó). Thay vào đó, dần dần tôi sẽ cho bạn thấy một cách suy
nghĩ mới về các vấn đề (mà bạn đã áp dụng ở một số nơi — bạn chỉ chưa nhận ra nó thôi).
Bài đăng này và hai bài tiếp theo coi như một chuyến du lịch ngắn về một số chủ đề liên quan
đến lập trình hàm, bao gồm các khái niệm cốt lõi. Một số các khái niệm đó sẽ được quay trở lại
chi tiết hơn khi tôi đã xây dựng thêm nhiều bối cảnh và sắc thái hơn trong suốt loạt bài này. Là
một điểm khởi hành cho chuyến du lịch, tôi sẽ để bạn xem xét hai cách triển khai thực hiện khác
nhau của một vấn đề, một cách được viết có tính chất mệnh lệnh và một cách khác với xu hướng
lập trình hàm hơn.
Trình phân loại số
Để nói về các phong cách lập trình khác nhau, bạn phải có mã để so sánh. Ví dụ đầu tiên của tôi
là một biến thể của bài toán viết mã xuất hiện trong cuốn sách của tôi Nhà lập trình năng suất
(xem Tài nguyên) và trong "Thiết kế dựa vào kiểm tra, Phần 1" and "Thiết kế dựa vào kiểm tra,
Phần 2" (hai bài đăng trong loạt bài trước của tôi trên developerWorks Kiến trúc tiến hóa và thiết
kế mới nổi). Tôi chọn mã này ít nhất cũng một phần vì hai bài viết đó mô tả thiết kế mã một cách
sâu sắc. Không có gì sai với thiết kế được tán dương trong hai bài viết đó, nhưng tôi sắp cung

cấp một lý lẽ cơ bản cho một thiết kế khác ở đây.
Các yêu cầu nói rõ rằng, cho bất kỳ số nguyên dương lớn hơn 1 nào, bạn phải phân loại nó, là
perfect (hoàn hảo), abundant (dư thừa) hay deficient (thiếu). Một số hoàn hảo là một số mà các
thừa số của nó (không bao gồm chính số đó) cộng dồn lại bằng chính nó. Tương tự như vậy, tổng
các thừa số của số dư thừa sẽ lớn hơn số đó và tổng các thừa số của số thiếu hụt sẽ nhỏ chính số
đó.
Trình phân loại số kiểu mệnh lệnh
Một lớp kiểu mệnh lệnh đáp ứng các yêu cầu này xuất hiện trong Liệt kê 1:
Liệt kê 1. NumberClassifier, giải pháp kiểu mệnh lệnh cho bài toán này

public class Classifier6 {
private Set<Integer> _factors;
private int _number;
public Classifier6(int number) {
if (number < 1) throw new
InvalidNumberException("Can't classify negative numbers");
_number = number;
_factors = new HashSet<Integer>>();
_factors.add(1);
_factors.add(_number);
}
private boolean isFactor(int factor) {
return _number % factor == 0;
}
public Set<Integer> getFactors() {
return _factors;
}
private void calculateFactors() {
for (int i = 1; i <= sqrt(_number) + 1; i++)
if (isFactor(i)) addFactor(i);

}
private void addFactor(int factor) {
_factors.add(factor);
_factors.add(_number / factor);
}
private int sumOfFactors() {
calculateFactors();
int sum = 0;
for (int i : _factors) sum += i;
return sum;
}
public boolean isPerfect() {
return sumOfFactors() - _number == _number;
}
public boolean isAbundant() {
return sumOfFactors() - _number > _number;
}
public boolean isDeficient() {
return sumOfFactors() - _number < _number;
}
public static boolean isPerfect(int number) {
return new Classifier6(number).isPerfect();
}
}
Một số thứ đáng lưu ý về đoạn mã này:
• Nó các bài kiểm tra đơn vị có phạm vi rộng (một phần là do tôi đã viết nó để thảo luận về
phát triển dựa vào kiểm tra).
• Lớp này có chứa một số lớn các phương thức gắn kết, là một tác dụng phụ của việc sử
dụng phát triển dựa vào kiểm tra trong khi xây dựng nó.
• Một sự tối ưu hóa hiệu năng được nhúng vào trong phương thức calculateFactors().

Phần chủ yếu của lớp này gồm có việc thu thập các thừa số để sau đó tôi có thể cộng lại
và cuối cùng phân loại chúng. Các thừa số luôn có thể được thu gom theo từng cặp. Ví
dụ, nếu số đang xét là 16, khi tôi lấy thừa số 2 thì tôi cũng có thể lấy 8 vì 2 x 8 = 16. Nếu
tôi thu gom các thừa số theo từng cặp, tôi chỉ cần kiểm tra các thừa số chưa vượt quá căn
bậc hai của số đích mà thôi, đó chính xác là những gì phương thức calculateFactors()
làm.
Trình phân loại số kiểu lập trình hàm (hơn một chút)
Vẫn sử dụng chính các kỹ thuật phát triển dựa vào kiểm tra, tôi đã tạo ra một phiên bản thay thế
của trình phân loại này, hiển thị trong Liệt kê 2:
Liệt kê 2. Trình phân loại số kiểu lập trình hàm (hơn một chút)

public class NumberClassifier {
static public boolean isFactor(int number, int potential_factor) {
return number % potential_factor == 0;
}
static public Set<Integer> factors(int number) {
HashSet<Integer> factors = new HashSet<Integer>();
for (int i = 1; i <= sqrt(number); i++)
if (isFactor(number, i)) {
factors.add(i);
factors.add(number / i);
} return factors;
}
static public int sum(Set<Integer> factors) {
Iterator it = factors.iterator();
int sum = 0;
while (it.hasNext()) sum += (Integer) it.next();
return sum;
}
static public boolean isPerfect(int number) {

return sum(factors(number)) - number == number;
}
static public boolean isAbundant(int number) {
return sum(factors(number)) - number > number;
}
static public boolean isDeficient(int number) {
return sum(factors(number)) - number < number;
}
}
Các khác biệt giữa hai phiên bản này của trình phân loại hơi khó thấy nhưng quan trọng. Sự khác
biệt chính là không có trạng thái chia sẻ một cách có chủ đích trong Liệt kê 2. Việc loại bỏ (hoặc
ít nhất giảm đi) trạng thái chia sẻ là một trong những trừu tượng hóa ưa thích trong lập trình hàm.
Thay vì chia sẻ trạng thái xuyên qua các phương thức dưới dạng các kết quả trung gian (xem
trường factors trong Liệt kê 1), tôi gọi các phương thức trực tiếp, loại bỏ trạng thái. Từ quan
điểm thiết kế, nó làm cho phương thức factors() dài hơn, nhưng nó ngăn không cho trường
factors () "thoát ra" ngoài phương thức. Cũng lưu ý rằng phiên bản của Liệt kê 2 có thể hoàn
toàn chứa các phương thức tĩnh. Không tồn tại kiến thức chia sẻ nào giữa các phương thức, vì
vậy tôi có ít nhu cầu đóng gói hơn theo phạm vi. Tất cả các phương thức làm việc hoàn toàn tốt
nếu bạn cung cấp cho chúng các kiểu tham số đầu vào mà chúng mong đợi. (Đây là một ví dụ về
một hàm thuần túy, một khái niệm tôi sẽ tiếp tục nghiên cứu trong một bài đăng sắp tới).
Về đầu trang
Các hàm
Lập trình hàm là một lĩnh vực rộng, trải dài trong khoa học máy tính, đã chứng kiến một sự quan
tâm đột biến gần đây. Các ngôn ngữ lập trình hàm mới trên JVM (ví dụ như Scala và Clojure) và
các khung công tác (như Akka và Functional Java) hiện có (xem Tài nguyên), cùng với các tuyên
bố tự nhận là lỗi ít hơn, năng suất hơn, trông đẹp hơn, kiếm tiền nhiều hơn và v.v Thay vì cố
gắng giải quyết toàn bộ chủ đề của lập trình hàm, tôi sẽ tập trung vào một số khái niệm then chốt
và làm theo một số gợi ý thú vị bắt nguồn từ các khái niệm đó.
Cốt lõi của lập trình hàm là hàm, cũng như các lớp là trừu tượng hóa chính trong các ngôn ngữ
hướng đối tượng. Các hàm tạo thành các khối xây dựng để xử lý và được nhúng với một số tính

năng chưa thấy trong các ngôn ngữ mệnh lệnh truyền thống.
Các hàm bậc cao hơn
Các hàm bậc cao hơn có thể hoặc nhận các hàm khác làm các đối số hoặc trả chúng về như các
kết quả. Chúng ta không có cấu kiện này trong ngôn ngữ Java. Cái gần nhất mà bạn có thể tiến
đến là sử dụng một lớp (thường là một lớp vô danh) như là một "thùng chứa" (holder) của một
phương thức mà bạn cần thực hiện. Java không có các hàm (hoặc các phương thức) đứng độc
lập, vì vậy chúng không thể được trả về từ các hàm hoặc được chuyển vào như các tham số.
Khả năng này là quan trọng trong các ngôn ngữ lập trình hàm ít nhất với hai lý do. Đầu tiên, các
hàm bậc cao hơn có nghĩa là bạn có thể giả định về các phần ngôn ngữ phù hợp với nhau như thế
nào. Ví dụ, bạn có thể loại bỏ toàn bộ các loại phương thức trên một hệ thống phân cấp các lớp
bằng cách xây dựng một cơ chế chung duyệt qua các danh sách và áp dụng một (hoặc nhiều)
hàm bậc cao hơn cho mỗi phần tử. (Tôi sẽ chỉ cho bạn một ví dụ về cấu kiện này ngay sau đây).
Thứ hai, bằng cách cho phép các giá trị trả về là các hàm, bạn tạo ra cơ hội để xây dựng hệ thống
rất năng động, có tính thích nghi cao.
Các vấn đề có thể dẫn đến giải pháp bằng cách sử dụng các hàm bậc cao hơn không phải là duy
nhất cho các ngôn ngữ lập trình hàm. Tuy nhiên, cách bạn giải quyết các vấn đề sẽ khác nhau khi
bạn đang suy nghĩ theo lập trình hàm. Hãy xem xét ví dụ trong Liệt kê 3 (lấy từ một cơ sở mã
lớn hơn) của một phương thức truy cập dữ liệu có bảo vệ:
Liệt kê 3. Khuôn mẫu mã có khả năng sử dụng lại tiềm năng

public void addOrderFrom(ShoppingCart cart,String userName, Order order)
throws Exception {
setupDataInfrastructure();
try {
add(order, userKeyBasedOn(userName));
addLineItemsFrom(cart, order.getOrderKey());
completeTransaction();
}
catch (Exception condition) {
rollbackTransaction();

throw condition;
}
finally { cleanUp(); }
}
Mã trong Liệt kê 3 thực hiện khởi tạo, thực hiện một số công việc, hoàn tất giao dịch nếu tất cả
mọi thứ đã thành công, hủy giao dịch nếu trái lại, và cuối cùng là xóa tài nguyên. Rõ ràng, có thể
sử dụng lại phần soạn sẵn của mã này và chúng tôi thường làm như vậy trong các ngôn ngữ
hướng đối tượng bằng cách tạo cấu trúc. Trong trường hợp này, tôi sẽ kết hợp hai trong Các mẫu
thiết kế của Bộ tứ (xem Tài nguyên): Các mẫu Lệnh và Phương thức khuôn mẫu. Mẫu của
Phương thức khuôn mẫu (Template Method) gợi ý rằng tôi nên di chuyển mã soạn sẵn phổ biến
lên hệ thống phân cấp thừa kế, trì hoãn các chi tiết thuật toán xuống cho các lớp con. Mẫu thiết
kế Lệnh (Command) đưa ra một cách để đóng gói hành vi trong một lớp với các ngữ nghĩa thi
hành đã biết rõ. Liệt kê 4 cho thấy kết quả của việc áp dụng hai mẫu này cho mã trong Liệt kê 3:
Liệt kê 4. Mã lệnh được cấu trúc lại

public void wrapInTransaction(Command c) throws Exception {
setupDataInfrastructure();
try {
c.execute();
completeTransaction();
}
catch (Exception condition) {
rollbackTransaction();
throw condition;
}
finally {
cleanUp();
}
}
public void addOrderFrom(final ShoppingCart cart, final String

userName, final Order order) throws Exception {
wrapInTransaction(new Command() {
public void execute() {
add(order, userKeyBasedOn(userName));
addLineItemsFrom(cart, order.getOrderKey());
}
});
}
Trong Liệt kê 4, tôi trích ra các phần chung nhất của mã này thành phương thức
wrapInTransaction() (bạn có thể nhận ra các ngữ nghĩa của nó — nó cơ bản là một phiên bản
đơn giản của TransactionTemplate của Spring), chấp nhận một đối tượng Command làm đơn vị
công việc. Phương thức addOrderFrom() thu lại thành định nghĩa một lớp bên trong vô danh,
tạo ra lớp Command, gói gọn hai mục công việc.
Việc gói gọn hành vi mà tôi cần trong một lớp lệnh chỉ là một tạo phẩm của thiết kế của Java, mà
không bao gồm bất kỳ loại hành vi đứng độc lập nào. Tất cả hành vi trong Java phải lưu trú bên
trong một lớp. Thậm chí các nhà thiết kế ngôn ngữ đã nhanh chóng nhận thấy khiếm khuyết
trong thiết kế này — với nhận thức muộn màng, thật khá ngây thơ khi nghĩ rằng không bao giờ
có thể có hành vi mà không gắn liền với một lớp. JDK 1.1 đã sửa chữa khiếm khuyết này bằng
cách thêm vào các lớp bên trong vô danh, mà ít nhất chúng đã cung cấp lớp vỏ cú pháp để tạo ra
nhiều lớp nhỏ chỉ với một vài phương thức hoàn toàn là lập trình hàm, chứ không là cấu trúc. Để
đọc một bài luận cực kỳ thú vị và hài hước về khía cạnh này của Java, hãy xem bài "Thi hành
trong Vương quốc của các danh từ" của Steve Yegge (xem Tài nguyên).
Java buộc tôi tạo ra một cá thể của một lớp Command, mặc dù mọi thứ tôi thực sự cần là phương
thức bên trong lớp đó. Chính lớp đó không mang lại lợi ích gì: nó có không có trường nào, không
có hàm tạo nào (ngoài hàm tạo được sinh ra tự động từ Java) và không có trạng thái. Nó chỉ là
một trình bao (wrapper) cho hành vi bên trong phương thức. Trong một ngôn ngữ lập trình hàm,
việc này thường được xử lý thông qua một hàm bậc cao hơn để thay thế.
Nếu tôi sẵn sàng rời khỏi ngôn ngữ Java một lát, về ngữ nghĩa, tôi có thể đến gần lý tưởng lập
trình hàm bằng cách sử dụng các bao đóng (closure). Liệt kê 5 cho thấy một ví dụ được cấu trúc
lại tương tự, nhưng bằng cách sử dụng Groovy (xem Tài nguyên) thay cho Java:

Liệt kê 5. Sử dụng các bao đóng của Groovy thay cho lớp lệnh

def wrapInTransaction(command) {
setupDataInfrastructure()
try { command() completeTransaction() }
catch (Exception ex) {
rollbackTransaction() throw ex
} finally { cleanUp() }
}

def addOrderFrom(cart, userName, order) {
wrapInTransaction {
add order, userKeyBasedOn(userName) addLineItemsFrom cart,
order.getOrderKey()
}
}
Trong Groovy, bất cứ thứ gì bên trong các dấu ngoặc ôm {} đều là một khối mã và các khối mã
có thể được chuyển giao làm các tham số, bắt chước một hàm bậc cao hơn. Sau hậu trường,
Groovy đang thực hiện mẫu thiết kế Lệnh (Command) cho bạn. Mỗi khối bao đóng trong
Groovy thực sự là một cá thể kiểu bao đóng của Groovy, trong đó bao gồm một phương thức
call() được tự động gọi ra khi bạn đặt một cặp dấu ngoặc rỗng sau biến đang lưu giữ cá thể bao
đóng. Groovy đã cho phép một số hành vi giống như lập trình hàm bằng cách xây dựng các cấu
trúc dữ liệu thích hợp, với vỏ cú pháp tương ứng, đưa vào trong chính ngôn ngữ đó. Như tôi sẽ
cho thấy trong các bài đăng tương lai, Groovy cũng bao gồm các khả năng lập trình hàm khác
nằm ngoài Java. Tôi cũng sẽ quay lại một số so sánh thú vị giữa các bao đóng và các hàm bậc
cao hơn trong một bài đăng sau.
Các hàm hạng nhất
Các hàm trong một ngôn ngữ lập trình hàm được coi là hạng nhất, có nghĩa là các hàm này có
thể xuất hiện bất cứ ở đâu mà một cấu kiện ngôn ngữ lập trình khác bất kỳ (ví dụ như các biến)
có thể xuất hiện. Sự có mặt của hàm hạng nhất cho phép sử dụng các hàm theo những cách bất

ngờ và buộc suy nghĩ về các giải pháp một cách khác đi, ví dụ như áp dụng các phép toán chung
chung (với các chi tiết sắc thái) cho các cấu trúc dữ liệu tiêu chuẩn. Điều này, đến lượt nó, lại
bộc lộ rõ một sự chuyển dịch căn bản trong tư duy theo các ngôn ngữ lập trình hàm: Hãy tập
trung vào các kết quả, chứ không vào các bước.
Trong các ngôn ngữ lập trình mệnh lệnh, tôi phải suy nghĩ về từng bước nguyên tử trong thuật
toán của mình. Mã trong Liệt kê 1 cho thấy điều này. Để giải quyết bài toán trình phân loại số,
tôi đã phải vạch ra chính xác cách thu thập các thừa số, điều này lại có nghĩa là tôi đã phải viết
mã cụ thể để lặp qua các số để xác định các thừa số. Tuy nhiên khi lặp qua các danh sách, việc
thực hiện các phép toán trên mỗi phần tử, nghe giống như một việc phổ biến thực sự. Hãy xem
xét mã lệnh phân loại số được viết lại, bằng cách sử dụng Khung công tác Java lập trình hàm
(Functional Java framework), xuất hiện trong Liệt kê 6:
Liệt kê 6. Trình phân loại số kiểu lập trình hàm

public class FNumberClassifier {
public boolean isFactor(int number, int potential_factor) {
return number % potential_factor == 0;
}
public List<Integer> factors(final int number) {
return range(1, number+1).filter(new F<Integer, Boolean>() {
public Boolean f(final Integer i) {
return isFactor(number, i);
}
});
}
public int sum(List<Integer> factors) {
return factors.foldLeft(fj.function.Integers.add, 0);
}
public boolean isPerfect(int number) {
return sum(factors(number)) - number == number;
}

public boolean isAbundant(int number) {
return sum(factors(number)) - number > number;
}
public boolean isDeficiend(int number) {
return sum(factors(number)) - number < number;
}
}
Các khác biệt chính giữa Liệt kê 6 và Liệt kê 2 nằm trong hai phương thức: sum() và
factors(). Phương thức sum() lợi dụng một phương thức trên lớp List (Danh sách) trong
Functional Java là phương thức foldLeft(). Phương thức này là một biến thể cụ thể dựa trên
một ý niệm xử lý-danh sách được gọi là một catamorphism, là một sự tổng quát hóa về gập danh
sách. Trong trường hợp này, "fold left" (gập trái) có nghĩa là:
1. Lấy một giá trị đầu và kết hợp nó qua một phép toán với phần tử đầu tiên của danh sách.
2. Lấy kết quả và áp dụng phép toán tương tự như thế cho phần tử tiếp theo.
3. Hãy tiếp tục làm việc này cho đến khi danh sách được khai thác hết.
Lưu ý rằng đây chính là điều bạn làm khi bạn tính tổng các số trong một danh sách: bắt đầu bằng
số không, cộng vào phần tử đầu tiên, lấy kết quả đó và cộng nó vào phần tử thứ hai và tiếp tục
cho đến khi danh sách được dùng đến hết. Functional Java cung cấp hàm bậc cao hơn (trong ví
dụ này là kiểu liệt kê Integers.add) và chịu trách nhiệm áp dụng nó cho bạn. (Tất nhiên, Java
không thực sự có các hàm bậc cao hơn, nhưng bạn có thể viết một hàm tương tự thích hợp nếu
bạn hạn chế nó cho một cấu trúc và kiểu dữ liệu đặc biệt).
Một phương thức hấp dẫn khác trong Liệt kê 6 là factors(), nó minh họa lời khuyên của tôi
"hãy tập trung vào các kết quả, chứ không vào các bước". Bản chất của vấn đề phát hiện các thừa
số của một số là gì? Nói cách khác, cho một danh sách tất cả các số có thể, tăng dần lên đến một
số đích, làm thế nào để tôi xác định những số nào là các thừa số của số đó? Điều này gợi ý một
phép lọc — Tôi có thể lọc toàn bộ danh sách các số, loại bỏ những số nào không đáp ứng tiêu
chuẩn của tôi. Về cơ bản phương thức này hiểu giống như mô tả sau: lấy dải số từ 1 đến số của
tôi (dải này không bao gồm đầu mút, do đó +1); lọc danh sách dựa trên mã trong phương thức
f(), là cách của Functional Java cho phép bạn tạo ra một lớp có các kiểu dữ liệu cụ thể; và trả về
các giá trị.

Mã này cũng minh họa một ý niệm lớn hơn nhiều, như là một xu hướng trong ngôn ngữ lập trình
nói chung. Trước đây, các nhà phát triển đã phải xử lý tất cả các thứ phiền nhiễu như cấp phát bộ
nhớ, thu gom rác và các con trỏ. Theo thời gian, các ngôn ngữ đã đảm nhận trách nhiệm đó nhiều
hơn. Khi các máy tính đã có nhiều sức mạnh hơn, chúng ta đã chuyển giao càng ngày càng nhiều
nhiệm vụ nhàm chán (có thể tự động hóa được) cho các ngôn ngữ và các thời gian chạy. Là một
nhà phát triển Java, tôi đã khá quen với việc nhường lại tất cả các vấn đề bộ nhớ cho ngôn ngữ
lập trình. Lập trình hàm đang mở rộng nhiệm vụ đó, bao gồm cả các chi tiết cụ thể hơn. Rồi thời
gian trôi qua, chúng ta sẽ dành ít thời gian lo lắng về các bước cần thiết để giải quyết một vấn đề
và suy nghĩ nhiều hơn về các quá trình. Theo tiến độ của loạt bài này, tôi sẽ chỉ ra rất nhiều ví dụ
về điều này.
Về đầu trang
Kết luận
Lập trình hàm là một quan niệm hơn là một bộ các công cụ hoặc các ngôn ngữ. Trong bài đăng
đầu tiên này, tôi đã bắt đầu trình bày một số chủ đề về lập trình hàm, trải dài từ các quyết định
thiết kế đơn giản đến một vài tư duy lại vấn đề đầy tham vọng. Tôi viết lại một lớp Java đơn giản
để làm cho nó mang phong cách lập trình hàm nhiều hơn, sau đó bắt đầu đi sâu vào một số chủ
đề đã đưa lập trình hàm vượt ra ngoài việc sử dụng các ngôn ngữ mệnh lệnh truyền thống.
Hai khái niệm dài hạn, quan trọng đầu tiên đã hiện ra ở đây. Trước tiên, hãy tập trung vào các kết
quả, chứ không vào các bước. Lập trình hàm sẽ cố gắng trình bày các bài toán khác đi vì bạn có
các khối xây dựng khác nhau, thúc đẩy các giải pháp. Xu hướng thứ hai tôi cần chỉ ra trong suốt
loạt bài này là chuyển giao các chi tiết nhàm chán cho các ngôn ngữ lập trình và các thời gian
chạy, cho phép chúng ta tập trung vào các khía cạnh duy nhất của các bài toán lập trình của
chúng ta. Trong bài đăng tiếp theo, tôi tiếp tục xem xét các khía cạnh chung về lập trình hàm và
nó áp dụng cho việc phát triển phần mềm hiện nay như thế nào

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×