본문 바로가기
Language

[Programming Language] 6. 부프로그램(Subprogram) (4)

by 삼준 2023. 7. 31.
반응형

직전글

2023.07.30 - [Language] - [Programming Language] 6. 부프로그램(Subprogram) (3)

 

● 중복 부프로그램

- 중복 연산자

여러 가지 의미를 갖는 연산자. 피연산자의 타입에 의해 의미가 결정됨.

 

- 중복 부프로그램

같은 참조 환경에서 다른 부프로그램과 이름이 같은 부프로그램

모든 중복 부프로그램은 "고유한" 프로토콜을 가짐. 즉, 매개변수의 개수, 순서, 타입, 또는 반환 타입에서 달라야 함.

어떤 부프로그램이 호출되었는지는 "실 매개변수 리스트"와 "반환값의 타입"에 의해 결정됨.

 

ㄴ C++, Java의 사용자 정의 중복 생성자
각 중복 생성자는 유일한 매개변수 프로파일을 갖기 때문에, 컴파일러는 매개변수의 타입으로 모호함을 해결함.

그러나, C++와 Java는 혼합형 표현식 (정수와 실수의 덧셈 등)을 허용하기 때문에, 반환 타입은 중복 함수의 모호성 제거에 사용되지 않음.

반환 타입이 중복함수 구분에 사용될 수 없는 이유 예시

위와 같은 프로그램은 컴파일에 실패함. C++ 컴파일러에게 두 fun 함수는 동일한 것으로 보이기 때문임.

 

ㄴ 디폴트 매개변수를 갖는 중복 부프로그램

// C++
void fun(float b = 0.0);
void fun();

...

fun();
// 매개변수가 생략된 것인지, 없는 것인지 구분할 수 없음 ㅡ 컴파일 오류

모호한 부프로그램 호출을 야기할 수 있음.

 

● 포괄형 부프로그램

소프트웨어 재사용은 소프트웨어 생산성 증가를 가져옴.
소프트웨어 재사용을 증가시키는 한 가지 방법은 타입이 다른 여러 개의 데이터에 동일한 알고리즘은 구현하는 여러 개의 다른 부프로그램을 생성하는 필요성을 줄이는 것임.

 

- 다형 부프로그램 (Polymorphic Subprogram)

부프로그램의 활성화(호출) 시점마다, 다른 타입의 매개변수를 취할 수 있는 부프로그램. 중복 부프로그램과는 다름.

중복 부프로그램은 이름만 같고 다른 기능을 하는 부프로그램이라면, 다형 부프로그램은 다른 타입들에 대해 같은 기능을 수행하는 부프로그램이라고 보면 됨.

중복 부프로그램도 특이 다형성 (Ad Hoc Polymorphism)이라는 특수한 종류의 다형성을 제공함.

※ "Ad Hoc"은 "임시방편의" 정도로 해석하면 됨.

 

- 부타입 다형성

객체 지향 프로그래밍을 지원하는 언어들이 일반적으로 지원하는 다형성.

타입 T의 변수는 타입 T 또는 T에서 파생한 타입의 객체를 접근할 수 있다는 것을 의미함.

ex. 오버로딩 안된 메소드

 

- 보다 일반적인 다형성을 제공하는 언어 : Python, Ruby

이들 언어에서 변수는 타입을 갖지 않음 따라서, 형식 매개변수는 타입을 갖지 않음.
그러므로, 메소드 안에서 형식 매개변수를 사용하는 연산자가 정의되어 있기만 하다면, 메소드는 어떠한 임의의 타입에 대해서도 동작함.

 

- 매개변수 다형성

부프로그램의 매개변수 타입을 서술하는 타입 표현식에서, 포괄형 매개변수를 사용한 부프로그램에 의해 제공됨.

매개변수 다형성 부프로그램 == 포괄형 부프로그램 == 다형 부프로그램

 

- Java에서의 포괄형 부프로그램

포괄형 타입과 포괄형 메소드에 대한 지원은 Java 5.0에서 추가됨.

// 정의
public static <T> T doIt(T[] list) {
  ...
}

...

// 호출
doIt<String>(myList);

포괄형 타입의 이름은 T. doIt이 취하는 매개변수는 T의 배열임. 호출은 String 타입의 배열 myList에 대하여 메소드를 호출한 것임.

 

