Lập trình Haskell hiện đại: Kết hợp LSP và live reload để có trải nghiệm gần như Lisp

26 tháng 8, 2026·5 phút đọc

Bài viết chia sẻ trải nghiệm của một lập trình viên Haskell khi kết hợp language server (hls), Eglot trong Emacs và công cụ ghcid với thư viện foreign-store để tạo ra quy trình phát triển có khả năng tải lại mã nguồn trực tiếp (live reload), giúp thu hẹp khoảng cách với phong cách lập trình tương tác của Lisp. Tác giả cũng mô tả chi tiết quá trình cài đặt, cấu hình và những khó khăn gặp phải, cùng với đoạn mã minh họa cho cơ chế giữ trạng thái chương trình khi tải lại mã.

Lập trình Haskell hiện đại: Kết hợp LSP và live reload để có trải nghiệm gần như Lisp

Lập trình Haskell hiện đại: Kết hợp LSP và live reload để có trải nghiệm gần như Lisp

Một lập trình viên Haskell chia sẻ hành trình thử nghiệm kết hợp hls (Haskell Language Server), Eglot trong Emacs và công cụ ghcid cùng thư viện foreign-store để tạo ra quy trình phát triển có khả năng tải lại mã nguồn trực tiếp (live reload) mà không mất trạng thái chương trình. Từ đó, anh có thể cập nhật mã và thấy kết quả ngay lập tức — một trải nghiệm gần với phong cách lập trình tương tác của Lisp mà nhiều người mơ ước.

Tác giả cũng chia sẻ chi tiết về quá trình cài đặt, cấu hình và những khó khăn thực tế khi tích hợp các công cụ này, kèm theo một đoạn mã minh họa cho cơ chế giữ trạng thái khi tải lại chương trình. Dù còn nhiều trở ngại, nhưng kết quả mang lại là một quy trình làm việc thú vị và hiệu quả cho các dự án thử nghiệm.

Vì sao Lisp lại đáng ghen tị?

Lập trình viên Lisp thường gõ mã trực tiếp vào REPL để thêm, bớt hoặc thay thế các phần của hệ thống đang chạy — không cần biên dịch, không cần chạy lại, không cần debugger. Họ luôn ở bên trong tiến trình chương trình của mình và có thể kiểm tra bất cứ thứ gì. Điều này giúp việc phát triển diễn ra mượt mà và tương tác hơn hẳn cách làm truyền thống.

-- Ví dụ về hàm update dùng foreign-store để giữ trạng thái khi reload
update :: IO ()
update = do
  lookupStore (case asyncStore of Store i -> i) >>= \case
    Nothing -> do
      -- Khởi tạo mới
      initialiseSdl >>= void . storeAction (Store 0) . newMVar
      start >>= void . storeAction (Store 1) . newIORef
    Just tidStore -> do
      -- Hủy thread cũ và tạo thread mới, tái sử dụng tài nguyên
      withStore tidStore $ \ref -> do
        readIORef ref >>= cancel
        start >>= writeIORef ref

Điều này không khả thi với Haskell truyền thống, nơi bạn phải biên dịch lại toàn bộ và chạy lại chương trình từ đầu. Tuy nhiên, với ghcidforeign-store, ta có thể mô phỏng một phần trải nghiệm đó.

Giải pháp cho Haskell

Thay vì gửi từng định nghĩa đến REPL như lập trình viên Lisp, tác giả dùng kết hợp:

  • ghcid: theo dõi thay đổi file, tự động biên dịch lại và chạy hàm chính (main).
  • foreign-store: lưu trữ tài nguyên (như cửa sổ SDL, texture) trong bộ nhớ, giúp giữ lại khi chương trình được tải lại.

Kết quả là mỗi khi lưu file, chương trình tự động được tải lại với trạng thái được bảo toàn, và cửa sổ hiển thị đồ họa (như slope field cho phương trình vi phân) sẽ cập nhật ngay lập tức.

Cấu hình Emacs + hls: một hành trình lắm chông gai

Việc cài đặt Eglot và hls không hề đơn giản. Tác giả phải sử dụng Nix để quản lý dependency, direnv để tự động nạp môi trường, và cài plugin envrc cho Emacs. Dù vậy, vẫn gặp nhiều vấn đề:

  • Eglot đôi khi không tìm thấy hls server, phải chạy M-x eglot-reconnect thủ công.
  • Thời gian khởi động lại server khá lâu, ngay cả với dự án nhỏ.
  • Eglot thêm độ trễ vào thao tác chỉnh sửa — một trở ngại lớn với người quen dùng Vim.

“Tôi thích ý tưởng về trải nghiệm Eglot-và-hls. Các gợi ý và hành động mã thêm vào rất tiện lợi. Nhưng độ trễ khi chỉnh sửa khiến tôi khó thích nghi, và nó đủ tệ để tôi không bật Eglot trong các dự án khác.”

Nhận xét từ cộng đồng

“Với Python và VSCode, mọi thứ hoạt động ngay lập tức. Python là ngôn ngữ thông dịch nên không hoàn toàn công bằng, nhưng với quy trình làm việc đã được biết đến rộng rãi và vượt trội này, thật kỳ lạ khi công cụ cho Haskell vẫn chưa mượt mà hơn. Có phải tất cả lập trình viên Haskell đều là những người khó tính không?” — một độc giả bình luận.

Kết luận: một quy trình đầy hứa hẹn cho dự án thử nghiệm

Dù còn nhiều rào cản, quy trình này là một bước tiến đáng kể so với cách làm truyền thống. Với các dự án thử nghiệm hoặc giai đoạn đầu của một ý tưởng, việc viết mã, viết test, và thấy kết quả ngay lập tức khi lưu file là vô cùng giá trị. No giúp ta tập trung vào logic và khám phá nhanh hơn, thay vì mất thời gian cho việc biên dịch và chạy lại thủ công.

Đối với người Việt, đây cũng là một gợi ý thú vị khi làm việc với Haskell hoặc các ngôn ngữ hàm khác — hãy thử kết hợp các công cụ hiện đại để cải thiện hiệu suất làm việc, ngay cả khi công nghệ còn mới và chưa hoàn hảo.

Không có mô tảKhông có mô tả

Hình minh họa cho quá trình kết hợp các công cụ lập trình hiện đại.

Ví dụ slope field cho phương trình vi phânVí dụ slope field cho phương trình vi phân

Ảnh chụp màn hình chương trình hiển thị slope field và nghiệm cụ thể — một ứng dụng điển hình của quy trình live reload.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