명령형 언어
명령형 언어 (Imperative language)
Ada는 객체 지향과 함수형 프로그래밍 요소를 지원하는 멀티 패러다임 언어예요. 하지만 그 핵심은 C나 Pascal에 가까운 단순하고 일관성 있는 절차적·명령형 언어예요.
출처: 명령형 언어 문서
본문
문장과 표현식의 구분
Ada와 C 같은 언어의 중요한 차이점 하나는 문장(statement)과 표현식(expression)이 아주 분명하게 구분된다는 점이에요. Ada에서 문장이 필요한 자리에 표현식을 쓰면 프로그램이 컴파일되지 않아요. 이 규칙은 유용한 스타일 원칙을 뒷받침해요. 표현식은 값을 전달하기 위한 것이지 부수 효과를 내기 위한 것이 아니라는 뜻이죠. 또 =(같음 연산자)를 :=(대입 연산자) 자리에 잘못 쓰는 등의 프로그래밍 오류도 막아줘요.
Hello World
아주 간단한 명령형 Ada 프로그램을 볼게요:
with Ada.Text_IO;
procedure Greet is
begin
-- Print "Hello, World!" to the screen
Ada.Text_IO.Put_Line ("Hello, World!");
end Greet;
이 소스가 greet.adb 파일에 있다고 가정할게요. 이 소스를 GNAT 컴파일러로 컴파일하고 실행 파일을 돌리면 예상대로의 결과를 얻어요.
$ gprbuild greet.adb
using project file [...]_default.gpr
Compile
[Ada] greet.adb
Bind
[gprbind] greet.bexch
[Ada] greet.ali
Link
[link] greet.adb
$ ./greet
Hello, World!
$
위 프로그램에서 눈여겨볼 점이 몇 가지 있어요:
- Ada의 서브프로그램은 프로시저(procedure) 아니면 함수(function)예요. 위처럼 프로시저는 호출해도 값을 반환하지 않아요.
with는 프로시저에서 필요한 외부 모듈을 참조할 때 써요. 여러 언어의import와 비슷하고 C/C++의#include와 대략 비슷해요. 여기서는 표준 라이브러리 모듈인Ada.Text_IO패키지를 요청하는데, 이 패키지에는 화면에 텍스트를 출력하는Put_Line프로시저가 들어 있어요.Greet는 프로시저이자 우리 첫 프로그램의 메인 진입점이에요. C/C++와 달리 원하는 어떤 이름이든 쓸 수 있어요. 빌더가 진입점을 결정하죠. 이 간단한 예시에서는 GNAT의 빌더인 gprbuild가 인자로 넘긴 파일을 사용해요.Put_Line은Greet와 마찬가지로Ada.Text_IO모듈에 선언된 프로시저예요. C의printf에 해당하는 Ada 문법이죠.- 주석은
--로 시작해 줄 끝까지 이어져요. 여러 줄 주석 문법은 없어요. 즉 한 줄에서 주석을 시작해 다음 줄로 이어갈 수 없어요. Ada에서 여러 줄 주석을 만드는 유일한 방법은 각 줄마다--를 쓰는 거예요. 예를 들어:
-- We start a comment in this line...
-- and we continue on the second line...
If/Then/Else
이 절에서는 Ada의 if 문장을 설명하고 정수 입출력, 데이터 선언, 서브프로그램 매개변수 모드 등 몇 가지 기본 언어 기능도 소개할게요.
Ada의 if 문장은 형태와 기능이 꽤 예상 가능한 수준이에요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Check_Positive is
N : Integer;
begin
-- Put a String
Put ("Enter an integer value: ");
-- Read in an integer value
Get (N);
if N > 0 then
-- Put an Integer
Put (N);
Put_Line (" is a positive number");
end if;
end Check_Positive;
if 문장은 최소한 예약어 if, 조건(반드시 Boolean 값), 예약어 then, 조건이 True로 평가되면 실행되는 비어 있지 않은 문장 시퀀스(then 부분), 그리고 종결 end if로 구성돼요.
이 예시는 정수 변수 N을 선언하고, 사용자에게 정수를 입력받고, 값이 양수인지 확인해서 양수라면 정수 값 뒤에 " is a positive number" 문자열을 출력해요. 값이 양수가 아니면 아무 출력도 하지 않아요.
Integer 타입은 미리 정의된 부호 있는 타입이며 그 범위는 컴퓨터 아키텍처에 따라 달라져요. 전형적인 현재 프로세서에서 Integer는 32비트 부호 있는 정수예요.
이 예시는 정수 입출력의 기본 기능을 보여줘요. 관련 서브프로그램은 미리 정의된 Ada.Integer_Text_IO 패키지에 있고, 키보드에서 숫자를 읽는 Get 프로시저와 정수 값을 표시하는 Put 프로시저를 포함해요.
else 부분이 있는 if 문장을 보여주는 약간의 변형도 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Check_Positive is
N : Integer;
begin
-- Put a String
Put ("Enter an integer value: ");
-- Reads in an integer value
Get (N);
-- Put an Integer
Put (N);
if N > 0 then
Put_Line (" is a positive number");
else
Put_Line (" is not a positive number");
end if;
end Check_Positive;
이 예시에서는 입력 값이 양수가 아니면 값 뒤에 " is not a positive number" 문자열을 출력해요.
마지막 변형은 elsif 섹션이 있는 if 문장을 보여줘요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Check_Direction is
N : Integer;
begin
Put ("Enter an integer value: ");
Get (N);
Put (N);
if N = 0 or N = 360 then
Put_Line (" is due north");
elsif N in 1 .. 89 then
Put_Line (" is in the northeast quadrant");
elsif N = 90 then
Put_Line (" is due east");
elsif N in 91 .. 179 then
Put_Line (" is in the southeast quadrant");
elsif N = 180 then
Put_Line (" is due south");
elsif N in 181 .. 269 then
Put_Line (" is in the southwest quadrant");
elsif N = 270 then
Put_Line (" is due west");
elsif N in 271 .. 359 then
Put_Line (" is in the northwest quadrant");
else
Put_Line (" is not in the range 0..360");
end if;
end Check_Direction;
이 예시는 사용자가 0부터 360(포함) 사이의 정수를 입력하기를 기대하고, 값이 어떤 사분면이나 축에 해당하는지 표시해요. Ada의 in 연산자는 스칼라 값이 지정된 범위 안에 있는지 검사하고 Boolean 결과를 반환해요. 프로그램의 효과는 자명해요. 나중에 case 문장을 통해 같은 효과를 내는 더 효율적인 대안 스타일을 볼 거예요.
Ada의 elsif 키워드는 중첩된 else .. if 블록을 쓰는 C/C++와 달라요. 또 Ada에는 end if가 있어서 "dangling else"(매달린 else) 문제를 피할 수 있다는 차이가 있어요.
반복문 (Loops)
Ada에는 반복문을 지정하는 세 가지 방법이 있어요. C/Java/Javascript의 for 루프와는 달리, 더 단순한 구문과 의미를 갖고 있으며 Ada의 철학에 부합해요.
for 루프 (For loops)
첫 번째 종류는 이산형 범위(discrete range)를 순회할 수 있는 for 루프예요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet_5a is
begin
for I in 1 .. 5 loop
-- Put_Line is a procedure call
Put_Line ("Hello, World!"
& Integer'Image (I));
-- ^ Procedure parameter
end loop;
end Greet_5a;
이 프로시저를 실행하면 다음 출력을 얻어요:
Hello, World! 1
Hello, World! 2
Hello, World! 3
Hello, World! 4
Hello, World! 5
몇 가지 주의할 점:
1 .. 5는 1부터 5까지(포함)의 이산형 범위예요.- 루프 본문 안의 루프 매개변수
I(이름은 임의)는 이 범위 안의 값을 가져요. I는 루프에 국한돼서 루프 밖에서는 참조할 수 없어요.I의 값은 매 반복마다 증가하지만 프로그램 관점에서는 상수로 취급돼요. 값을 수정하려고 시도하는 건 불법이라 컴파일러가 프로그램을 거부해요.Integer'Image는 Integer를 받아String으로 변환하는 함수예요. 이것은'구문으로 표시되는 애트리뷰트(attribute) 라는 언어 구조의 예시이며, 나중에 더 자세히 다룰 거예요.&기호는 String 값의 연결(concatenation) 연산자예요.end loop는 루프의 끝을 표시해요.
루프의 "보폭(step)"은 1(앞방향)과 -1(뒤방향)로 제한돼요. 범위를 거꾸로 순회하려면 reverse 키워드를 써요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet_5a_Reverse is
begin
for I in reverse 1 .. 5 loop
Put_Line ("Hello, World!"
& Integer'Image (I));
end loop;
end Greet_5a_Reverse;
이 프로시저를 실행하면 다음 출력을 얻어요:
Hello, World! 5
Hello, World! 4
Hello, World! 3
Hello, World! 2
Hello, World! 1
for 루프의 경계는 런타임에 계산될 수 있어요. 루프 본문이 실행되기 전에 한 번 평가되죠. 상한 값이 하한 값보다 작으면 루프는 전혀 실행되지 않아요. reverse 루프에서도 마찬가지예요. 그래서 다음 예시는 아무 출력도 만들어내지 않아요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet_No_Op is
begin
for I in reverse 5 .. 1 loop
Put_Line ("Hello, World!"
& Integer'Image (I));
end loop;
end Greet_No_Op;
for 루프는 여기서 보여준 것보다 더 일반적인데, 자세한 건 나중에 다룰게요.
bare 루프 (Bare loops)
Ada에서 가장 단순한 루프는 bare 루프이며 다른 종류의 Ada 루프의 기초를 이뤄요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet_5b is
-- Variable declaration:
I : Integer := 1;
-- ^ Type
-- ^ Initial value
begin
loop
Put_Line ("Hello, World!"
& Integer'Image (I));
-- Exit statement:
exit when I = 5;
-- ^ Boolean condition
-- Assignment:
I := I + 1;
-- There is no I++ short form to
-- increment a variable
end loop;
end Greet_5b;
이 예시는 앞서 보여준 Greet_5a와 같은 효과를 내요. 몇 가지 개념을 보여주죠:
is와begin사이에I라는 변수를 선언했어요. 이것이 선언 영역(declarative region) 이에요. Ada는 선언 영역과 서브프로그램의 문장 부분을 뚜렷하게 구분해요. 선언은 선언 영역에 나타날 수 있지만 문장으로서는 허용되지 않아요.- bare 루프 문장은 단독
loop키워드로 시작하며, 모든 종류의 루프 문장처럼end loop키워드 조합으로 종료돼요. 그 자체로는 무한 루프예요.exit문장으로 빠져나올 수 있어요. - 대입 구문은
:=이고, 같음 구문은=예요. 이 둘을 혼동할 방법이 없어요. 앞서 말했듯 Ada에서는 문장과 표현식이 구별되고 표현식은 유효한 문장이 아니기 때문이에요.
while 루프 (While loops)
Ada의 마지막 루프 종류는 while 루프예요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet_5c is
I : Integer := 1;
begin
-- Condition must be a Boolean value
-- (no Integers).
-- Operator "<=" returns a Boolean
while I <= 5 loop
Put_Line ("Hello, World!"
& Integer'Image (I));
I := I + 1;
end loop;
end Greet_5c;
조건은 각 반복 전에 평가돼요. 결과가 false면 루프가 종료돼요. 이 프로그램은 이전 예시들과 같은 효과를 내요.
C 기반 언어와 달리, Ada는 while 루프의 조건에 대해 다른 의미를 가져요. Ada에서 조건은 Boolean 값이어야 하며 그렇지 않으면 컴파일러가 프로그램을 거부해요. 조건은 0이 아닌지 0인지에 따라 True/False로 취급되는 정수가 아니에요.
Case 문장 (Case statement)
Ada의 case 문장은 C/C++의 switch 문장과 비슷하지만 몇 가지 중요한 차이가 있어요. 앞서 if 문장으로 보여준 프로그램의 변형 예시를 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Check_Direction is
N : Integer;
begin
loop
Put ("Enter an integer value: ");
Get (N);
Put (N);
case N is
when 0 | 360 =>
Put_Line
(" is due north");
when 1 .. 89 =>
Put_Line
(" is in the northeast quadrant");
when 90 =>
Put_Line
(" is due east");
when 91 .. 179 =>
Put_Line
(" is in the southeast quadrant");
when 180 =>
Put_Line
(" is due south");
when 181 .. 269 =>
Put_Line
(" is in the southwest quadrant");
when 270 =>
Put_Line
(" is due west");
when 271 .. 359 =>
Put_Line
(" is in the northwest quadrant");
when others =>
Put_Line
(" Au revoir");
exit;
end case;
end loop;
end Check_Direction;
이 프로그램은 정수 값을 반복적으로 요청하고, 값이 0 .. 360 범위에 있으면 해당 사분면이나 축을 표시해요. 이 범위 밖의 정수면 작별 메시지를 출력한 뒤 루프(그리고 프로그램)가 종료돼요.
case 문장의 효과는 앞 예시의 if 문장과 비슷하지만, case 문장은 여러 범위 검사를 포함하지 않아 더 효율적일 수 있어요.
Ada case 문장의 주목할 점:
- case 표현식(여기서 변수
N)은 이산형(discrete type)이어야 해요. 즉 정수 타입이거나 열거 타입이어야 하죠. 이산형에 대해서는 이산형에서 더 자세히 다룰게요. - case 표현식의 가능한 모든 값은 case 문장의 고유한 분기 하나로 덮여야 해요. 이는 컴파일 시점에 검사돼요.
- 분기는 단일 값(
0), 값의 범위(1 .. 89), 또는 이 둘의 조합(|로 구분)을 지정할 수 있어요. - 특수하게, 선택적인 마지막 분기는 앞선 분기들에 포함되지 않은 모든 값을 덮는
others를 지정할 수 있어요. - 실행은 case 표현식을 평가하고, 그 값을 덮는 고유 분기의 문장 시퀀스로 제어를 옮기는 것으로 구성돼요.
- 선택된 분기의 문장 실행이 끝나면 제어는
end case이후로 재개돼요. C와 달리 다음 분기로 넘어가지 않아요(fall through). 그래서 Ada에는break문장이 필요 없고 실제로도 없어요.
선언 영역 (Declarative regions)
앞서 말했듯 Ada는 프로그램에서 사용될 개체의 이름을 도입하는 선언과 처리를 수행하는 문장을 구문적으로 뚜렷하게 분리해요. 선언이 나타날 수 있는 프로그램 영역을 선언 영역이라고 해요.
어떤 서브프로그램에서든 is와 begin 사이의 섹션은 선언 영역이에요. 여기에 변수, 상수, 타입, 내부 서브프로그램, 기타 개체를 둘 수 있어요.
선언 영역에서 정수 변수 X를 선언하고 초기화와 덧셈을 수행하는 간단한 예시를 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
X : Integer;
begin
X := 0;
Put_Line ("The initial value of X is "
& Integer'Image (X));
Put_Line ("Performing operation on X...");
X := X + 1;
Put_Line ("The value of X now is "
& Integer'Image (X));
end Main;
중첩 프로시저의 예시도 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
procedure Nested is
begin
Put_Line ("Hello World");
end Nested;
begin
Nested;
-- Call to Nested
end Main;
선언은 문장으로 나타날 수 없어요. 문장 사이에서 지역 변수를 선언해야 한다면 블록 문장(block statement)으로 새 선언 영역을 도입할 수 있어요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Greet is
begin
loop
Put_Line ("Please enter your name: ");
declare
Name : String := Get_Line;
-- ^ Call to the
-- Get_Line function
begin
exit when Name = "";
Put_Line ("Hi " & Name & "!");
end;
-- Name is undefined here
end loop;
Put_Line ("Bye!");
end Greet;
주의: Get_Line 함수는 사용자로부터 입력을 받아 문자열로 결과를 얻는 기능이에요. C의 scanf 함수와 대략 동등해요. String을 반환하는데, 이는 나중에 볼 Unconstrained array type이에요. 지금은 String 변수를 선언하고 크기를 미리 모르면, 선언 중에 변수를 초기화해야 한다는 점만 기억해두세요.
조건 표현식 (Conditional expressions)
Ada 2012는 조건 문장(if와 case)에 대응하는 표현식 버전을 도입했어요.
if 표현식 (If expressions)
앞서 본 예시의 대안 버전이에요. if 문장이 if 표현식으로 바뀌었어요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Check_Positive is
N : Integer;
begin
Put ("Enter an integer value: ");
Get (N);
Put (N);
declare
S : constant String :=
(if N > 0
then " is a positive number"
else " is not a positive number");
begin
Put_Line (S);
end;
end Check_Positive;
if 표현식은 N에 따라 두 String 중 하나로 평가되고 그 값을 지역 변수 S에 대입해요.
Ada의 if 표현식은 if 문장과 비슷해요. 하지만 표현식이라는 사실에서 비롯된 몇 가지 차이점이 있어요:
- 모든 분기의 표현식은 같은 타입이어야 해요.
- 주변 표현식에 이미 괄호가 없다면 반드시 괄호로 둘러싸야 해요.
then뒤의 표현식이 Boolean 값이 아니라면else분기가 필수예요. Boolean 값이면else분기는 선택 사항이고, 없으면 기본적으로else True로 처리돼요.
다른 예시를 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
begin
for I in 1 .. 10 loop
Put_Line (if I mod 2 = 0
then "Even"
else "Odd");
end loop;
end Main;
이 프로그램은 "Odd"와 "Even"을 번갈아 10줄 출력해요.
case 표현식 (Case expressions)
if 표현식과 유사하게, Ada에는 case 표현식도 있어요. 예상한 대로 동작해요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
begin
for I in 1 .. 10 loop
Put_Line
(case I is
when 1 | 3 | 5 | 7 | 9 => "Odd",
when 2 | 4 | 6 | 8 | 10 => "Even");
end loop;
end Main;
이 프로그램은 앞선 예시와 같은 효과를 내요. 구문은 case 문장과 다르게 분기들이 쉼표로 구분돼요.