[Procedural Programming + Ada] Bài 4 – Packages & Sub-Programs

Trong tất cả các ví dụ trước đó thì chúng ta đã sử dụng một tệp main.adb duy nhất được chỉnh sửa code nhiều lần để tìm hiểu về các yếu tố đơn giản. Tuy nhiên, Ada rất xem trọng việc tách chương trình mà chúng ta viết thành nhiều tệp khác nhau và vì

Trong tất cả các ví dụ trước đó thì chúng ta đã sử dụng một tệp main.adb duy nhất được chỉnh sửa code nhiều lần để tìm hiểu về các yếu tố đơn giản. Tuy nhiên, Ada rất xem trọng việc tách chương trình mà chúng ta viết thành nhiều tệp khác nhau và vì vậy nên chỉ cho phép định nghĩa một sub-program duy nhất trong mỗi tệp.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;


function Sub is
   -- 
begin
   Put_Line ("Just a sub-program");
end Sub;


procedure Main is
   --
begin
   Put_Line ("The main program");
end Main;
gprbuild -q -P learn_ada.gpr

learn_ada.gpr:1:09: warning: there are no sources of language "c" in this project
main.adb:12:01: error: end of file expected, file can have only one compilation unit
gprbuild: *** compilation phase failed

Dòng warning đầu tiên là do mình đã khai báo thêm có sử dụng C kèm trong project, nếu bạn không muốn thấy thông báo này hiện lặp lại thì có thể bỏ C trong danh sách ngôn ngữ sử dụng trong tệp learn_ada.gpr.

Như vậy là trình biên dịch sẽ báo lỗi ngay ở vị trí mà chúng ta bắt đầu định nghĩa khối procedure thứ hai. Và để tiếp tục tiến xa hơn, chúng ta sẽ cần phải tìm hiểu cách tạo ra và sử dụng các package.

Package

Khái niệm package trong Ada có thể được hiểu với ý nghĩa tương đương với thư viện library trong C. Chúng ta sẽ cần tạo ra một tệp khai báo tổng quan bao gồm các hằng số constant và chữ ký của các sub-program mà thư viện đó cung cấp. Tương ứng với kiểu tệp khai báo header.h của C thì chúng ta sẽ có một tệp cấu hình specification.ads. Và tệp mã nguồn cũng chứa code logic chi tiết được Ada gọi là body.adb và yêu cầu đặt cùng tên với tệp cấu hình.

Đây là một ví dụ về code khai báo cấu hình và viết code định nghĩa chi tiết cho một package:

with Ada.Text_IO; use Ada.Text_IO;

package Week is

   Sun : constant String := "Sunday";
   Mon : constant String := "Monday";
   Tue : constant String := "Tuesday";
   Wed : constant String := "Wednesday";
   Thu : constant String := "Thursday";
   Fri : constant String := "Friday";
   Sat : constant String := "Saturday";

   procedure Put_Week;

end Week;
package body Week is

   procedure Put_Weeks is
      --
   begin
      Put_Line ("Sun means " & Sun);
      Put_Line ("Mon means " & Mon);
      Put_Line ("Tue means " & Tue);
      Put_Line ("Wed means " & Wed);
      Put_Line ("Thu means " & Thu);
      Put_Line ("Fri means " & Fri);
      Put_Line ("Sat means " & Sat);
   end Put_Weeks;
   
end Week;

Bây giờ chúng ta đã có thể sử dụng package Week trong chương trình chính.

with Ada.Text_IO; use Ada.Text_IO;
with Week; use Week;

procedure Main is
   --
begin
   Put_Line ("Using package: Week");
   Put_Weeks;
end Main;
Using package: Week
Mon means Monday
Tue means Tuesday
Wed means Wednesday
Thu means Thursday
Fri means Friday
Sat means Saturday
Sun means Sunday

Trong trường hợp cần kiến trúc chương trình với nhiều package xếp lớp. Ví dụ như trong Week chúng ta cần chia nhỏ thành các package nhỏ hơn thì chúng ta có thể thực hiện như sau. Trong thư mục week chúng ta sẽ tạo thêm thư mục child.

with Ada.Text_IO; use Ada.Text_IO;

package Week.Child is
   procedure Put_Week_Child;
end Week.Child;
package body Week.Child is

   procedure Put_Week_Child is
      --
   begin
      Put_Line ("Child package of Week");
   end Put_Week_Child;

end Week.Child;

Và sử dụng Week.Child tại chương trình main giống như một package độc lập.

with Ada.Text_IO; use Ada.Text_IO;
with Week.Child; use Week.Child;

procedure Main is
   --
begin
   Put_Line ("Using package: Week.Child");
   Put_Week_Child;
end Main;
Using package: Week.Child
Child package of Week

Sub-program

Có hai kiểu sub-program trong Ada, đó là các các Thủ Tục procedure và Hàm function. Điểm khác biệt căn bản giữa hai kiểu sub-program này đó là một Hàm function sẽ luôn luôn trả về một giá trị ở vị trí được gọi, còn một Thủ Tục procedure thì không bao giờ. Và ngược lại, một Hàm function sẽ không bao giờ tạo ra thay đổi tác động tới các yếu tố bên ngoài chính nó, còn một Thủ Tục procedure lại thường được sử dụng để tác động lên các yếu tố bên ngoài ví dụ như trạng thái hiển thị và các biến ngoại vi.

Hay nói một cách khác, Ada không hỗ trợ việc tạo ra một sub-program vừa tạo ra sự thay đổi tới môi trường bên ngoài và vừa trả về một giá trị ở vị trí được gọi. Và chính sự phân bổ chức năng của các sub-program được định hình rõ ràng ở cấp độ cú pháp của ngôn ngữ như thế này, sẽ buộc chúng ta hình thành thói quen thiết kế các sub-program có chủ đích rõ ràng hơn.