● 함수 설계 고려 사항

1. 함수 부작용

대부분의 언어 (특히 명령형 언어)에서는 함수의 부작용이나 별칭을 발생시키는 함수를 허용함.

순수 함수 언어들은 함수의 부작용을 가지지 않음. (변수를 가지지 않기 때문)

 

2. 반환값의 타입

대부분의 명령형 언어는 함수에 의해서 반환되는 타입을 제한함.

C 언어의 경우, 배열과 함수를 제외한 모든 타입을 반환할 수 있음. (포인터로는 반환 가능함.)
Python의 경우, 모든 타입의 값을 반환할 수 있음.
Java의 경우, 메소드를 제외한 임의의 타입이나 클래스를 메소드가 반환할 수 있음.

 

3. 반환값의 개수

대부분의 명령형에는 단지 한 개의 값만이 함수로부터 반환됨.
예외로는 Ruby, Lua, Go 등이 있음.
만약 함수 fun이 세 개의 값을 반환하고, 호출자가 세 개를 받는다면 다음과 같이 사용됨.

a, b, c = fun();

 

● 사용자 정의 중복 연산자

C++, Python에서 연산자는 사용자에 의해서 중복될 수 있음. (Python의 오버로딩에 대한 언급이 없는 이유는 Python은 타입이 없어 오버로딩이 안되기 때문임.)

- Python 클래스 예제

Python에서 기본으로 제공하는 복소수 클래스 Complex는 real(실수부)과 image(허수부)로 명명된 두 개의 멤버를 가짐.

예제는 x + y를 x.__add__(y) 형태로 구현하였음.

def __add__ (self, second):
  return Complex(self.real + second.real, self.image + second.image)

객체 지향 프로그래밍을 지원하는 대부분의 언어에서, 객체에 대한 참조는 각 메소드 호출로서 묵시적으로 전달됨.

그러나, Python에서 이 참조는 명시적으로 전달되어야 함. 즉, 여기서 __add__ 메소드의 첫 번째 매개변수를 self로 지정해야 함.

※ Python은 복소수 클래스를 지원하므로, 위의 예제 메소드는 설명 목적 이외에는 필요하지 않음.

 

● 클로저 (Closure)

부프로그램과 부프로그램이 정의된 참조환경.
참조환경은 부프로그램이 프로그램 내에서 호출될 때, 반드시 필요함.

 

- 정적 영역 프로그래밍 언어가 중첩 부프로그램을 허용하지 않는 경우

클로저는 필요 없으며, 이런 언어들은 클로저를 지원하지 않음. 왜냐하면, 그러한 언어에서 부프로그램의 참조환경에 있는 모든 변수(지역 + 전역)는 프로그램에서 부프로그램이 호출되는 장소에 관계없이 접근 가능하기 때문임.
즉, 부프로그램이 활성화되었을 때 참조환경이 항상 접근 가능하므로, 부프로그램과 참조환경을 묶은 개념인 클로저가 필요하지 않음.

 

- 언어가 중첩 부프로그램을 허용하는 경우

클로저가 필요함.

부프로그램이 중첩될 때, 부프로그램의 참조 환경은 지역 변수와 전역 변수 이외에도 그 부프로그램을 포함하는 부프로그램들에서 정의된 변수를 포함할 수 있음.

A의 참조환경

모든 포함 영역이 활성이고 가시적인 장소에서 부프로그램이 호출된다면, 부프로그램의 중첩은 어떠한 문제도 야기하지 않음. (B가 실행되고 A가 실행되는 경우)

하지만, 부프로그램이 다른 곳에서 호출된다면 문제가 발생할 수 있음. (B가 실행되지 않고 A가 실행되는 경우)

예를 들어, 부프로그램이 매개변수로 전달되거나, 부프로그램이 변수에 배정되어 프로그램의 거의 모든 곳에서 호출되는 경우, 문제가 발생 가능함.

 

ㄴ 발생 가능한 문제

하나의 부프로그램이 그것을 포함하는 부프로그램이 종료된 이후에, 호출될 수 있음 (A가 B가 종료된 이후 호출 가능함)

