
자, 이제 우리가 만든 코드를 좀 더 깔끔하게 정리하고, 똑같은 작업을 반복해야 할 때 아주 편리하게 사용할 수 있는 특별한 방법을 알아볼 거예요! 마치 레고 블록처럼, 여러 번 사용할 코드를 하나의 블록으로 만들어두고 필요할 때마다 가져다 쓰는 것과 비슷하답니다. 이 블록을 우리는 함수 (Function) 또는 프로시저 (Procedure) 라고 부릅니다.
여러 번 쓰는 코드는 미리 만들어두자!
우리가 매번 같은 메시지를 여러 사람에게 보내야 한다고 상상해 보세요. 매번 처음부터 타이핑한다면 매우 번거로울 것입니다. 대신, 미리 자주 쓰는 메시지를 저장해 두면 필요할 때마다 손쉽게 불러서 보낼 수 있겠죠?
프로그래밍에서도 이와 비슷한 원리가 적용됩니다. 반복되는 작업을 함수나 프로시저라는 미리 정의된 코드 묶음으로 만들어 두면, 해당 작업이 필요할 때마다 단 한 줄의 코드만 호출하여 실행할 수 있습니다. 이로 인해 코드 작성이 훨씬 간편해지고, 유지보수와 관리가 용이해집니다.
함수 만들기 (결과를 주는 마법 상자)
함수 (Function) 는 마치 결과를 내놓는 마법 상자와 같아요. 우리가 어떤 재료(입력)를 상자에 넣으면, 마법 상자는 정해진 방법대로 그 재료를 가공해서 새로운 결과(출력)를 우리에게 다시 돌려줍니다.
SPARK Ada에서 함수를 만드는 방법은 다음과 같아요:
function 함수_이름 (재료_이름 : 재료_타입) return 결과_타입 is
begin
-- 마법 상자 안에서 할 일 (코드)
return 만들어진_결과;
end 함수_이름;
function 함수_이름
: 이것은 “이제부터 함수를 만들 거야!”라고 컴퓨터에게 알려주는 약속이에요.함수_이름
은 우리가 이 마법 상자를 부를 때 사용할 이름입니다.(재료_이름 : 재료_타입)
: 이것은 마법 상자에 넣을 재료를 설명하는 부분이에요.재료_이름
은 우리가 이 재료를 부를 이름이고,재료_타입
은 그 재료가 어떤 종류인지 (예: 숫자, 글자) 알려주는 거예요. 이 부분을 매개변수 (Parameter) 라고 부릅니다. 함수는 여러 개의 재료를 받을 수도 있고, 아예 받지 않을 수도 있어요.return 결과_타입
: 이것은 마법 상자가 우리에게 어떤 종류의 결과를 돌려줄지 알려주는 부분이에요. 예를 들어, 숫자를 더하는 함수라면 결과도 숫자겠죠?is
: “이제 마법 상자 안에서 무슨 일이 일어날지 적을 거야!”라는 뜻입니다.begin
: 마법 상자의 문이 열리는 곳이라고 생각하면 돼요.-- 수행할 코드
: 이 부분에 우리가 원하는 작업을 적습니다. 마치 마법사가 주문을 외우는 것처럼요!return 만들어진_결과;
: 이 부분은 마법 상자가 작업을 끝내고 우리에게 결과를 돌려주는 순간이에요.만들어진_결과
는 우리가 원하는 최종 결과 값입니다.end 함수_이름;
: 이것은 “마법 상자 만들기가 끝났어!”라고 컴퓨터에게 알려주는 약속입니다.
간단한 예시: 두 개의 숫자를 받아서 더한 결과를 돌려주는 함수
function 더하기 (첫번째_수 : Integer; 두번째_수 : Integer) return Integer is
결과 : Integer; -- 결과를 저장할 상자 (지역 변수)
begin
결과 := 첫번째_수 + 두번째_수; -- 두 수를 더해서 결과 상자에 넣기
return 결과; -- 결과 상자에 있는 값을 돌려주기
end 더하기;
이 함수는 더하기
라는 이름을 가지고 있고, 첫번째_수
와 두번째_수
라는 두 개의 정수(Integer) 재료를 받아서, 정수 타입의 결과 값을 돌려줍니다.
프로시저 만들기 (명령을 수행하는 마법사)
프로시저 (Procedure) 란 사전적으로 다음과 같은 의미를 갖습니다.
- 어떤 일을 하는 방식, 특히 통상적이거나 올바른 방식.
- 특정 순서나 방식으로 수행되는 일련의 행위 또는 단계.
- 공식적인 상황에서, 어떤 일을 처리하기 위해 확립된 또는 공식적인 방법이나 절차.
컴퓨터 과학 분야에서 ‘프로시저’는 특정 작업을 수행하기 위해 미리 정의된 일련의 명령어들을 의미합니다. 이는 사전적 의미와 마찬가지로, 정해진 순서에 따라 작업을 처리하는 방식을 나타냅니다.
이제 SPARK Ada에서 프로시저를 만드는 방법을 알아보겠습니다. 프로시저 (Procedure) 는 함수와 비슷하지만, 결과 값을 직접 돌려주지 않고 특정 작업을 수행하는 마법사와 같아요. 예를 들어, 화면에 글자를 보여주거나, 어떤 설정을 변경하는 등의 작업을 수행합니다.
SPARK Ada에서 프로시저를 만드는 방법은 다음과 같아요:
procedure 프로시저_이름 (매개변수_목록) is
begin
-- 수행할 코드 (마법사가 하는 일)
end 프로시저_이름;
procedure 프로시저_이름
: “이제부터 프로시저를 만들 거야!”라고 컴퓨터에게 알려주는 약속이에요.프로시저_이름
은 우리가 이 마법사를 부를 때 사용할 이름입니다.(매개변수_목록)
: 이것은 마법사에게 시킬 일을 설명하는 재료 또는 지시사항이에요. 함수와 마찬가지로 여러 개의 매개변수를 받을 수도 있고, 없을 수도 있습니다.is
: “이제 마법사가 무슨 일을 할지 적을 거야!”라는 뜻입니다.begin
: 마법사가 일을 시작하는 곳이라고 생각하면 돼요.-- 수행할 코드
: 이 부분에 마법사가 해야 할 일을 적습니다.end 프로시저_이름;
: 이것은 “마법사 만들기가 끝났어!”라고 컴퓨터에게 알려주는 약속입니다.
간단한 예시: 화면에 “안녕하세요!”라고 출력하는 프로시저
with Ada.Text_IO; use Ada.Text_IO;
procedure 인사하기 is
begin
Put_Line("안녕하세요!"); -- 화면에 "안녕하세요!"라고 출력하기
end 인사하기;
이 프로시저는 인사하기
라는 이름을 가지고 있고, 특별한 재료(매개변수)는 필요하지 않습니다. 실행되면 화면에 “안녕하세요!”라는 메시지를 보여주는 작업을 수행합니다.
함수와 프로시저를 사용하는 방법
우리가 만든 함수나 프로시저를 실제로 사용하려면, 그 이름을 불러주면 돼요. 마치 함수를 호출하는 것은, 전자레인지의 버튼을 누르는 것과 같습니다. 버튼 한 번으로 내부에 준비된 복잡한 과정이 자동으로 실행되어 원하는 결과를 얻을 수 있죠. 프로시저를 사용하는 것도 마찬가지입니다. 프로시저의 이름을 불러주면, 미리 정의된 일련의 작업들이 순서대로 실행됩니다.
함수 사용하기: 함수는 결과를 돌려주기 때문에, 보통 그 결과를 받아서 사용합니다.
결과_값 : Integer;
...
결과_값 := 더하기(5, 3); -- "더하기" 함수를 불러서 5와 3을 재료로 주고, 결과를 받아서 결과_값 상자에 저장
Put_Line("결과는: " & Integer'Image(결과_값)); -- 결과_값에 저장된 값을 화면에 출력
프로시저 사용하기: 프로시저는 특정 작업을 수행하므로, 단순히 이름을 불러주기만 하면 됩니다.
인사하기; -- "인사하기" 프로시저를 불러서 실행
매개변수 전달 방식 (값 호출과 값 반환)
함수나 프로시저에 재료(매개변수)를 전달하는 방식은 여러 가지가 있지만, SPARK Ada에서는 주로 in, out, 그리고 in out 세 가지 방식을 사용합니다.
in
매개변수 (값 호출):
함수나 프로시저가 사용할 입력 값을 전달할 때 사용합니다. 마치 우리가 요리사에게 요리 재료를 주는 것과 같아요. 요리사는 우리가 준 재료를 사용해서 요리를 하지만, 우리가 원래 가지고 있던 재료 자체는 변하지 않습니다. SPARK Ada에서는 매개변수 모드를 in
으로 명시하거나, 아무것도 쓰지 않으면 기본적으로 in
으로 처리됩니다.
with Ada.Text_IO; use Ada.Text_IO;
procedure 보여주기 (숫자 : in Integer) is
begin
Put_Line("보여줄 숫자는: " & Integer'Image(숫자));
end 보여주기;
my_number : Integer := 10;
보여주기(my_number); -- my_number의 값 10이 복사되어 보여주기 프로시저로 전달됨
-- 보여주기 프로시저 안에서 숫자를 변경하려고 해도 SPARK Ada에서는 허용되지 않습니다.
-- 그리고 변경하더라도 my_number의 값은 그대로 10입니다.
out
매개변수 (값 반환):
프로시저가 작업을 수행한 후 결과를 우리에게 돌려줄 때 사용합니다. 마치 우리가 세탁소에 옷을 맡겼다가 깨끗하게 세탁된 옷을 다시 받는 것과 같아요. 프로시저를 만들 때 매개변수 모드를 out
으로 지정합니다.
with Ada.Text_IO; use Ada.Text_IO;
procedure 두배로만들기 (원본_숫자 : in Integer; 결과_숫자 : out Integer) is
begin
결과_숫자 := 원본_숫자 * 2; -- 원본 숫자에 2를 곱한 결과를 결과_숫자에 저장
end 두배로만들기;
my_number : Integer := 5;
doubled_number : Integer;
두배로만들기(my_number, doubled_number); -- my_number의 값 5를 전달하고, 결과는 doubled_number에 저장됨
Ada.Text_IO.Put_Line("두 배 값은: " & Integer'Image(doubled_number)); -- doubled_number에는 10이 저장되어 있습니다.
in out
매개변수: 값을 전달하고 변경된 값을 돌려받는 방법 (마법사에게 재료를 주고, 마법사가 가공한 결과물 받기)
때로는 프로시저가 처음부터 어떤 값을 가지고 작업을 시작해야 하지만, 작업이 끝난 후에는 그 값이 변경되어야 하는 경우가 있습니다. 이럴 때 사용하는 것이 in out
매개변수입니다. 마치 우리가 마법사에게 어떤 재료를 주면, 마법사가 그 재료를 가지고 마법을 부린 후, 원래 재료가 변형된 결과물을 우리에게 다시 돌려주는 것과 같습니다.
SPARK Ada에서 in out
매개변수를 사용하는 방법은 다음과 같습니다. 프로시저를 만들 때 매개변수 모드를 in out
으로 지정합니다.
with Ada.Text_IO; use Ada.Text_IO;
procedure 두배로만들기_InOut (결과_숫자 : in out Integer) is
begin
결과_숫자 := 결과_숫자 * 2; -- 현재 값에 2를 곱해서 다시 저장
end 두배로만들기_InOut;
my_number : Integer := 5;
두배로만들기_InOut(my_number); -- my_number의 값 5를 전달하고, 프로시저 내부에서 값이 변경됨
Ada.Text_IO.Put_Line("두 배 값은: " & Integer'Image(my_number)); -- my_number에는 이제 10이 저장되어 있습니다.
위 예시에서 두배로만들기_InOut
프로시저의 결과_숫자
매개변수는 in out
모드로 선언되었습니다. my_number
변수를 이 프로시저에 전달하면, 프로시저 내부에서 my_number
의 값이 2배로 변경되고, 프로시저 호출이 끝난 후에도 my_number
변수의 값이 10으로 유지되는 것을 확인할 수 있습니다.
SPARK Ada에서 in out
매개변수 사용 시 주의사항:
in out
매개변수는 프로시저 안팎으로 값이 모두 전달되기 때문에, 코드의 흐름을 파악하는 것이in
이나out
매개변수만 사용하는 경우보다 조금 더 복잡해질 수 있습니다.- SPARK의 강력한 정적 분석 기능을 활용하기 위해서는
in out
매개변수를 사용하는 프로시저에 대해 사전 조건(Pre
)과 사후 조건(Post
)을 명확하게 명시하는 것이 중요합니다. 이를 통해 프로시저가 호출될 때 어떤 상태여야 하고, 호출이 끝난 후 어떤 상태가 되는지를 정확하게 정의할 수 있습니다. - 초보자의 경우,
in out
매개변수를 과도하게 사용하면 코드의 이해도가 떨어질 수 있습니다. 가능한 경우에는in
매개변수로 값을 전달하고,out
매개변수를 통해 결과를 반환하는 방식을 사용하는 것을 고려해 보는 것도 좋습니다.
정리하자면, SPARK Ada는 in out
매개변수를 지원하며, 프로시저가 값을 받아서 변경한 후 다시 전달해야 하는 상황에서 유용하게 사용할 수 있습니다. 다만, 코드의 명확성과 SPARK의 검증 기능을 최대한 활용하기 위해서는 신중하게 사용하는 것이 중요합니다.
자주 묻는 질문: 함수나 프로시저 안에서 매개변수를 변경했는데, 원래 변수는 왜 그대로인가요?
프로그래밍을 처음 배우는 학습자들은 함수나 프로시저에 변수를 전달했을 때, 그 내부에서 변수의 값을 변경하면 원래 변수의 값도 함께 바뀔 것이라고 생각하기 쉽습니다. 하지만 SPARK Ada (그리고 많은 프로그래밍 언어)에서는 in
모드로 전달된 매개변수의 경우, 실제로는 변수의 값이 복사되어 함수나 프로시저 내부로 전달됩니다. 이를 콜 바이 밸류 (Call by Value) 또는 값 호출이라고 합니다.
따라서 함수나 프로시저 내부에서 이 복사된 값을 아무리 변경해도, 원래 변수의 값에는 영향을 미치지 않습니다. 마치 우리가 책을 복사해서 친구에게 빌려줬을 때, 친구가 복사본에 아무리 낙서를 해도 원래 책은 깨끗한 것과 같은 원리입니다.
다음 SPARK Ada 코드를 통해 이를 명확히 이해해 봅시다.
with Ada.Text_IO; use Ada.Text_IO;
procedure Test_Value_Passing is
procedure Increment (Value : in Integer) is
begin
declare
Local_Value : Integer := Value; -- Value 값을 복사한 지역 변수
begin
Local_Value := Local_Value + 1;
Ada.Text_IO.Put_Line("Increment 프로시저 내부, Local_Value 값: " & Integer'Image(Local_Value));
end;
-- Value := Value + 1; -- SPARK Ada에서는 'in' 매개변수의 값을 직접 변경할 수 없습니다.
end Increment;
Number : Integer := 1;
begin
Ada.Text_IO.Put_Line("Increment 호출 전, Number 값: " & Integer'Image(Number));
Increment(Number);
Ada.Text_IO.Put_Line("Increment 호출 후, Number 값: " & Integer'Image(Number));
end Test_Value_Passing;
이 코드를 실행하면 다음과 같은 결과가 출력됩니다.
Increment 호출 전, Number 값: 1
Increment 프로시저 내부, Local_Value 값: 2
Increment 호출 후, Number 값: 1
결과를 보면 Increment
프로시저 내부에서는 Value
값을 복사한 Local_Value
가 2로 증가했지만, 프로시저 호출 전후로 Number
의 값은 여전히 1인 것을 확인할 수 있습니다. 이는 Number
의 값인 1이 복사되어 Increment
프로시저로 전달되었기 때문입니다. 프로시저 내부에서 변경된 것은 복사된 값일 뿐, 원래 변수인 Number
는 변경되지 않은 것입니다.
함수와 프로시저를 사용하는 것은 정말 편리해요!
함수와 프로시저를 사용하면 다음과 같은 좋은 점들이 있습니다:
- 코드 재사용성 증가: 똑같은 코드를 여러 번 작성할 필요 없이, 함수나 프로시저를 필요한 곳에서 불러서 사용할 수 있습니다.
- 프로그램 구조 개선: 코드를 논리적인 작은 덩어리(함수, 프로시저)로 나누어서 작성하면 프로그램을 훨씬 이해하기 쉽고 관리하기 편하게 만들 수 있습니다.
- 오류 감소: 반복되는 코드를 함수나 프로시저로 묶어두면, 오류가 발생했을 때 그 부분만 수정하면 되므로 전체 프로그램의 오류를 줄일 수 있습니다.
이제부터 우리는 코드를 작성할 때 함수와 프로시저를 적극적으로 활용하여 더 효율적이고 깔끔한 프로그램을 만들어갈 것입니다!