Haskell #18.6 Module of your

 

Tự lập ra các module của mình

making modules

Đến giờ ta đã xem xét một vài module khá hay, nhưng làm thế nào để tự tạo lập được module của riêng mình? Hầu hết ngôn ngữ lập trình đều cho phép bạn phân chia mã lệnh thành nhiều file và Haskell cũng không phải ngoại lệ. Khi viết chương trình, một phương pháp hay là nhóm các hàm và kiểu dùng cho một mục đích cụ thể vào cùng một module. Bằng cách này, bạn có thể dễ dàng sử dụng lại các hàm đó trong những chương trình khác, chỉ việc nhập module bạn đã viết.

Ta hãy xem làm thế nào để tự lập ra các module, qua việc chế tạo một module nhỏ chứa các hàm để tính thể tích và diện tích của một số khối hình học. Ta sẽ bắt đầu bằng việc tạo ra một file có tên là Geometry.hs.

Ta nói rằng một module xuất khẩu các hàm. Điều này có nghĩa là khi tôi nhập một module, tôi có thể dùng các hàm mà module đó xuất khẩu. Dù module này có thể định nghĩa các hàm mà bên trong nó các hàm khác của nó gọi đến, nhưng ta chỉ có thể thấy được những hàm nào mà module xuất khẩu.

Ở đầu mỗi module, chúng ta chỉ định tên của module. Nếu ta có file tên là Geometry.hs, thì ta nên đặt tên module là Geometry. Tiếp theo, ta chỉ định tên các hàm mà module này xuất khẩu, và sau đó ta có thể bắt tay vào viết các hàm. Vì vậy ta sẽ bắt đầu như sau:

module Geometry
( sphereVolume
, sphereArea
, cubeVolume
, cubeArea
, cuboidArea
, cuboidVolume
) where

Bạn thấy đấy, chúng ta sẽ tính diện tích và thể tích các khối cầu, khối lập phương và khối hộp chữ nhật. Hãy tiếp tục đi định nghĩa các hàm:

module Geometry
( sphereVolume
, sphereArea
, cubeVolume
, cubeArea
, cuboidArea
, cuboidVolume
) where

sphereVolume :: Float -> Float
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)

sphereArea :: Float -> Float
sphereArea radius = 4 * pi * (radius ^ 2)

cubeVolume :: Float -> Float
cubeVolume side = cuboidVolume side side side

cubeArea :: Float -> Float
cubeArea side = cuboidArea side side side

cuboidVolume :: Float -> Float -> Float -> Float
cuboidVolume a b c = rectangleArea a b * c

cuboidArea :: Float -> Float -> Float -> Float
cuboidArea a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2

rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b

Ở đây toàn những kiến thức hình học cơ bản. Dù vậy, có một điều cần lưu ý. Vì một khối lập phương chỉ là một trường hợp riêng của khối hộp chữ nhật, nên ta định nghĩa diện tích và thể tích khối lập phương như là khối hộp mà các cạnh đều dài bằng nhau. Ta cũng định nghĩa một hàm phụ trợ tên là rectangleArea, để tính diện tích khối hộp chữ nhật dựa trên chiều dài các cạnh bên của nó. Hàm này khá “tủn mủn” vì ta chỉ dùng nó trong module này thôi (cụ thể là dùng trong các hàm cuboidArea và cuboidVolume) mà không xuất khẩu nó! Vì ta muốn module được viết chỉ trình bày các hàm để tính cho hình khối ba chiều, nên tuy dùng đến rectangleArea nhưng ta không xuất khẩu nó.

Khi tạo module, ta thường chỉ xuất các hàm nào đóng vai trò như một giao diện đến module, còn những chi tiết thực hiện cần phải giấu đi. Khi người khác dùng module Geometry, bản thân họ không quan tâm đến các hàm mà ta không xuất khẩu. Ta có thể quyết định thay đổi toàn bộ những hàm này, hoặc xóa bỏ chúng trong phiên bản mới (ta có thể xóa bỏ hàm rectangleArea và chỉ dùng phép *) mà không ai để ý đến vì từ đầu ta đã không xuất khẩu hàm đó.

Để sử dụng module vừa tạo lập, ta chỉ cần viết:

import Geometry

Để thực hiện điều này, Geometry.hs phải nằm trong cùng thư mục với chương trình nhập nó.

Ta cũng có thể bố trí cấu trúc thừa kế đối với module. Mỗi module có thể chứa một số các module nhỏ và mỗi module nhỏ có thể chứa module nhỏ của riêng mình. Ta hãy tách nhỏ ra, cụ thể là module Geometry có ba module con, mỗi module cho một loại hình khối.

Trước hết, ta sẽ tạo một thư mục có tên Geometry. Chú ý chữ G viết in. Trong đó, ta đặt ba file: Sphere.hsCuboid.hs, vaf Cube.hs. Sau đây là nội dung từng file:

Sphere.hs

module Geometry.Sphere
( volume
, area
) where

volume :: Float -> Float
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)

area :: Float -> Float
area radius = 4 * pi * (radius ^ 2)

Cuboid.hs

module Geometry.Cuboid
( volume
, area
) where

volume :: Float -> Float -> Float -> Float
volume a b c = rectangleArea a b * c

area :: Float -> Float -> Float -> Float
area a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2

rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b

Cube.hs

module Geometry.Cube
( volume
, area
) where

import qualified Geometry.Cuboid as Cuboid

volume :: Float -> Float
volume side = Cuboid.volume side side side

area :: Float -> Float
area side = Cuboid.area side side side

Được rồi! Đầu tiên là Geometry.Sphere. Lưu ý cách ta đặt nó vào trong một thư mục có tên Geometry và rồi định nghĩa module có tên là Geometry.Sphere. Ta làm việc tương tự đối với khối hộp chữ nhật. Cũng lưu ý cách mà trong 3 module con, ta đã định các hàm có cùng tên. Ta có thể làm điều này được vì chúng là các module riêng rẽ. Ta muốn dùng các hàm từ Geometry.Cuboid trong Geometry.Cube nhưng không thể trực tiếp viết import Geometry.Cuboid vì module này xuất khẩu các hàm có cùng tên với Geometry.Cube. Đó là lý do tại sao ta thực hiện nhập có chọn lọc và mọi việc đều ổn.

Vì vậy bây giờ nếu ta trong một file có cùng cấp với thư mục Geometry thì ta có thể viết, chẳng hạn:

import Geometry.Sphere

Rồi ta có thể gọi area và volume và chúng sẽ trả lại diện tích và thể tích một khối cầu. Và nếu ta muốn tráo đổi nhiều module thế này, ta phải nhập chọn lọc vì các module đó xuất khẩu các hàm có cùng tên. Vì vậy ta chỉ cần viết kiểu như sau:

import qualified Geometry.Sphere as Sphere
import qualified Geometry.Cuboid as Cuboid
import qualified Geometry.Cube as Cube

Và rồi ta có thể gọi Sphere.areaSphere.volumeCuboid.area, v.v. Mỗi hàm sẽ tính diện tích hoặc thể tích của hình khối tương ứng.

Lần tới khi bạn phải viết một file rất lớn và có rất nhiều hàm, hãy cố thử xem những hàm nào phục vụ cùng một mục đích chung và xem liệu bạn có thể đặt chúng vào một module riêng hay không. Bạn sẽ có thể chỉ nhập module riêng đó lần tới khi bạn viết một chương trình đòi hỏi tính năng tương tự nào đó.