Haskell #18.1 Module

 

Nạp module

Trong Haskell, module là tập hợp các hàm, kiểu, và lớp chứa kiểu có liên quan. Chương trình Haskell là một tập hợp các module trong đó module chính nạp những module khác rồi dùng các hàm định nghĩa trong đó để thực hiện nhiệm vụ.

Việc chia mã lệnh vào trong nhiều module khác nhau có khá nhiều ưu điểm. Nếu một module đủ tính tổng quát, thì các hàm mà nó xuất ra sẽ có thể dùng được trong một loạt những chương trình khác nhau. Nếu mã lệnh của bạn được chia thành những nhiều module gói gọn, theo nghĩa không quá phụ thuộc vào nhau (ta cũng nói rằng chúng được ghép lỏng lẻo), thì sau này bạn có thể dùng lại chúng. Điều này giúp cho viết mã lệnh sẽ dễ quản lý hơn, khi mã lệnh được chia thành nhiều phần, mỗi phần có mục đích riêng nào đó.

modules

Thư viện chuẩn Haskell được chia thành các module, mỗi module chứa các hàm và kiểu có đôi chút liên quan với nhau và phục vụ một mục đích chung nào đó. Có một module để xử lý danh sách, một module để lập trình tương tranh (concurrent), một module giúp xử lý số phức, v.v. Tất cả mọi hàm, kiểu và lớp mà ta đã làm việc đến giờ đều thuộc về module Prelude, vốn mặc nhiên được nhập vào. Ở chương này, ta sắp xem xết một số module hữu dụng và các hàm trong module đó. Song trước hết, ta phải xem cách nhập module đã.

Cú pháp dùng để nhập module trong file mã lệnh Haskell là import <module name>. Điều này phải được thực hiện trước bất kì lời định nghĩa hàm nào, vì vậy các lệnh nhập thường được thực hiên ở đầu file. Dĩ nhiên là một file lệnh có thể nhập nhiều module. Chỉ cần đặt mỗi lệnh nhập trên một dòng riêng. Ta hãy nhập module Data.List, vốn có một loạt các hàm hữu ích giúp làm việc với danh sách và dùng một hàm mà nó xuất ra để tạo một hàm nói cho ta biết trong danh sách có bao nhiêu phần tử không trùng lặp nhau.

import Data.List

numUniques :: (Eq a) => [a] -> Int
numUniques = length . nub

Khi bạn viết import Data.List, tất cả các hàm được Data.List xuất ra đều trở nên dùng được trong không gian tên tổng thể, nghĩa là bạn có thể gọi nó từ bất kì chỗ nào trong file lệnh. nub là một hàm được định nghĩa trong Data.List; hàm này nhận vào một danh sách và gạt bỏ tất cả những phần tử lặp thừa. Việc hợp length với nub bằng cách viết length . nub sẽ tạo ra một hàm tương đương với \xs -> length (nub xs).

Bạn cũng có thể đặt các hàm của module bên trong không gian tên tổng thể khi dùng GHCI. Nếu bạn ở trong GHCI và muốn gọi được các hàm được xuất bởi Data.List, hãy gõ vào:

ghci> :m + Data.List

Nếu ta muốn nạp những tên từ nhiều module bên trong GHCI, ta không cần phải viết :m + nhiều lần, mà chỉ cần nạp nhiều module cùng lúc.

ghci> :m + Data.List Data.Map Data.Set

Tuy vậy, nếu bạn đã nạp một file lệnh mà nó đã nạp trước một module, bạn sẽ không cần phải dùng :m + để truy cập tới module đó.

Nếu chỉ cần một số hàm từ một module, bạn có thể thực hiện nhập có chọn lựa. Nếu ta muốn nhập mỗi hai hàm nub và sort từ Data.List, ta sẽ viết:

import Data.List (nub, sort)

Bạn cũng có thể chọn cách nhập tất cả các hàm trong module trừ một số hàm nhất định. Điều này phát huy tác dụng khi một số module xuất các hàm có tên trùng nhau mà bạn muốn tránh những tên xung đột như vậy. Chẳng hạn ta đã có riêng hàm mang tên nub và muốn nhập vào tất cả các hàm từ Data.List ngoại trừ hàm nub:

import Data.List hiding (nub)

Một cách khác để xử lý xung đột về tên là dùng lệnh nhập chọn lọc. Module Data.Map, trong đó có cấu trúc dữ liệu để tra các giá trị tương ứng từ các khóa, xuất một loạt các hàm có tên giống như hàm trong Prelude, chẳng hạn filter hoặc null. Vì vậy khi ta nhập Data.Map rồi gọi filter, Haskell sẽ không biết rằng phải dùng hàm nào. Sau đây là cách giải quyết:

import qualified Data.Map

Như vậy sẽ khiến cho khi ta muốn chỉ đến hàm filter trong Data.Map, ta sẽ phải viết Data.Map.filter, còn viết mỗi filter nghĩa là ta vẫn chỉ đến hàm filter thông thường mà ta vẫn biết và yêu quý. Nhưng việc phải gõ Data.Map ở trước từng hàm một trong module đó thì thật là tẻ nhạt. Đó là lý do tại sao ta có thể đổi tên hàm nhập chọn lọc thành một thứ ngắn gọn hơn:

import qualified Data.Map as M

Bây giờ để chỉ hàm filter của Data.Map, ta chỉ cần viết M.filter.

Hãy dùng sổ tra cứu tiện lợi này để xem có những module nào ở trong thư viện chuẩn. Một cách hay để thu nhặt kiến thức Haskell là chỉ viết click chuột khắp các mục trong sổ tra cứu thư viện chuẩn và khám phá các module cùng hàm của chúng. Bạn cũng có thể xem mã nguồn Haskell của mỗi module. Việc đọc mã nguồn của một số module thực sự là cách làm hay để học Haskell và nắm được ý nghĩa của nó.

Để tìm kiếm hoặc tra vị trí của các hàm, hãy dùng Hoogle. Đó thực sự là máy tìm kiếm rất tốt dành cho Haskell; bạn có thể tìm theo tên hàm, tên module, hoặc thậm chí cả dấu ấn kiểu.