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
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 sâu rộng và chắc chắ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à Haskell
và LISP
. 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
:
(defun fib (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
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à Java
và JavaScript
(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ừ Brooks
và Curry
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#
và 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/Apple
, Visuak Basic 9.0
, C++11
, F#
, Rust
, Elm
, PureScript
, v.v...
Trong số đó thì Elm
và PureScript
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 60 - 90%
. Phần còn lại 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 Elm
và PureScript
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.
module Main where main :: IO ()
main = putStrLn message where message = hello "Haskell" hello :: String -> String
hello name = "Hello, " ++ name ++ " !"
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 này, sau khi đã nắm được cú pháp căn bản của ngôn ngữ này.
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
.
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 khai báo định kiểu tham số đầu vào là name
kiểu chuỗi String
và trả về kết quả 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ề 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
là 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á thân thiện - không có các dấu ngoặc đơn như C
hay JavaScript
.
#include <stdio.h> void main () { char* message = hello ("C"); printf ("%s", message);
}
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 where
là message = 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
. Bởi 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ả ngắn gọn vế phải của main
.
module Main exposing (main)
import Html exposing (Html, text) main : Html message
main = let message = hello "Elm" in text message
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
.
module Main where main :: IO ()
main = putStrLn (hello "Haskell") hello :: String -> String
hello name = "Hello, " ++ name ++ " !"
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 HTML
và JS
đượ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 Procedual 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
không cần quyền Admin
:
Set-ExecutionPolicy Bypass -Scope Process -Force;[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; try { Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Invoke-WebRequest https://www.haskell.org/ghcup/sh/bootstrap-haskell.ps1 -UseBasicParsing))) -ArgumentList $true } catch { Write-Error $_ }
Còn với Linux
thì sẽ là Terminal
:
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
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 Stack
và HLS - 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.
ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.2 cabal --version
cabal-install version 3.6
compiled using version 3.6 of the Cabal library stack --version
Version 2.9.1 ... hpack-0.35.0
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.
> cd Desktop
> ghc main.hs
> main
Hello, Haskell !
(chưa đăng tải) [Functional Programming + Haskell] Bài 2 - ...