Khái niệm cơ bản về Functional Programming

Functional Programming được sử dụng khá phổ biến ngày nay và trong hầu hết các ngôn ngữ như là Scala, Haskell, Javascript…Nếu bạn đã từng làm việc với ngôn ngữ Scala, chắc bạn cũng không xa lạ gì về khái niệm lập trình hàm mà nó là một trong những điểm mạnh của Scala so với các ngông ngữ khác trước đây mình đã dùng.

Có rất nhiều các khái niệm khác nhau, nhưng trong phạm vi bài viết này chúng ta chỉ đề cập đến:

  •  Sự minh bạch tham chiếu (referential transparency)
  • Nguyên tắc truy cập thống nhất
  • Idempotence

Đây là 3 khái niệm quen thuộc trong Function Programming. Và không cái nào trong số chúng là khó cả, nhưng nó là một trong những thứ mà tôi không thể nhớ bất cứ khi nào tìm thấy chúng.

Minh bạch tham chiếu và hàm thuần tuý

Referential transparency là một thuộc tính của biểu thức. Nó giúp dễ dàng hơn để hiểu về cách xử lý của một chương trình

Một biểu thức thực sự minh bạch tham chiếu tức là nó phải có khả năng được thay thế bởi giá trị của chính nó mà không bị ảnh hưởng bởi các xử lý của chương trình. Điều này nghĩa là biểu thức luôn luôn tính toán trả về các kết quả như nhau trong bất kỳ trường hợp nào.

Nếu một biểu thức không minh bạch tham chiếu, chúng ta có thể gọi biểu thức đó là referential opaque.

Ví dụ về biểu thức minh bạch tham chiếu như sau:

3 + 2
{
  val x = 5
  val y = 37

  x + y * 5
}

Tính minh bạch tham chiếu là điều kiện cần để một hàm là Pure function hay một hàm thuần tuý.  Và với định nghĩa về minh bạch tham chiếu thì chúng ta có thể hiểu rằng một Pure function tức là nó không bị side effect tức là hàm đấy giá trị tính toán không bị ảnh hưởng bởi các tác nhân bên ngoài.

// Đây là một pure function
def pure(x: String): Boolean = {
  x.length > 5
}

và một ví dụ về hàm không phải là Pure function

var global = 100

// đây không phải là pure function
def impure(x: String): Boolean = {
  myFunctionThatWritesIO() // đây là một side effect
  x.length > global // global giá trị có thể bị thay đổi, nên ảnh hưởng đến giá trị trả về
}

Nhưng làm thế nào để bạn có thể viết các hàm với Pure function, mà không bị side effect (Giống như các hoạt động IO), để hữu ích khi xây dựng các chương trình? Vâng, hoàn toàn có thể làm được, bởi vì Pure function nắm bắt các side effect cho các thực thi sau đó. Monad trong Haskell được biết đến nhiều nhất trong ví dụ này. Thay vì thực hiện side effect, hàm sẽ trả về side effect được đóng gói trong một số kiểu cho thực thi sau đấy. Các bước thực hiện tính toán là thuần tuý, nhưng khi nó được chạy không an toàn và không thuần tuý là bởi vì có chứa các IO gây ảnh hưởng.

Cuối cùng, Referential transparency không chỉ được xem như là một định nghĩa đơn thuần, nhưng nó là khởi đầu của rất nhiều lợi ích mà chúng ta có được với Function Programming.

Idempotence

Idempotence là một thuộc tính của các hàm / xử lý nhất định. Một hàm được gọi là idempotant chỉ khi kết quả được áp dụng 2 lần vào cùng một hàm là giống như kết quả được áp dụng một lần vào hàm. ví dụ nếu bạn áp dụng kết quả bằng cách gọi hàm f(x) vào lại chính hàm f này một lần nữa thì chúng ta sẽ nhận được kết quả là như nhau. Bạn thể hình dung như sau

∀x . f(f(x)) = f(x)

Ví dụ như là bạn muốn sắp xếp lại một list thì đó là idempotence bởi vì khi bạn thực hiện sắp xếp lại nó lần thứ 2 là không thay đổi.

Nguyên tắc truy cập thống nhất

Nguyên tắc truy cập  thống nhất được định nghĩa lần đầu tiên trong thế giới hướng đối tượng bởi Bertrand Meyer và nó nói rằng

Tất cả các service được cung cấp bởi một module nên có sẵn thông qua quy tắc thống nhất, không phản ánh liệu chúng có được thực hiện thông qua lưu trữ hay thông qua tính toán

Trong thực tế điều này nghĩa là cú pháp được sử dụng để thao tác các thuộc tính( được lưu trữ) và các phương thức (tính toán) phải như nhau. Một số ngôn ngữ như Java không chú ý nguyên tắc này, như bạn có thể truy cập các field của object thông qua:

obj.field;

Nhưng một phương thức nên được truy cập thông qua:

obj.method(); // lưu ý sự cần thiết của ()

Trong Java, nguyên tắc được mô phỏng thông qua sử dụng getter/setter:

obj.getField(); // nhìn vào đây bạn không thể biết nó trả về một giá trị hay một tính toán

Trong Scala, không có sự khác biệt như vậy

obj.field
obj.method // miễn là phương thức không có tham số

Điều này cho phép thay đổi các trường của class đơn giản thành các phương thức mà không phải mất nhiều thời gian trong quá trinh refactor code.

Bạn có thể nhận ra các điều này trong Scala, Source code của bạn không cần quan tâm rằng nếu nó đang xử lý một Option hay một collection, bởi vì tất cả chúng đều được diễn giải như nhau trong hầu hết tất cả các trường hợp chỉ bằng cách sử dụng các Higher Order Function như là .map.foreach.flatMap.filter.isEmpty

Nguyên tắc này cũng mang đến một số vấn đề bất ngờ: Nếu bạn truy cập đến một giá trị mà đòi hỏi tính toán tốn kém (Ví dụ một câu lệnh truy cập đến cơ sở dữ liệu chậm chạp), bạn sẽ không biết đánh giá như thế nào, hệ thống có thể bị chậm

Đôi khi bạn không cần quan tâm đến những thực thi bên dưới, nhưng đôi khi bạn cần phải hiểu được nó

 

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.