만일, 이런 일이 발생한 경우, 호출된 부프로그램을 포함하는 (종료된) 부프로그램에서 정의된 변수는 해제되기 때문에, 호출된 부프로그램이 해제된 변수를 접근할 수 없는 문제가 발생함.

 

ㄴ 문제 해결방법

부프로그램이 호출되는 곳이 어느 곳이든 참조환경이 사용 가능해야 함.
이를 위해서는, 중첩 부프로그램에서 사용되는 변수에 대해서는, 그 변수가 정의된 부프로그램의 활성기간이 아니라, 전체 프로그램의 활성 기간이 그 변수의 존속기간이 되어야 함. 존속기간이 전체 프로그램의 활성 기간인 변수는 무제한 기간을 가진다고 표현함.
이것이 가능하기 위해서는, 그 변수들이 스택 동적이 아니라 힙 동적이어야 함.

 

ㄴ 지원하는 언어

정적 영역과 중첩 부프로그램을 허용하며, 부프로그램이 매개변수로 전달되는 것을 허용하는 언어들.
거의 모든 함수형 프로그래밍 언어와 대부분의 스크립트 언어.

명령형 언어 중에는 C#이 있음. (Python은 스크립트 언어에 포함됨.)

 

ㄴ JavaScript 클로저 예

아래 예제에서 클로저 = makeAdder 함수에서 정의된 무명함수 (return 되는 함수) + 무명함수의 참조환경 (변수 x와 y를 모두 접근가능한 참조환경)

function makeAdder(x) {
  return function(y) {return x + y;} ;
}

...

var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write("Add 10 to 20: " + add10(20) + "<br />");
document.write("Add 5 to 20: " + add5(20) + "<br />");

첫 번째 참조환경에서는 x는 10에, 두 번째 참조환경에서 x는 5에 바인드 됨.

위의 코드가 HTML 문서에 포함되고 브라우저에 디스플레이되면, 아래와 같은 출력을 가짐.

Add 10 to 20: 30
Add 5 to 20: 25

추가 그림 설명1

왼쪽은 add# 선언 시 스택 상황이고, 오른쪽은 add# 호출 시 스택 상황임.

선언할 때 존재했던 x가 호출할 때는 존재하지 않음.

실제로는 다음과 같음.

추가 그림 설명2 (메모리 공간)

힙 공간에 x를 저장해 두고, 스택에는 그 주소를 저장해 둠. 덕분에 나중에 호출되었을 때, 불러와서 사용할 수 있게 됨.

● 코루틴 (Coroutines)

특별한 종류의 부프로그램으로, 호출자와 피호출자가 대등한 위치를 가진다는 특징이 있음. 전통적인 부프로그램에서는 호출자와 피호출 프로그램이 주-종속 관계임. 이 때문에 코루틴 제어 구조는 대칭적 단위 제어 모델이라고도 불림.

코루틴은 코루틴 자체에 의해서 제어되는 다중 진입점을 가짐. 일반적인 부프로그램은 단일 진입점을 가짐.
코루틴의 두 번째 실행은 가끔 처음이 아닌 지점에서 시작함. 이런 이유로, 코루틴의 시작을 호출(Call) 대신에 리쥼 (Resume)이라고 부름.

또한, 코루틴은 활성화 사이에 상태 (변수 값)를 유지하는 방법을 가짐. 따라서, 코루틴은 과거 민감형인 정적 지역 변수를 가짐.
코루틴을 지원하는 현대 언어는 Lua가 유일함.

 

- 예시

sub co1() {
  ...
  resume co2();
  ...
  resume co3();
  ...
}

co1이 처음 리쥼될 때, 실행은 첫 문장에서 시작하여 "resume co2()"까지 실행하고 co2에 제어를 전달함.
다음에 co1이 리쥼될 때, 실행은 "resume co2()" 다음 첫 문장에서 시작함.
co1이 세 번째로 리쥼될 때, "resume co3()" 후 첫 문장에서 시작함.

 

코루틴 집합으로 해결할 수 있는 문제로는 카드 게임 등이 있음. (번갈아서 진행되는 턴제 게임류)

 

- 루프 없는 두 개 코루틴의 실행 순서

- 루프를 갖는 코루틴의 실행순서

 

반응형

댓글