(해당 강의노트는 Michael L. Scott의 [Programming Language Pragmatics: 4th edition] 책을 기반으로 작성되었습니다)
[ 03 Names, Scopes, and Bindings(2)] 3.3 Scope Rules
Table of Contents
3.3 Scope Rules
- 3.3.1 Static Scoping
- 3.3.2 Nested Subroutines
- 3.3.3 Declaration Order
- 3.3.4 Modules
- 3.3.5 Module Types and Classes
- 3.3.6 Dynamic Scoping
3.3 Scope Rules
Scope of a binding란?
- 바인딩이 활성화된 프로그램의 textual region
Statically scoped(lexically scoped)
- 대부분의 현대 언어들이 이에 해당
- 바인딩이 활성화된 프로그램의 부분이 컴파일 시간에 완전히 결정나는 경우
- C, Java, Python, C#, ...and so on
Dynamically scoped
- 런타임때 실행의 흐름에 따라 바인딩이 달라질 수 있는 경우
- 오래된 언어들이 이에 해당
- APL, Snobol, Tcl, ...etc
Scope of a binding
- 특정한 바인딩 없이 사용하는 것임
- 비공식적으로, 스코프란 어떠한 바인딩 변화도 없는 프로그램의 최대 영역을 의미함(or at least none are destroyed)
- 일반적으로 스코프는 모듈의 바디, 클래스, 서브루틴 혹은 구조화된 흐름 제어 선언문(조건문, 반복문)이며 때때로는 블럭이라고 부른다
Referencing environment
- 어떤 프로그램이 실행되고 있는 한 순간에서 활성화된 바인딩의 집합을 의미함
- static 혹은 dynamic scope rules에 의해 결정됨
In C : Example
- 서브루틴 진입시 새로운 스코프를 도입(introduce)
- 전역변수와 같은 이름을 가진 지역변수가 있다면 지역변수에 해당하는 바인딩을 생성하고, 전역변수에 해당되는 바인딩은 비활성화한다
- 즉, 함수내에서 전역변수와 이름이 같은 지역변수 a가 있을 때 a를 호출 시, 지역변수 a가 호출된다
- 해당 서브루틴 종료 시
- 지역변수를 위한 바인딩은 소멸되고, 숨겨져있던 모든 전역변수를 위한 바인딩은 재활성화 된다.
3.3.1 Static Scoping
Static scoping
- 컴파일 때 결정되어지는 네임과 오브젝트 사이에 있는 바인딩을 의미
- 런타임 시 흐름제어를 고려하지 않는 프로그램의 text 검사에 의함
- 일반적으로, 지정된 이름을 위한 "현재" 바인딩은 어떤 주어진 위치를 기준으로 가장 가깝게 둘러싸고있는 블럭 내에서 찾을 수 있다.
Early versions of Basic
- 오직 한개, 글로벌 스코프만 가짐
- 명백한 declaration이 없음
- declaration: 변수나 함수 이름이 스코프 영역에 보여주게 하는 것
스코프 룰은 pre-Fortan 90에서 더 복잡
- 전역, 지역 변수 사이의 구분
- 지역변수의 스코프는 해당 지역변수가 나타나는 서브루틴 내로 제한되어 있다 즉, 다른곳에서는 unvisible함
- 변수 선언은 선택사항
- 만약 변수가 선언되어 있지 않을 경우, 현재 서브루틴에 로컬이고, 만약 해당 이름이 I-N 혹은 real 로 시작한다면 각 정수,실수 타입으로 가정
- Fortan90 과 이를 계승하는 언어들은, 프로그래머가 implicit declaration을 turn off할 수 있었고, 비선언된 변수 사용 시 컴파일 에러 발생
fortran의 지역 변수의 생애주기는 서브루틴 안에서 한 번 실행되면 소멸한다.
- 즉, 함수 호출 시 함수 안의 지역변수는 호출 시마다 매번 새롭게 메모리 공간에 할당되고 소멸하는 것임
- fortran 뿐만 아니라 대부분의 언어에서 해당
save statement를 사용하여 프로그래머는 해당 변수를 저장 가능
- 다른 언어에서도 비슷하게 적용
- In C, static을 변수 앞에 붙여 사용 가능
- 스태틱 변수는 전역변수처럼 자동으로 0으로 초기화되어 사용
- 해당 save statement를 붙이게 되면 프로그램 전체가 종료될 때까지 해당 변수는 살아있음
- 라이프타임이 서브루틴 내부에서 프로그램 전체로 확장됨
- 단 해당 변수가 선언된 서브루틴이 실행되지 않을 경우 해당 변수는 inactive 상태임
- 서브루틴 실행 후 reactive 됨
3.3.2 Nested Subroutines
Nested Subroutines
: 함수 안에 또 함수를 정의하는 것
- Algol, Python등에 존재
- C 언어 계열은 안됨( C, C++, JAVA), 단 클래스 안에 클래스는 정의 가능
- Algol-family 언어들은 특정 스코프 안에 정의되어 있는 상수, 타입, 변수, 함수는 스코프 밖에서는 보이지 않음
- 선언된 영역 안에서만 사용 가능함
- 많은 언어들이 Nest Subroutine을 서로 내포할 수 있는 기능을 제공함
Scope rule & Closest Nested Scope rule
- Scope rule이란?
- 가장 가까운 영역에서부터 변수가 만들어진 스코프를 찾는것
- Closest Nested Scope rule이란?
- 선언을 통해서 변수 혹은 함수의 이름을 스코프 영역에 알렸을 때, 해당 이름은 선언된 해당 영역에서 사용될 수 있는 규칙
- 즉 해당 이름이 해당 영역에서 유효하다.
- 단, f()안에 정의되어 있는 함수 g()에 f()에 정의되어 있는 변수와 같은 이름의 변수가 있을 경우 f()에서의 같은 이름의 변수는 감춰지고 g()에서 선언한 변수의 이름만 사용하게 된다.
이름이 주어졌을 때, 현재 가장 가까운 스코프 내에서부터 선언된 이름에서부터 찾는다
- 만약 한 개만 존재할 경우, 그 이름에 대한 active binding으로 정의될 수 있음
- 만약 가장 가까운 스코프 내에 없다면, 그 다음으로 가까운 스코프로 이동하면서 이름을 찾는다
- 현재를 기준으로 바깥 쪽 스코프로 이동하며 찾음
많은 종류의 언어들이 빌트인 혹은 predefined object들과 수학적 함수, 다른 케이스의 타입들을 제공한다
- predefined 함수들은 바깥에 안보이는 영역에 선언되어있다.
- 프로그래머는 Predefined object와 같은 이름의 전역변수를 선언할 수 있다.
- 이 경우 현재 스코프와 가까운 프로그래머가 선언한 변수가 사용되고 바깥에 미리 선언된 객체는 감춰지게 된다.
- built-in 함수의 예 : I/O routines
- 다른 케이스의 타입 들 : char, integer,...and so on
같은 이름의 Nested declaration에 의해서 name-to-object binding은 숨겨질 수 있다.
- 같은 의미로 scope 안에 hole 이 생겼다고도 함
- 대부분의 언어에서는 숨겨진 해당 객체를 nested scope안에서 접근 불가하게 함
- 반대로 몇 언어에서는 프로그래머가 특정 키워드를 추가하여 접근이 가능하게 하기도 함
- C++ 에서는 namespace 를 사용하여 쓰기도 함
Access to Nonlocal Object
- Access to local objects
- 컴파일러가 frame pointer register를 현재 실행 중인 서브루틴의 프레임을 가리키게 함
- 해당 레지스터를 베이스 삼아 서브루틴 내의 displacement addressing, target code에 객체가 접근 가능하게 함
- What about objects in lexically surrounding subroutines?
- 런타임 시 해당 스코프와 상응하는 프레임을 찾아야 함
- 널이 나오기 전까지 스태틱 링크를 따라가 올라가서 프레임을 찾으면 됨
3.3.3 Declaration Order
- 객체 x가 코드 블럭 b 영역 내에 선언되었다고 가정해보자
- x의 스코프는 선언 전에 b의 포션에 포함된다
- If so can x actually be used in that portion of the code
- 코드
-
몇몇 언어에서는 가능하지만 또다른 몇몇 언어에서는 불가능한 선언 방식
// In C++ language, int main(){ int n = 0; n = x + 5; // Is it possible? int x = 3; }
-
C언어 안에서는 함수의 시작 첫 부분에서(함수 내에서 사용할 변수들을) 모든 변수들을 미리 선언해야 한다.
-
Early C language에서 해당, 현재는 가능
-
Algol 60, Lisp 등 초창기의 언어들
Pascal
- 사용되기 이전에 변수는 반드시 선언되어야 함
- 재귀 타입, 함수들을 수용해야 하기 때문에 해당 mechanism이 필요한것
-
but a forward reference(In pascal keyword) is a static semantic error
-
code : for functions(subroutine) and recursive types
// In C void g(); // need to prototype for g, void f(){ g(); } void g(){ f(); }
- 함수 안에 변수가 선언되었으면 함수 전체 블럭이 변수의 스코프가 된다
- whole-block scope, declare-before-use 규칙은 충돌할 수 있다
-
예시 코드 : N이 foo영역 전체가 스코프 영역이므로 첫번째 라인에서 선언한 const N = 10은 숨겨지게 되고,
N = 20의 N을 M = N에서 가리키게 된다. 따라서 초기화 전에 N을 사용하게 되므로 오류가 발생하게 된다.const N = 10; procedure foo; const // notion of start of constant scope M = N; // static semantic error! N = 20; // local constant declaration; hides the outer N
-
예시 코드
const N = 10; procedure foo; const M = N; (* static semantic error! *) var A : array[1..m] of integer N : real; (* hiding declaration *)
- Messages like "N used before declaration" & " N is not a constant" are not helpful
- what these message can not help? 보통의 사용자들은 해당 N이 첫번째 선언된 N이라고 생각하기 떄문
- 따라서, 파스칼 컴파일러의 경우 스코프 영역의 전체 변수를 확인해봐야 한다.
- 표준 파스칼은 5번을 대부분 따르나, Pascal 계승 언어들은 식별자가 선언된 전체 영역을 스코프로 잡지 않고, 선언된 이후부터 블럭의 끝까지만 스코프 영역으로 설정한다.
- 6번처럼 Ada, C, C++, Java에서 해당 방식을 차용
C++, JAVA는 해당 규칙 완화
-
두 언어 다 클래스의 멤버 변수들을 클래스의 메소드 이후에 선언해도 같은 클래스 내에서는 접근 가능하게 해줌
// In Java class Test{ void Test(){ System.out.println(T); } private int T; }
-
또한 자바의 경우 클래스 상속과 관련해서 부모 클래스와 자식 클래스의 위치 상관없이 선언 가능하다
class Child extends Parents{ .... } class Parents{ .... }
-
단, C#은 파스칼의 방식을 아직 사용
Modular-3
- declaration order에 접근하는 가장 간단한 방법
- declaration의 스코프는 해당 declaration이 나타나는 전체 블럭이다
- declaration의 순서는 문제가 되지 않는다.
파이썬도 파스칼과 마찬가지로 whole block scope rule을 따름
-
컴파일 오류의 예시 코드
def T(): x = 3 print(x) // print 3 def S(): # print(x) # 이 경우 T()에서 선언된 x가 아닌 s()에서 선언된 x가 사용됨, 따라서 주석처리해야 컴파일 오류 발생x x = 5 print(x) // print 5 s() print(x) // print 3
-
지정자 사용 예시 코드
x = 3 def T(): global x print(x) // print 3 x = 5 print(x) // print 5
Declarations and Definitions
-
recursive type, subroutine 의 경우 서로 각 함수를 호출하기 전에 이미 앞에 호출하려는 함수가 정의되어 있어야 함
- In C, use Proto type -> declaration
// In C,
#include <stdio.h>
void func(); //proto type
int main(void){
func();
return 0;
}
void func(){
printf("call the func() \n");
}
-
C, C++ 에서는 선언(Declaration)과 정의(Definition)를 구분한다
-
선언(Declaration)
-
void f();
-
이름을 짓고 해당 스코프내에서 나타내는 것
-
return, function의 name, argument의 정보를 담고 있음
-
세부적인 함수 내용은 이후 '정의' 부분에서 함
-
-
정의(Definition)
-
void f(){ ....; }
-
실제 구현한 내용으로, 디테일하게 들어있음
-
-
-
만약, 선언이 정의라고 할만큼 충분하지 않을 경우, 뒤에서 또 다른 정의가 나올 수 밖에 없다
-
예시 코드
- 구조체의 경우
struct manager; /* declaration only */
struct employee {
struct manager* boss; // manager가 정의되기 전에 사용하므로 위에서 선언이 필요함
struct employee* next_employee;
…
};
struct manager { /* definition */
struct employee* first_employee;
…
};
- 함수의 경우
void list_tail(follow_set fs); /* declaration only */
void list(floow_set fs) {
switch (input_token) {
case id: match(id);
…
}
void list_tail(follow_set fs) { /* definition */
switch (input_token) {
case comma: match(comma); list(fs);
…
}
-
Nested Block
-
Algol 60, C89 등 많은 언어에서는 지역 변수를 서브루틴의 시작 부분에서만 선언할 수 있음
-
반대로, Algol 68, C99, C 계열의 모든 언어들은 더 유연성있게, 변수 선언을 서브루틴 어디서나 사용할 수 있다.
- 단, 해당 변수가 사용되기 전에 선언되어야 함
-
대부분의 언어에서 nested declaration은 동일 이름의 바깥쪽에 선언된 변수를 감추게 된다
-
JAVA, C#은 함수안에 똑같은 이름을 가진 변수를 선언할 경우 static semantic error 발생
-
예시 코드
-
-
// Java
class Test {
int x = 7;
public void f() {
int x = 5;
{
// int x = 7; // compile error
System.out.println("1. x = " + x);
}
System.out.println("2. x = " + x);
}
}
- 예시 코드
// IN C
{
int temp = a;
a = b;
b = temp;
}
- C언어에서는 nested blocks안에 선언된 변수들을 유용하게 사용할 수 있음
- 임시 선언을 사용하는 code에게 lexically adjacent하게 유지하는 것은 아래와 같은 이점이 있음
1. 프로그램의 가독성을 높임
2. 해당 코드가 또 다른 temp라는 이름을 가진 변수를 간섭할 가능성을 제거함
- nested block안에서 선언된 변수들을 위해 공간을 할당하거나 비할당하는 것은 런타임 시간때 이루어지는 것이 아님
- 서브루틴이 시작할 때 공간이 할당되며, 종료시 비할당된다.
3.3.4 Modules
-
Modularization 즉, 모듈화는 프로그램이 해당 부분을 필요해하기 전까지는 최대한 감추는, information hiding(은닉성)의 특성을 가진다
-
제대로 모듈화가 되어 있는 코드는 이해해야 하는 정보 부분을 최소화 시켜서 프로그래머의 "cognitive load"(인지부하)를 줄여준다
- cognitive load를 줄인다 : 머리로 생각해야 하는 부분을 줄여줌
-
잘 디자인된 프로그램은 최대한 간단하게 인터페이스가 만들어져 있으며, 변경될 수 있는 부분은 싱글 모듈 안에 감추어져있다.
-
SW 유지보수에 information hiding은 매우 중요
- 인지부하를 줄여준다
- 이름 충돌의 위험을 줄여준다
- Safeguards the integrity(응집성) of data abstactions
- 데이터 추상화의 응집성을 통해 데이터를 보호할 수 있음
- 런타임 에러 compartmentalize(구획화)
- 오류가 발생 할 경우 특정 모듈내로 한정지어준다
- compartmentalize : to separate something into parts and not allow thoes parts to mix together
-
데이터와 서브루틴의 Encapsualting
- nested subroutine에 의해 제공되는 information hiding은 nested subroutine을 포함하고 있는 서브루틴에 포함되어 있는 객체와 같은 생애주기를 가지고 있다는 한계를 가지고 있다
- 즉 nested subroutine의 생애주기는 해당 서브루틴을 포함하고있는 서브루틴의 생애주기를 따른다
- 서브루틴에 의해 반환될 경우, 지역변수는 더 이상 메모리에 남아 있지 않은다
- 지역변수에 저장되어 있는 값들은 버려짐
- 해당 문제의 부분적 해결 방법
- Fortan의 save statement와, C, Algol의 static변수를 사용
- 싱글 서브루틴 추상화를 빌드하는 것이 가능
- 한 개 이상의 서브루틴으로 구성되는 것이 필요한 인터페이스에서 추상화의 구조를 허락하지 않음
- Fortan의 save statement와, C, Algol의 static변수를 사용
- 한 개 이상의 함수를 사용하는 추상화의 예
- 랜덤 숫자를 발생시키는 함수를 생각해보자
- 시드 넘버가 같을 경우 무작위 숫자가 같은 패턴으로 계속 나옴
- 따라서 시드 넘버를 다르게 주어야 함
- 보통 현재 시각(계속 바뀌는 값)을 시드넘버로 줌
- 다음 랜덤 넘버를 결정지어주는 생성자의 상태를 rand_int, set_seed를 보여주게 하고 싶지만, 프로그램의 나머지 부분으로부터 이를 숨기고자 한다.
- 많은 언어들은 module construct의 사용을 통해 이러한 목표를 달성할 수 있음.
- 랜덤 숫자를 발생시키는 함수를 생각해보자
- Modules as Abstactions
- 모듈은 서브루틴, 타입, 값 등의 객체들을 encapsulate를 가능하게 함
- 모듈 안의 객체들은 서로 볼 수 있음
- 단, export 되어 있지 않은 한, 모듈 안쪽에 있는 객체들은 바깥쪽에서는 볼 수 없음
- 모듈 바깥에 있는 객체들이 안쪽에서는 보이지 않을 수 있음
- export, import 표현 방식은 언어마다 다 다름
- 객체의 가시성(visibility에만 영향을 미침
- 그것이 수반하고있는 객체의 lifetime에는 영향을 미칠 수 없음
- 1970년대 후반과 1980년대 초반에 핫한 키워드가 바로 모듈
- 다른 언어에서는 모듈대신 패키지로 사용하였음
- 자바에서는 9버전 이후부터 패키지 -> 모듈이 추가되었음
- C++, C#은 namespace를 사용
- C언어에서는 static variable, function등을 사용해서 separate compilation을 구현하면 일종의 모듈화
- 모듈은 1970년대 후반과 1980년대 초반의 principal language 혁신 중 하나
- 이는 Modula1,2,3 , Turing, Ada 83, Clu 언어들에서 나타남
- 더 최근 형태로는 Haskell, C++, Java, C# 등의 주요 스크립트 언어에서도 나타남
- Ada, Java, Perl등의 몇몇 언어에서는 모듈 대신 package라는 용어로 대신 사용함
- Java9 버전 이후부터는 모듈 개념이 추가됨
- C++. C#, PHP에서는 namespace 사용
- 모듈은 별도의 C의 compilation facility를 이용해 어느정도 emulation할 수 있음
- emulate : to copy something achieved by someone else and try to do it as well as they have
- 모듈은 서브루틴, 타입, 값 등의 객체들을 encapsulate를 가능하게 함
- nested subroutine에 의해 제공되는 information hiding은 nested subroutine을 포함하고 있는 서브루틴에 포함되어 있는 객체와 같은 생애주기를 가지고 있다는 한계를 가지고 있다
-
Pseudorandom number generator module in C++
- 코드
// Pseudorandom number generator module in C++
#include <time.h>
namespace rand_mod {
unsigned int seed = time(0);
const unsigned int a = 48271;
const unsigned int m = 0x7fffffff;
void set_seed(unsigned int s) {
seed = s;
}
unsigned int rand_int() {
return seed = (a * seed) % m;
}
}
- namespace 내부에 만들어진 name의 바인딩은 부분적으로 혹은 전체적으로 바깥에서는 숨겨져 있지만 destroy된 것은 아니다
- C++의 경우, lexical nestring의 가장 바깥에서만 namespace가 나타날 수 있다.
- integer seed는 해당 값이 오직 set_seed, rand_int에서만 보인다고 해도 프로그램의 전체 실행 동안 유지하고 있어야 한다.
- C++의 경우, rand_mod namespace의 바깥에서 set_seed, rand_int에 접근하는 것을 다음과 같은 표기로 접근하는 것을 허용
: rand_mod::set_seed, rand_mod::rand_int
- seed 변수도 rand_mode::seed로 접근할 수 있지만 변수를 이렇게 접근하는 것은 그닥 좋은 접근방식이 아님
- 또한 아래와 같이 직접적인 방식으로 name-by-name basis 방식을 사용하면 prefix선언을 하지 않아도 됨
using rand_mod::rand_int;
…
int r = rand_int();
- 대안적으로, 네임의 전체 세트를 선언해주는 방식도 있음(이 방법이 제일 자주 사용됨)
using namespace rand_mod;
…
set_seed(12345);
int r = rand_int();
- 단 이 방법의 경우, 모듈의 이름이 전체 노출되는 것이므로 import된 context의 이름들과의 충돌 가능성을 높이고 논리적으로 private한 모듈의 seed와 같은 객체에 의도치 않게 접근하게 되는 가능성을 높인다. 즉, 좋지 않은 방법임
-
Imports and Exports
- 일부 언어들은 모듈에서 어떤 것을 사용할지에 대해 제한적인 방법 내에 export 할 수있음
- 변수를 read-only로 export
- 타입을 opaquely(<-> transperantly) 하게, 즉 불투명하게 Export
- 선언된 변수의 타입
- 모듈의 서브루틴으로 인자를 넘김
- 다른것과 비교되거나 할당될 수 있지만 어떠한 방법으로도 수정(조작)되면 안됨 : read-only이므로
- closed scopes
- Names은 반드시 명백하게 import되어야 함
- Modular, Haskell
- open scopes
- import가 가능하지 않음
- selectively open modules
- names은 자동적으로 export되나 모듈 이름이 qualify할 때만 바깥에서 사용가능하다.
- 단, 해당 네임이 다른 스코프에서 importe되지 않는 한 가능
- C++, JAVA
- modular 2 예제
- 일부 언어들은 모듈에서 어떤 것을 사용할지에 대해 제한적인 방법 내에 export 할 수있음
-
Moduels as Managers
- 모듈은 서브루틴에서 private하게 만들어지는 데이터를 허락하는데, 이를 사용하는 추상화 구조를 허용한다.
- 단, 각 모듈은 single abstraction으로 정의되어야 함
- 이전 예시 코드에서는 스택의 인스턴스를 여러 개 만드는 것과, 하나 이상의 pseudorandom number generator를 만드는 것을 허락하지 않음
- 해결방법
- 모듈코드를 복사하고 새로운 이름을 붙임
- 내부구조를 바꿔서 여러 개를 관리하는 것으로 추상화 구조를 바꾸기
- 모듈을 타입으로 처리, 일종의 클래스라고 보면 됨
- Pseudorandom generator manager example
- 만약 여러개의 generator를 만드는 것을 원할 경우, generator type의 인스턴스를 위해 "manager"라고 불리는 모듈로부터 export할 수 있는 namespace를 만들면 된다.
- manager idiom 은 추가적으로 생성과 초기화할 수 있는 서브루틴과 파괴 가능한 generator instances를 필요로 한다.
- 또한 모든 서브루틴에서 generator를 question안에서 구체화 할 수 있는 추가 파라미터를 취할 수 있어야 한다
// Pseudorandom number generator module in C++
#include <time.h>
namespace rand_mgr {
const unsigned int a = 48271;
const unsigned int m = 0x7fffffff;
struct generator {
unsigned int seed;
};
generator* create() {
generator* g = new generator;
g->seed = time(0);
return g;
}
void set_seed(generator* g, unsigned int s) {
g->seed = s;
}
unsigned int rand_int(generator* g) {
return g->seed = (a * g->seed) % m;
}
}
using rand_mgr::generator;
generator* g1 = rand_mgr::create();
generator* g2 = rand_mgr::create();
…
using rand_mgr::rand_int;
int r1 = rand_int(g1);
int r2 = rand_int(g2);
3.3.5 Modules Types and Classes
1. Euclid, ML 등에서 나타나는 multiple instance problem은 각 모듈을 타입으로 다루는 것이 대안적인 해결책이 될 수 있음
- simple encapsulation construct보다 나음
2. 객체지향의 클래스는 모듈 타입의 확장이라고 보면 된다
- module instance에 접근하는 것은 일반적으로 객체에 접근하는 것으로 보인다
generator* g = rand_mgr::create();
…
int r = rand_int(g);
- 이는 아래 코드로 대체될 수 있음
rand_gen* g = new rand_gen();
…
int r = g->rand_int();
- Pseudorandom number generator class in C++
class rand_gen {
unsigned int seed = time(0);
const unsigned int a = 48271;
const unsigned int m = 0x7fffffff;
public:
void set_seed(unsigned int s) {
seed = s;
}
unsigned int rand_int() {
return seed = (a * seed) % m;
}
};
3. Object Orientation
- 모듈 타입과 클래스간의 차이점은?
- 클래스는 상속과 dynamic method dispatch를 지원함
- 상속을 활용하여 기존의 클래스를 활용하여 확장 함수를 만들 수 있음
- dynamic method dispatch는 기존의 부모클래스에 있던 함수를 오버라이드해 재정의할 수 있음
- polynomial, 다형성
- 클래스는 상속과 dynamic method dispatch를 지원함
4. Modules Containg Classes
- C++, Java, C#, Python
- class는 모든 경우의 모듈을 대체할만한 적절한 방법은 아님
- FPS(일인칭 게임)게임을 개발한다고 가정해보자
- classe는 characters, possessions, buldings, goals 그리고 다른 추상 데이터의 주인이 될 것이다
- 게임은 보통 큰 프로젝트이므로 많은 프로그래머들이 제작하게 됨
- 즉, 하나로 클래스를 묶어서 하기에는 큰 프로그램이므로 오리지널 모듈의 의미를 사용해서 개발해야 됨
5. 자바의 모듈
- 자바 9부터 지원
- 패키지들을 묶어서 추상화시켜 관리할 수 있는 방법
- 패키지는 관련 있는 클래스들을 묶어서 관리할 수 있도록 했고, 그 안에서 Private, protected, public등의 가시성을 부여
- 패키지 내부 또는 외부에서 사용할 수 있는 클래스를 구별
- 추상화와 캡슐화 때문
- 패키지의 특성이 오히려 추상화나 캡슐화를 해치는 경우가 발생함
- 예: sun.misc.BASE64Encoder / Base64Edcoder 클래스
- 모듈은 내부적으로 사용하려고 만든 패키지들의 public 클래스들이 모두에게 공개되는 것을 막을 수 있음
6. 기존 패키지 대비 모듈의 장점
- public 클래스를 숨길 수 있으므로 강한 캡슐화 지원
- 모듈 단위로 추상화와 캡슐화를 지원함으로써 패키지 간의 관계를 모듈 단위로 정리할 수 있어 복잡한 의존성을 줄일 수 있음
- 모듈은 컴파일된 자바 코드 외에 이미지 파일이나 xml파일 같은 다른 리소스 파일들을 포함시킬 수 있음
- 응용 프로그램을 모듈에 넣는 것보다는 자주 사용될 수 있는 라이브러리를 구축할 때 더 유용함
- 즉, 작은 응용 프로그램을 만들 때에는 모듈을 사용하지 않는 편이 나음
- 라이브러리를 만들 때 모듈을 사용하는 것이 더 바람직
3.3.6 Dynamic Scoping
- 다이나믹 스코핑 : 객체와 이름간의 바인딩이 런타임 때 실행되는 순서에 따라서 해당 변수의 사용 여부를 알 수 있는 경우
- Dynamic scope rules
- "current" 바인딩은 실행 중에 가장 최근에 마주친 이름으로 해당 스코프에서 복귀해도 아직 사라지지 않은 것
- 다이나믹 스코핑을 지원하는 언어
- APL, Snobol, Tcl, TeX, early dialects of Lisp, Perl
- 다이나믹 스코핑을 지원하는 언어의 경우 인터프리터 방식을 채택하는 경우가 많음
- Static vs. Dynamic Scoping
- static의 경우 해당 변수가 스코프 내에 없을 경우 가장 가까운 바깥쪽부터 같은 이름의 변수를 찾음
- Dynamic Scoping의 경우 실행 중에 만났던 가장 최근의 변수를 찾음
- C, C++, Java 의 경우 Static scoping에 해당
- 예시
x : integer –global
procedure set_x(n : integer)
x := n
procedure print_x()
write_integer(x)
procedure first()
set_x(1)
print_x()
procedure second()
x : integer
set_x(2)
print_x()
set_x(0)
first()
print_x()
second()
print_x()
1. Static scope일 경우 1122를 출력 : 변수들이 선언된 스코프내에서만 live
- set_x(0) : 해당 변수가 스코프 내에 없으므로 가장 바깥쪽의 전역변수 x에 0을 저장
- first() : 전역변수 x에 1을 저장하고 이를 출력
- print_x() : 전역변수 x에 저장된 값 1을 출력
- second() : 전역변수 x에 2를 저장하고 이를 출력
- print_x() : 전역변수에 x에 저장된 값 2를 출력
2. Dynamic Scope일 경우 1121을 출력 : 함수를 어디서 호출하였는지에 따라 상위 scope를 결정
- set_x(0) : 전역 변수 x에 0을 저장
- first() : 전역변수 x에 1을 저장하고 이를 출력
- print_x() : 전역변수 x에 저장된 값 1을 출력
- second() : 실행 중에 만난 가장 최근 변수인 second()내의 지역변수 x에 2를 저장하고 이를 출력
- print_x() : second()종료 시 지역변수 x는 더 이상 메모리 공간에 존재하지 않음. 따라서 전역변수 x에 저장된 값 1을 출력
'Computer Science > 프로그래밍언어론' 카테고리의 다른 글
[Programming Language Pragmatics] 06 Control Flow(2) (2) | 2020.12.06 |
---|---|
[Programming Language Pragmatics] 06 Control Flow(1) (0) | 2020.10.25 |
[Programming Language Pragmatics] 03 Names, Scopes, and Bindings(3) (0) | 2020.10.23 |
[Programming Language Pragmatics] 01 Introduction (0) | 2020.10.19 |
[Programming Language Pragmatics] 03 Names, Scopes, and Bindings(1) (3) | 2020.09.11 |