Lập trình hàm - Bài số 2

  1. Lập trình

Có vẻ bài viết Giới thiệu về lập trình hàm được nhiều bạn thích. Điều đó khuyến khích mình viết tiếp bài số 2 luôn thay vì đợi khi nào rảnh hơn. Trong bài viết tiếp theo này, chúng ta tiếp tục tìm hiểu một số khái niệm và ví dụ về lập trình hàm. Bài viết được chủ ý viết ngắn và đơn giản để người đọc đỡ cảm thấy oải hoặc hại não. Tuy nhiên ở cuối bài viết có một câu hỏi nhỏ cho những bạn nào thích thử thách. ;-)

Giống như trong bài đầu tiên, ngôn ngữ minh hoạ là ngôn ngữ lập trình Scala. Cú pháp của Scala khá giống với Java hoặc Python nên nhiều lập trình viên có thể tiếp cận dễ dàng, đọc mã của Scala là có thể hiểu nếu người đọc đã có kiến thức lập trình cơ sở, kể cả khi chưa lập trình Scala bao giờ.

1. Hàm hạng nhất

Scala nói riêng và các ngôn ngữ lập trình hàm nói chung đều có khái niệm first-class function, nôm na là hàm hạng nhất. Ý của nó đơn giản là ngoài việc khai báo hàm như trong các ngôn ngữ lập trình mệnh lệnh thì hàm trong Scala có thể coi như các kiểu dữ liệu, dùng được trong các hàm khác.

Ví dụ, nếu ta quan tâm tới chức năng tăng thêm 1 vào một biến số nguyên thì trong Scala ta có thể khai báo một hàm như sau:

(x: Int) => x + 1

Như đã giải thích trong bài viết trước, dấu mũi tên kí hiệu phép biến đổi hàm, chuyển từ x thành x + 1. Hàm này đang tạm không có tên gì cả và là hàm một biến số. Tất nhiên là ta có thể đặt tên cho kiểu hàm này nếu thấy cần thiết.

Bây giờ, ta khai báo một biến kiểu hàm, hệt như cách bạn khai báo một biến thuộc kiểu T nào đó trong các ngôn ngữ lập trình bạn quen thuộc khác, như sau:

val f = (x: Int) => (x+1)

và sau đó ta gọi hàm f với một tham số x cụ thể nào đó, ví dụ f(10). Ta sẽ thấy kết quả là 11. Ở đây, hàm này có chức năng tăng thêm 1, nên để cho trực quan khi chương trình dài và lập trình sản phẩm thật thì ta nên đặt tên nó là increase, thay vì là f.

Tất nhiên là hàm để tính toán trong thực tế thường rất phức tạp chứ không phải chỉ cộng 1 như trên. Ta có thể viết thoải mái logic của hàm, như khi lập trình bình thường với cả phần thân của hàm. Ví dụ:

val f = (x: Int) => { val u = 3*x*x + 2*x + 1 u + 1 }

Khi gọi f(3), bạn sẽ thấy kết quả là 35. Chú ý rằng trong Scala, ta không cần dùng từ khoá return để báo là trả về giá trị. Lệnh cuối cùng trong thân hàm sẽ được tính và kết quả của nó sẽ được trả về.

2. Sử dụng hàm

Giả sử ta có một danh sách mẫu dữ liệu gồm các số nguyên như sau:

val xs = List(1, 3, 5, 7, 9, 11, 13, 15)

và ta quan tâm là khi tính toán trên các dữ liệu này bằng hàm f thì những mẫu dữ liệu nào cho kết quả thoả mãn một điều kiện nào đó, ví dụ kết quả nhỏ hơn 500.

Cách giải quyết bằng tư duy lập trình hàm giống như trong bài giới thiệu ở trước, ta làm hai bước:

  • Áp dụng hàm f lên từng mẫu của danh sách xs, ta sẽ thu được một danh sách mới;
  • Lọc danh sách này, giữ lại những số thoả mãn điều kiện lọc. Ở đây, điều kiện lọc là một hàm dạng (x: Int) => Boolean. Và ta có thể dùng hàm này làm đối số của hàm lọc. 

Ta làm điều này bằng chuỗi lệnh tương ứng mapfilter:

val ys = xs.map(x => f(x)).filter(x => x < 500)

Và như đã giới thiệu trong bài 1, ta có thể viết gọn hơn bằng ký hiệu gạch dưới:

val ys = xs.map(f(_)).filter(_ < 500) 

Kết quả sẽ là List(7, 35, 87, 163, 263, 387)

Trong các ví dụ trên, để đơn giản thì ta dùng hàm một biến, và kiểu hàm trả về là đơn giản. Tất nhiên, hàm có thể nhiều biến, hoạt động trên 2 hoặc nhiều hơn 2 biến, trả về kết quả thuộc một kiểu nào đó. Ví dụ, đây là một hàm hai biến, có kết quả là một cặp: một xâu và một danh sách số nguyên.

(u: String, v: Int) => (String, List[Int])

3. Ví dụ hơi hơi hại não

Nếu bạn đã hiểu những vấn đề cơ bản ở trên, bạn có thể tự tra cứu các hàm hay dùng trên các kiểu dữ liệu hay dùng của Scala, ví dụ các kiểu danh sách (List), ánh xạ (Map, còn gọi là kiểu từ điển trong Python), vân vân.

Thử thách nho nhỏ cho bạn như sau. Hãy cho biết đoạn mã Scala sau làm điều gì và kết quả của nó là gì?

val s = "tom loves jerry but jerry loves a dog and that dog loves jerry" val ys = s.split("\\s+") .map(x => (x, 1)) .groupBy(_._1) .mapValues(_.size) println(ys)

Tất nhiên, để biết kết quả của nó thì dễ, bạn sao chép và cắt dán vào trình thông dịch Scala là xong. Tuy nhiên, việc giải thích cách hoạt động của mỗi lệnh mới là quan trọng. ;-) 

Và cuối cùng, sau khi hiểu đoạn mã Scala với phong cách lập trình hàm trên đây làm điều gì, bạn thử làm điều tương tự bằng một ngôn ngữ dạng lập trình mệnh lệnh như Java. Bạn sẽ thấy lí do vì sao mà nhiều lập trình viên nếu đã tư duy và lập trình với Scala thạo rồi thì ít khi muốn quay lại lập trình Java, trừ phi bắt buộc phải làm điều đó.

Từ khóa: 

lập trình hàm

,

scala

,

lập trình

Rất bổ ích cho các bạn trẻ, cảm ơn anh
Lê Hồng Phương
Trả lời
Rất bổ ích cho các bạn trẻ, cảm ơn anh
Lê Hồng Phương