Ví dụ nếu cần thực hiện một thao tác thay đổi trạng thái hiển thị của console thì hiển nhiên chúng ta sẽ phải nghĩ tới một procedure. Còn nếu cần một công cụ hỗ trợ thu gọn logic tính toán thì có thể chúng ta sẽ muốn sử dụng một function hơn – để đảm bảo là sẽ không có dòng code lạc nào tạo ra hiệu ứng biên side-effect không mong muốn.

procedure

Từ đầu cho tới giờ thì chúng ta đã sử dụng các procedure căn bản không có các tham số parameter. Và bây giờ chúng ta sẽ làm một ví dụ định nghĩa procedure có các tham số. Để thuận tiện thì chúng ta sẽ bổ sung thêm một procedure Increment trong package Week.

with Ada.Text_IO; use Ada.Text_IO;

package Week is

   Sun : constant String := "Sunday";
   Mon : constant String := "Monday";
   Tue : constant String := "Tuesday";
   Wed : constant String := "Wednesday";
   Thu : constant String := "Thursday";
   Fri : constant String := "Friday";
   Sat : constant String := "Saturday";

   procedure Put_Weeks;
   
   procedure Increment ( Day : in Integer
                       ; Increased : out Integer
                       );
   
end Week;
package body Week is

   -- procedure Put_Weeks ...
   
   procedure Increment ( Day : in Integer
                       ; Increased : out Integer
                       ) is
   begin
      Increased := Day + 1;
   end Increment;
   
end Week;

Và sau đó thử sử dụng Increment trong Main:

with Ada.Text_IO; use Ada.Text_IO;
with Week;

procedure Main is
   Day : Integer := 0;
   Increased : Integer;
begin
   Week.Increment (Day, Increased);
   Put_Line ("Increased :" & Integer'Image (Increased));
end Main;
Increased : 1

Ngoài yếu tố định kiểu dữ liệu của các tham số thì chúng ta có thêm các từ khóa inout đính kèm ở phía trước. Từ khóa in sẽ giúp chúng ta giới hạn chức năng của tham số Day trong code logic chi tiết của Increment, và đảm bảo sẽ không có thao tác nào tạo ra sự thay đổi trên tham số này. Trong khi đó thì từ khóa out sẽ giúp chúng ta đảm bảo rằng, procedure này sẽ bắt buộc phải tạo ra tác động thông qua tham số Increased.

Trong môi trường của C thì chúng ta có thể sử dụng từ khóa const để mô phỏng lại các tham số in, còn các tham số out thì sẽ là các con trỏ pointer.

#include<stdio.h>#defineprocedurevoid#definein(type)const type#defineout(type) type*

procedure increment(in(int) day,out(int) increasedRef
){*increasedRef = day +1;}

procedure main(){int day =0, increased;// - beginincrement(day,&increased);printf("Increased : %i n", increased);// - end}

Bây giờ giả sử dụng ta thực hiện một phép gán thay đổi giá trị của biến Day trong code ví dụ của Ada – hay day trong code ví dụ của C ở trên thì chắc chắn trình biên dịch sẽ thông báo lỗi. Tuy nhiên thì khi mô phỏng lại tham số out của Ada bằng con trỏ pointer trong C thì chúng ta không thể đảm bảo được rằng tham số đó chắc chắn phải được sử dụng. Nếu chúng ta quên không tạo ra thao tác nào tác động tới con trỏ đó thì trình biên dịch của C cũng sẽ không đưa ra nhắc nhở nào.

Bạn thấy đấy, nếu xét trên khía cạnh giao diện lập trình bậc cao thì việc đem so sánh C với Ada chắc chắn sẽ là một sự thiệt thòi cho C. Và có lẽ, khi đã biết tới Ada thì việc sử dụng C sẽ chỉ có nhiều ý nghĩa nếu như chúng ta muốn giao tiếp với các thư viện cung cấp bởi các môi trường phát triển phần mềm khác. Ví dụ đơn cử là trình điều khiển của các DBMS chắc chắn sẽ luôn có phiên bản dành cho C đầu tiên rồi sau đó mới là các phiên bản dành cho các ngôn ngữ lập trình bậc cao phổ biến. Bởi vì dù gì thì C cũng là gốc rễ của các hệ điều hành máy tính mà chúng ta đang sử dụng. 😄

function

Bây giờ chúng ta sẽ chỉnh sửa lại Increment một chút và sử dụng định nghĩa function thay thế cho procedure.

package Week is

   -- ...

   procedure Put_Weeks;
   function Increment (Day : Integer) return Integer;

end Week;
package body Week is

   -- procedure Put_Weeks ...

   function Increment (Day : in Integer) 
      return Integer is
   begin
      return Day + 1;
   end Increment;
   
end Week;
with Ada.Text_IO; use Ada.Text_IO;
with Week;

procedure Main is
   Day : Integer := 0;
   Increased : Integer;
begin
   Increased := Week.Increment (Day);
   Put_Line ("Increased :" & Integer'Image (Increased));
end Main;

Do các Hàm function được định nghĩa là sẽ không tạo ra thay đổi tác động tới môi trường bên ngoài nên chúng ta không cần các chỉ dẫn inout cho các tham số. Thay vào đó thì mặc định tất cả các tham số được truyền vào sẽ đều là in và không có bất kỳ thao tác nào dạng như phép gán giá trị được cho phép. Nếu muốn thực hiện các thao tác tính toán có logic phức tạp, chúng ta sẽ phải khai báo thêm các biến cục bộ để sử dụng.

(chưa đăng tải) [Procedural Programming + Ada] Bài 5 – Types & Attributes

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