Thời trang lập trình – Sự trỗi dậy của Declarative Programming!

Kể từ sau loạt bài về Apple Watch + CI, tôi muốn chuyển sang các chủ đề khác nói về các món ăn chơi nhảy múa mà không phải là về code, công việc. Nhưng nói thật là các món ăn chơi thì nhiều, cũng lắm sự kỳ công đòi hỏi người chơi phải có niềm yêu thích thực sự, thời gian tìm hiểu nhất định. Thêm nữa là khi viết những bài như vậy cần phải có kiến thức rộng, bao quát cũng như khá khó để tạo được cảm hứng cho người đọc. Thế nên thôi, lại quay về với cái máng lợn là: CODE…

Chắc hẳn trong công việc, mọi người đều có những mục tiêu riêng, đích đến nhất định qua các quãng thời gian dài ngắn khác nhau. Là 1 một lập trình viên iOS đơn thuần, mục tiêu trong năm nay của tôi là học 1 ngôn ngữ lập trình mới, vốn đang rất hot trong cộng đồng cũng như nội bộ công ty: Swift của Apple. Nhưng lần mò, vâng vẫn là cái trò lần mò, tôi được biết tới Functional Programming. Nhưng nghe lạ tai quá, tìm hiểu mãi thì mới đi đến ngọn nguồn của vấn đề: Declarative Programming.


Mới đầu đọc thì tôi chỉ biết đến nó như là 1 lĩnh vực nhỏ trong thế giới Computer Science bao la và cao siêu khó lường. Nhưng các kết quả tìm kiếm thường có cụm từ: Declarative Programming vs Imperative ProgrammingImperative Programming (IP) thường gắn liền với lập trình hướng đối tượng OOP mà lâu nay tôi và các bạn(xin lỗi nếu có vơ đũa cả nắm) vẫn nghĩ là tối ưu, là phương pháp hay nhất khi phát triển phần mềm. Imperative Programming là cách mà bao lâu nay tôi vẫn lập trình, từ thời học cấp 3, Đại học và cả khi đi làm. Và nó có 1 thế giới đối nghịch Declarative Programming (DP). Vậy là ngoài kia, ngoài cái thế giới mà tôi đang sống có 1 thế giới khác mà bao lâu nay tôi không hề hay biết. Trên đường đi khám phá thế giới mới tôi gặp rất nhiều lời ca ngợi về nó, càng thôi thúc sự tò mò, khát khao khám phá của bản thân.

1.Định nghĩa

Hai mô hình trên vốn khá rộng, nhưng để định nghĩa thì có thể gói gọn 1 cách tương phản rõ ràng như sau:

  • Imperative programming: telling the “machine” how to do something, and as a result what you want to happen will happen.
  • Declarative programming: telling the “machine” what you would like to happen, and let the computer figure out how to do it.

 

 Tôi chọn để nguyên văn tiếng Anh vì nó vốn quá súc tích, ngắn gọn và rất dễ để nhận ra sự khác bọt ở đây. Tuy nhiên hẳn các bạn cũng sẽ thấy mơ hồ với từ khóa “what”, “how” được dùng ở đây. Vậy hãy để tôi lấy ví dụ cho dễ hiểu.

Ví dụ 1: Nhân đôi các phần tử có trong mảng cho sẵn.

  • Imperative: Xời, đơn giản! Đây là cách bạn sẽ nghĩ ra ngay trong đầu:

 

Cách thức giải quyết bài toán:

  1. Khai báo 1 mảng doubled mới để lưu dữ liệu
  2. Duyệt qua các phần tử của mảng.
  3. Thực hiện phép nhân đôi các phần tử trong mảng
  4. Lần lượt lưu kết quả vào mảng doubled

 

  • Declarative:

 Ở đây sẽ khác hơn 1 chút, chúng ta không thấy việc duyệt mảng mà chỉ thấy câu lệnh map cùng phép tính nhân với *2.

Lệnh map ở đây thực hiện việc tạo 1 mảng doubled mới từ mảng đã cho với các phần tử đã được nhân *2 qua  function (n) {return n*2)} (ở đây function (n) được khai báo trực tiếp thay vì được tách ra ngoài).

 

“Dùng hàm có sẵn, thư viện bên ngoài thì nói làm gì. Hư cấu!”. Nhưng xin các bạn hãy nhìn lại. Cùng 1 bài toán sẽ có nhiều cách giải, về nguyên lý cơ bản là giống nhau nhưng cú pháp khác nhau, điều đó giải thích tại sao lại có cách giải này hay hơn cách giải bt khác. Xin nhấn mạnh sự khác biệt ở đây là khả năng đọc hiểu code (readability).  Dùng mô hình Imperative chúng ta sẽ cần chỉ ra các bước tuần tự cần thực hiện trong khi Declarative sẽ cho thấy ngay cái ta cần xảy ra mà không bận tâm phải duyệt mảng, lưu kết quả vào mảng như thế nào. Bài toán yếu cần nhân đôi cá giá trị và code của bạn tập trung vào việc nhân đôi *2.

function(n) ở trên được gọi là pure function: nó không hề thay đổi bất kì giá trị nào (side effects) mà chỉ nhận vào input, trả ra output sau khi thực hiện tính toán.

––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

