Giới Thiệu Ngôn Ngữ Haskell

Tram Ho

Mô hình lập trình hàm Functional Programming và các ngôn ngữ hỗ trợ xuất hiện từ rất sớm ngay khi khái niệm Declarative Programming xuất hiện trong lĩnh vực lập trình phần mềm nói chung. Tuy nhiên do mang nặng các đặc trưng vay mượn từ Toán Học nên có phần kém thân thiện hơn so với Procedural Programming về khả năng tiếp cận những lập trình viên tiềm năng không đến từ các môi trường mang tính chất học thuật. Chính vì vậy nên các ngôn ngữ lập trình đặt nền móng hoàn toàn trên Functional Programming thường ít phổ biến và hầu như không được biết đến bởi số đông lập trình viên ở thời gian trước 2010.

Mặc dù vậy, điều đó không đồng nghĩa với việc Functional Programming khó hơn so với Procedural Programming, hay yêu cầu người sử dụng cần phải có nền tảng kiến thức Toán Học dày dặn. Và thậm chí với một tài liệu hướng dẫn tự học thân thiện, có phần xem nhẹ các khái niệm vay mượn từ Toán Học thì bất kỳ ai cũng có thể bắt đầu học, sử dụng, và thấy được hiệu quả mà lối tư duy của Functional Programming đem lại.

Chính vì vậy nên vào khoảng những năm sau 2010 thì một loạt các ngôn ngữ lập trình phổ biến như JavaScript (ECMA 2015), Java 8 (Java 2014), v.v… đều đem vào phiên bản cập nhật mới những tính năng Functional để hỗ trợ lập trình viên viết code ngắn gọn, đẹp đẽ, và trực quan hơn. Còn ở thời điểm hiện tại thì có lẽ không ai là không biết tới Funtional Programming và một vài kỹ thuật viết code liên quan như biểu thức lambda, tham số curry, …

Haskell Language

Trong nhóm các ngôn ngữ thuần Functional có hai ngôn ngữ được xem là đại biểu cho hai trường phái sử dụng cú pháp khác nhau để thể hiện tư duy lập trình Functional, đó là HaskellLISP. Trong đó thì Haskell được cho là có cú pháp thân thiện hơn đối với lập trình viên đã quen thuộc với các ngôn ngữ lập trình phổ biến. Lý do thì là bởi vì cú pháp của LISP được thiết kế gần với logic đọc của máy tính hơn và đây là một ví dụ về hàm tính giá trị của phần tử thứ n trong dãy số Fibonaci đang được đặt trên trang chủ của LISP:

Chúng ta sẽ sớm tự viết lại hàm fib này bằng cú pháp của Haskell, tuy nhiên trước hết sẽ là một trích đoạn ngắn về lịch sử của ngôn ngữ này. Haskell được giới thiệu lần đầu vào năm 1990, sớm hơn vài năm so với hai anh em nhà JavaJavaScript (1995). Ngôn ngữ thuần Functional này được thiết kế bởi một nhóm kỹ sư lập trình khoảng gần hai chục người và được đặt tên theo nhà khoa học, toán học người Mỹ Haskell Brooks Curry. Sir này rất nổi tiếng trong môi trường học thuật ở bển và ngoài Haskell thì từ BrooksCurry cũng được sử dụng để đặt tên cho các ngôn ngữ lập trình khác nữa, hầu hết đều được ứng dụng ở mảng đào tạo học thuật và ứng dụng công nghiệp.

Và như vậy là cho tới khoảng những năm sau 2010 thì cộng đồng lập trình viên trên thế giới nói chung đã không thể bỏ qua Funtional Programming khiến cho Haskell đã trở thành ngôn ngữ có ảnh hưởng lớn. Cụ thể là các module đặc biệt như LINQ/C#Generics/Java đều được thiết kế với cú pháp và tư duy tham chiếu từ Haskell. Một số ngôn ngữ khác nhận được sự ảnh hưởng từ Haskell có thể kể đến là Scala, Swift, Visual Basic 9.0, C++ 11, F#, Rust, Elm, PureScript, v.v…

Trong số đó thì ElmPureScript là những cái tên điển hình cũng được thiết kế thuần Functional với cú pháp sử dụng giống với Haskell khoảng ~ 90% . Phần khác biệt còn lại là bởi vì những tính năng mà Haskell có hỗ trợ còn ElmPureScript thì chưa hỗ trợ ở thời điểm hiện tại.

Hello, Haskell !

Những điều tuyệt vời về Haskell và môi trường phát triển của ngôn ngữ này thì rất nhiều và chúng ta sẽ không thể liệt kê trước toàn bộ trong một bài viết. Vì vậy nên tốt nhất là chúng ta hãy khởi đầu ngay với code Hello World và tên tệp là Main.hs – có thể được tạo ra bằng MS Notepad hoặc bất kỳ trình soạn thảo code nào.

Bạn có thể chạy thử code ví dụ khởi đầu trong môi trường thực thi code online tại đây: REPLit.com. Chúng ta sẽ nói tới việc cài đặt môi trường phát triển đặc trưng của Haskell trong phần tiếp theo của bài viết.

Chúng ta đang có một chương trình Hello, World khởi đầu ở hàm main được định nghĩa bởi một dòng khai báo định kiểu dữ liệu không có tham số đầu vào và trả về một giá trị thuộc kiểu IO (), và một dòng mô tả logic hoạt động chi tiết là main sẽ ủy thác công việc cho hàm putStrLn đã được định nghĩa sẵn trong thư viện tiêu chuẩn của Haskell. So với Elm thì thao tác khai báo định kiểu trong Haskell chỉ khác duy nhất ở vị trí ký hiệu :: được sử dụng để tách biệt tên hàm và vế thông tin định kiểu các tham số và giá trị trả về. Ở Elm chúng ta chỉ sử dụng 01 dấu hai chấm :.

Hàm putStrLn yêu cầu tham số đầu vào là một chuỗi String lưu trong biến message và việc xác định chuỗi được in ra tiếp tục được ủy thác cho hàm hello tự định nghĩa với tham số đầu vào là name và kiểu chuỗi String và kết quả trả về là nội dung câu chào hỏi đầy đủ cũng thuộc kiểu chuỗi String.

Như vậy kết quả cuối cùng mà chúng ta thu được sau khi chạy hàm main là một giá trị thuộc kiểu IO () đã được Haskell định nghĩa sẵn. Còn về logic sẽ chuyển giá trị này thành một logic tạo ra sự thay đổi trạng thái của cửa sổ Console như thế nào thì đã được triển khai trong trình biên dịch compiler rồi và chúng ta không cần phải quan tâm tới.

Như vậy cú pháp cơ bản của Haskell không có gì xa lạ nếu bạn đã đồng hành cùng mình trong Sub-Series Elm. Hàm main được hỗ trợ bởi các hàm khác ở dạng biểu thức được tính toán và trả về một kết quả nào đó, thay vì mô tả các bước cần thực hiện. Một chương trình được viết bằng Haskell sẽ chỉ toàn các lời gọi hàm trong các biểu thức như thế này, và vì vậy nên thao tác gọi hàm được thiết kế trông khá tự nhiên – không có các dấu ngoặc đơn như C hay JavaScript.

Thay vì việc viết các câu lệnh thuần tự Imperative mô tả thao tác tính toán nội dung chuỗi cần in trước, rồi sau đó chạy thủ tục printf để thực hiện in nội dung ra cửa sổ Console; Thì ở Haskell chúng ta lại viết code ở dạng khai báo Declarative để định nghĩa main ngắn gọn là putStrLn message, rồi sau đó mới tiếp tục cung cấp định nghĩa chi tiết về mesage ở dòng tiếp theo với từ khóa gắn kèm wheremessage = hello "Haskell".

Đây là thứ mà Elm không có, và ở Elm chỉ hỗ trợ cấu trúc let .. in cũng được vay mượn từ Haskell nhưng sẽ khiến cho việc đọc code khó theo dõi hơn theo tư duy Declarative, bởi chúng ta sẽ phải đọc định nghĩa chi tiết về message trước rồi mới đọc được vế còn lại của hàm main. Nếu chúng ta có nhiều yếu tố gắn kèm tương tự message thì khi muốn đọc lướt qua và tách lấy thông tin định nghĩa ngắn gọn nhất là main = text message để kiểm tra khai báo định kiểu dữ liệu của hàm main thì chúng ta sẽ phải xác định dòng cuối cùng trong khối let .. in để tách lấy thông tin mô tả vế phải của biểu thức chính.