Ví dụ 2: Tính tổng tất các phần tử trong mảng.

  • Imperative: tạo 1 biến mới, duyệt qua các phần tử và thực hiện cộng dồn vào biến đã tạo (How to do)

 

  • Declarative:

 Hàm reduce thực hiện gộp các phần tử trong mảng thành 1 giá trị duy nhất theo function(sum, n) truyền vào. Hãy nhìn vào code, cái bạn sẽ thấy ngay được map và reduce giúp chúng ta xử lý với mảng, tất cả chúng ta cần làm ở đây là chỉ ra chúng ta muốn gì (what we you want to do).

Tôi đoán rằng đến đây các bạn và cả tôi ngày xưa cũng vẫn thấy lạ lẫm, không thể hiểu rõ sự khác nhau giữa 2 mô hình trên. Hẳn bạn sẽ tự bảo việc gì phải làm theo những cách kỳ quái, khó hiểu một cách trừu tượng thế này? Làm theo cách bao lâu nay bạn vẫn làm, cách bạn được học trong nhà trường, cách mà hàng triệu lập trình viên trên thế giới vẫn làm: bảo cho máy tính các bước cần thực hiện để giải quyết vấn đề. Như thế không tốt hơn sao?

Hãy hình dùng các mô hình trên như các thể loại nhạc, người thích thể loại nhạc này, người thì lại không và cho nó là tẻ nhạt, thậm chí dị hợm. Hồi xưa tôi nghĩ là mình không thích MetalBlack Metal (mấy thể loại nhạc nhức đầu, chẳng hát mà toàn gào thét…) nhưng bây giờ thì sao, tôi có thể trả lời ngay là tôi chỉ thích nó…

Hồi bé các bạn có ghét khi bố mẹ mở nhạc vàng trong khi muốn xem Đan Trường, Lam Trường… Nhưng sau này lớn lên, trưởng thành hơn thì ít nhiều bạn lại thích nghe nhạc vàng? Đã bao giờ bạn luôn miệng nói rằng mình ghét Sơn Tùng, chả quan tâm MTP là đứa nào. Nhưng khi tình cờ nghe “Con cua ngang qua, Nắng ấm xa dần, Nem của ngày hôm qua”… bạn thấy cũng lạ lạ, hay hay nếu không biết nó là của Sơn Tùng MTP? Hãy để tôi chỉ cho bạn 1 ngôn ngữ mà ai cũng từng học, từng dùng mà không hề biết nó là Declarative Programming: SQL (Structured Query Language)!!!

Hãy xem câu query sau:

 Hình dung cách bạn sẽ viết code để giải bài toán này theo cách Imperative?

 Nhìn xem làm theo cách vốn có của SQL sẽ đơn giản, ngắn gọn và dễ hiểu hơn biết nhường nào. Bạn chỉ cần quan tâm: lấy gì, ở đâu, điều kiện là gì? Việc còn lại là để DBMS làm công việc còn lại cho bạn. Giống như cách bạn nhờ ai đó lấy cho lon pepsi trong tủ lạnh:

  • Imperative: đứng dậy, đi đến tủ lạnh, mở cửa ra, lấy 1 lon pepsi, đem đến đây cho tôi!
  • Declarative: Em yêu ơi, anh ước có 1 lon pepsi lạnh.

=> Thấy không, bạn đơn giản chỉ cần nói ra điều ước của mình, còn làm sao thì đó là việc của ông bụt, bà tiên. Điều kì diệu đó cũng được thực hiện bởi sự hỗ trợ của ngôn ngữ bạn dùng khi bạn code.

 Bây giờ trở lại với vấn đề lý thuyết sẽ dễ hiểu hơn và phần nào trả lời câu hỏi to tướng từ đầu đến giờ vẫn chả nhỏ đi được là bao!

2. Thiên thời

Tại sao tôi lại đặt tiêu đề là sự trỗi dậy của Declarative Programming? Nó không phải mới được đề xướng trong những năm gần đây, mà nó đã có từ rất lâu rồi, từ những năm 50 –  thời sơ khai của máy tính hiện đại, đầu tiên với ngôn ngữ lập trình Lisp. Bao lâu nay nó vẫn tồn tại song song với Imperative mà chúng ta không hề hay biết. Vậy tại sao sau 60 năm năm nó mới lại nổi lên?

Thời kì đầu, máy tính còn khá chậm chạp, cồng kềnh, tốc độ xử lý không nhanh như bây giờ. Thời đó có 2 tư tưởng chính khi phát triển các ngôn ngữ lập trình:

  • Rất quen thuộc, đó là dựa theo nguyên lý Von Neumann.
  • Đi từ nguyên lý cơ bản trong toán học.

Sự chậm chạp của máy tính thời đó không đủ điều kiện để Lisp phát triển và thế là Imperative lên ngôi, đặc biệt với sự ra đời của C. Đến thập niên 90, có thể coi lập trình hướng đối tượng (OOP) là đỉnh cao với sự kết hợp mô hình Imperative Programming. Chúng ta trừ tượng hóa các vấn đề  thành các đối tượng, đưa ra các phương thức mà đối tượng đó có rồi kết nối chúng lại để tạo ra một chuỗi các bước để đối tượng thực hiện giải quyết vấn đề (how). Hàng loạt các ngôn ngữ mới ra đời đi theo con đường multi-paradigm (OOP + Imperative): Java, C#, C++, Smalltalk, Python, Ruby, PHP…

Nhưng bây giờ, máy tính phát triển chóng mặt, tốc độ, kích thước cải thiện rất rất nhiều. Theo định luật Moore thì cứ sau 18 tháng, số lượng bóng bán dẫn(transistor) lại tăng lên 2 lần(vài năm trước thì có thể hiểu là nhanh hơn gấp đôi). Và cơ hội thứ hai lại đến với Declarative Programming.