Hiển nhiên, chúng ta cũng có thể xếp chồng các lời gọi hàm nhờ đẩy mức ưu tiên của các lời gọi hàm phía sau bằng các cặp ngoặc đơn () giống như trong các ngôn ngữ lập trình phổ biến khác thay vì sử dụng cấu trúc binding với where hay let .. in.

Lúc này lời gọi hàm hello được viết sau putStrLn nhưng sẽ được thực thi trước do ưu tiên của cặp ngoặc đơn () bao quanh cả tham số truyền vào. Và chúng ta có thể thấy được đặc trưng đầu tiên của Functional Programming đó là các chương trình phụ sub-program sẽ luôn luôn trả về một giá trị nào đó chứ không trực tiếp tạo ra hiệu ứng biên side-effect tới các yếu tố môi trường bên ngoài.

Ngay cả hàm putStrLn được thiết kế sẵn cũng sẽ trả về một giá trị thuộc kiểu IO () và có thể được truyền vào các lời gọi hàm khác để thực hiện tính toán nếu cần thiết. Logic tạo ra hiệu ứng biên side-effect sẽ được triển khai tự động bởi trình biên dịch tương tự với Elm sẽ trả về một cấu trúc dữ liệu mô tả một trang đơn SPA, còn về việc các tệp HTMLJS được tạo ra như thế nào thì sẽ được tối ưu ở cấp độ của trình biên dịch và người viết code sẽ không cần quan tâm tới việc thiết kế procedure thực hiện công việc đó.

Chính vì vậy nên việc sử dụng một ngôn ngữ thuần Functional Programming trong môi trường được thiết kế đặc trưng sẽ có trải nghiệm khác biệt rất nhiều so với việc vay mượn các công cụ Functional và sử dụng trong các môi trường Imperative không được tối ưu cho Functional Programming.

Điều này cũng tương tự với việc viết code Procedural trong Ada sẽ thuận lợi hơn rất nhiều so với việc vay mượn các công cụ như contract rồi đem sang các ngôn ngữ phổ biến không được tối ưu hoàn toàn cho Procedural Programming. Các công cụ được vay mượn sang các môi trường không được thiết kế tối ưu đặc trưng cho một mô hình lập trình nào đó sẽ luôn có giới hạn về mặt chức năng và tính dễ đọc của cú pháp sử dụng.

Haskell Setup

Để bắt đầu cài đặt bộ công cụ tạo môi trường phát triển Haskell trên bất kỳ hệ điều hành nào thì chúng ta chỉ cần mở trang web chính thức của Haskell tại địa chỉ: Haskell.org -> GHC-up. Lệnh tải về và kích hoạt trình cài đặt GHC-up cho hệ điều hành mà bạn đang sử dụng sẽ xuất hiện ngay trong bảng To install on ... của trang web. Tất cả những gì mà chúng ta cần làm là copy lệnh cài đặt này và nhập vào cửa sổ dòng lệnh của hệ điều hành đang sử dụng.

Với Windows sẽ là Powershell:

Còn với Linux thì sẽ là Terminal:

Sau đó thì tiến trình cài đặt sẽ có đưa ra một số menu hỏi về các công cụ muốn cài đặt thêm thì chúng ta cứ cài đầy đủ tất cả bao gồm cả trình quản lý project StackHLS - Haskell Language Server. Ví dụ như bạn muốn cài đặt thêm plug-in cho VS Code để hỗ trợ nhận dạng cú pháp và soát lỗi trong các tệp code của Haskell thì cái HLS chắc chắn là sẽ cần thiết. Đối với Windows thì trình cài đặt sẽ gợi ý cài thêm chuỗi công cụ MSYS2 để sử dụng các câu lệnh bash như Linux và việc cài đặt thêm cũng không có gì rườm rà. Bạn hãy cứ cài đặt đầy đủ bởi nhỡ có lúc nào đó sẽ cần sử dụng tới.

Sau khi tiến trình cài đặt hoàn tất thì chúng ta có thể gõ lệnh để kiểm tra phiên bản của trình biên dịch GHC để xác nhận là đã cài đặt thành công.


Và sau đó tiến hành việc biên dịch và chạy code của tệp main.hs đã tạo ra ở ví dụ của phần trước.
Chia sẻ bài viết ngay

Nguồn bài viết : Viblo