PL Design Principle
PL 설계 원칙
PL 설계 기본 원칙
1. Efficiency (효율성)
2. Generality (일반성)
3. Orthogonality (직교성)
4. Uniformity (획일성)
기타 설계 원칙
1. Simplicity (간결성)
2. Expressiveness (표현력)
3. Preciseness (정확성)
4. Machine Independence (기계 독립성)
5. Security (안전성)
6. Extensibility (확장성)
7. Restrictability (제약성) & Subset(부분성)
8. Consistency (일관성)
Fundamental PL Design Principle (PL 설계 기본 원칙)
1. Efficiency (효율성)
- 효율적이어야 하는 대상으로 목적코드, 번역, 구현, 프로그래밍이 있다.
A. 목적코드의 효율성 : 효율적인 실행 코드를 생성할 수 있도록 최적화되어야 한다.
ex) Pascal에서 상수는 값 자체만을 표시해야 하며, 계산이 필요한 표현은 상수로 허용되지 않는다.
B. 번역의 효율성 : 적절한 크기의 번역기가 빠르게 번역할 수 있어야한다.
ex) Pascal : 1-Pass Compile (1단계의 언어 번역 단계)
ex) Modula-2 : 2-Pass Compile (2단계의 언어 번역 단계)
C. 구현의 효율성 : PL의 번역기가 효율적으로 설계될 수 있어야 한다.
ex) Algol 60 : 번역 알고리즘이 충분히 이해되지 못하고, 번역기 구현이 어려워 실제로 널리 사용되지 못했다.
D. 프로그래밍의 효율성 : 쉽고 빠르게 프로그램 작성이 가능해야 한다. (Writablilty(작성력)과 같은 개념)
- 언어의 표현력에 좌지우지되며, 언어가 지원하는 추상화 레벨과 일반성에 관계된다.
ex) 프로그래밍 효율성이 높은 언어 : Lisp, Prolog (간결한 문맥, 대부분의 계산을 실행 시간에 처리)
※ 일반성, 직교성, 획일성은 밀접한 관계에 있어, 서로 딱 떨어져 구분되지 않는다.
2. Generality (일반성)
- 관련이 있는 여러 개념들이 일반적인 하나의 개념으로 결합되어야 한다.
- 당연히 지원되어야하는 기능들이 정상적으로 작동되어야 한다.
- 일반성은 종종 간결성, 판독성, 신뢰성을 저하시킨다. (반비례 관계)
ex) C언어에서 포인터에 대한 여러가지 기능을 제공함으로써 Segmentation Fault가 빈번히 발생되게 되었다.
ex) Java는 Call by Address방식은 불허하고, Call by Reference방식을 채택하여 신뢰성, 판독성 문제를 해결했다.
ex) Pascal에서는 Aliasing(이명)과 위험을 줄이기 위해 본질적으로 포인터 기능을 제한했다.
A. 프로시저에 대한 일반성
ex) Pascal은 프로시저 선언, 프로시저 매개변수를 지원하지만 프로시저형 변수는 지원하지 않아 결국, 프로시저를 매개변수로 사용하지 못한다. (일반성 결여)
B. 배열에 대한 일반성
ex) Pascal에는 가변 배열 개념이 없고, Modula-2와 Fortran에서는 가변 배열 매개변수를 전달할 수는 있지만, 가변 배열 데이터형을 선언할 수는 없다. (일반성 결여)
ex) C와 Ada에서는 가변 배열이 제공된다. (일반성 확보)
C. 동등/배정 연산자에 대한 일반성
ex) 대부분의 PL에서는 동등 연산(=)과 배정 연산(:=)을 배열이나 레코드에 적용하지 못한다. (일반성 결여)
ex) Ada에서는 동등 연산과, 배정 연산 또한 배열, 레코드에 적용 가능하다. (일반성 확보)
D. 매개 변수에 대한 일반성
ex) Fortran에서 매개변수 전달 기법은 오직 Call by Reference 밖에 없다. (일반성 결여)
ex) Algol 68, C, Java, Ada는 전달 기법으로 Call by Value, Call by Reference 모두를 지원한다. (일반성 확보)
E. 상수에 대한 일반성
ex) Fortran에는 상수 이름 개념이 없고, 상수 표현에 식을 사용할 수 없다. (일반성 결여)
ex) Modula-2에는 상수 정의에 함수 호출을 포함할 수 없다. (일반성 결여)
ex) Ada는 일반적 상수 선언 능력을 완벽히 갖추고 있다. (일반성 확보)
3. Orthogonality (직교성)
- 서로 다른 Component가 공존 가능하고, 문제를 일으키지 않아야 한다.
- 모든 데이터형을 일관성있게 대할 수 있어야 한다.
- 비일반성 : 문맥과 관계없는 제한
- 비직교성 : 문맥에 의존하는 제한
ex) Algol 68의 중요 설계 목표 중 하나는 직교성 확보이며, 실제로 직교적인 언어의 가장 좋은 예시로 남아있다.
ex) Pascal은 리턴값으로 구조형을 허용하지 않는다. (직교성 결여)
ex) Pascal에서 파일은 프로시저 값으로 전달될 수 없고, 파일 변수에 대한 배당도 불허한다. (직교성 결여)
ex) C언어는 리턴값으로 배열형만 허용하지 않는다. (반직교적)
ex) Ada에서는 모든 데이터형을 리턴값으로 활용할 수 있다. (직교성 확보)
ex) Modula-2에서 크기가 다른 객체에 대한 배정이 허용되는 경우는 문자열 간 대입밖에 없다. (직교성 결여)
ex) C언어의 매개변수 전달 방법은 배열만 참조 전달 방식을 택하며, 나머지 모든 데이터형은 값 전달 방식을 취한다. (직교성 결여)
4. Uniformity (획일성)
- 다수의 문법에 중복으로 사용되는 기호들을 직관적으로 사용할 수 있어야 한다.
- 비조화 : 유사한 것들이 유사하게 동작되지 않거나, 유사하지 않은 것들이 유사하게 동작하는 경우를 의미한다.
- 비획일성은 특별한 문맥에서만 발생되고 구성자들간의 상호작용으로 볼 수 있으므로- 즉, 이랬다가 저랬다가 하면 획일성이 결여된 것으로 간주하면 된다.
ex) int형에 대한 덧셈 연산자와 float형에 대한 덧셈 연산자가 따로 존재한다면, 해당 PL의 획일성이 결여되었음을 의미한다.
ex) Pascal에서 whlie, if 문은 begin-end 구조를 취하는 반면, repeat 문은 begin-end 구조를 취하지 않는다. (획일성 결여)
ex) Pascal은 리턴값을 보낼 때, return문을 사용하지 않고, 배정 연산 기호(:=)를 이용한다. (획일성 결여)
\(\texttt{functionName := value;}\) : 함수 이름에 배정문을 사용할 경우, 해당 함수가 값을 리턴함을 의미한다.
ex) Pascal에서 \(\texttt{↑integer}\)는 정수형 포인터를, \(\texttt{x}\uparrow\)는 x가 지칭하는 값을 의미한다. (획일성 결여)
ex) C언어에서는 포인터(&연산자)와 포인팅 값(*연산자)에 각각 다른 연산자를 적용한다. (비조화 탈피)
ex) Modula-2는 ↑연산자 대신 \(\texttt{POINTER TO}\)를 사용한다. (비조화 탈피)
ex) Pascal과 Modula-2에서는 세미콜론이 Separator(문장 분리자)인 동시에 Terminator(종결자)로 사용된다. (비획일적, 비직교적(=다른 연산자와 기호가 겹친다))
Secondary PL Design Principle (PL 설계 기타 원칙)
1. Simplicity (간결성)
- PL은 간결한 형태로 이루어져 배우고 사용하기 쉬워야 한다.
- Pascal의 주요 설계 목적 중 하나이다.
- 직교성, 일반성, 획일성 모두는 간결성과 배치되는 성질이다.
- 과한 단순성은 알고리즘 작성에 방해가 되며, 표현력 결여, 과한 제한의 원인이 된다.
- 구성 요소의 수가 적은 것이 간결성을 보장하지는 않는다.
ex) Prolog와 Lisp는 적은 구성자로 이루어졌지만, 복잡한 실행 시간 시스템을 요구하기 때문에 컴파일러가 아닌 인터프리터를 통해 번역된다.
2. Expressiveness (표현력)
- 복잡한 과정, 구조를 표현하는데 용이해야 한다.
- Recursion은 표현력을 제고시키는 대표적인 기능이다.
ex) 표현력은 우수하지만 간결하지 못한 언어 : Lisp, Prolog, Algol 68
ex) 표현력과 간결성 모두를 확보한 C언어 표현 : \(\texttt{while (*s++ == *t++)}\)
3. Preciseness (정확성) = Definiteness (명확성)
- 언어는 정확한 정의를 통해 설계되어 구문이 어떻게 동작할지 예측이 가능해야 한다.
- 정확성은 곧 언어의 신뢰성, 번역기의 신뢰성으로 직결된다.
- 언어 지침서, 언어 보고서를 통해 정확성을 증명할 수 있다.
- ANSI, ISO와 같은 표준 협회에 표준으로 채용된 국제 표준 언어 버전은 정확하게 작동한다 할 수 있다.
4. Machine Independence (기계 독립성)
- 언어는 특정 기계에 의존되지 않고 독립적이어야 한다.
- 기계 독립성은 독립적 언어 정의를 통해 호환성을 제고시키는 역할을 한다.
- 유연한 메모리 할당 방법, 기계 구조와 독립적으로 정의된 데이터형을 사용함으로써 구현할 수 있다.
ex) Java는 정수, 실수형에 여러 데이터형을 정의하여 다양한 Bit수를 제공함으로써 기계 독립적 구조를 구현했다.
5. Security (안전성)
- 언어는 프로그래밍 오류를 적게 하고, 오류 발견이 쉬워야 한다.
- 안전성은 신뢰성, 정확성과 직결된다.
- 데이터형 분류, 데이터형 검사, 변수 선언 등의 기능으로 안전성을 확보할 수 있다.
- 때때로 안전성은 큰 복잡성을 야기하기도 한다.
ex) Lisp, Prolog와 같이 복잡한 문제를 수행하는 프로그램 혹은 다양한 데이터 처리를 위한 Generic(포괄) 기능 구현에서 정적 타입 검사, 변수 선언과 같이 안전성을 확보하는 기능은 복잡성을 야기한다.
ex) 함수형 언어 ML은 안정성을 확보하면서 최대한의 표현력, 일반성을 갖도록 설계되었다. (다중 형태의 객체를 허용, 선언을 요구하지 않으면서 정적 타입 검사를 진행)
6. Extensibility (확장성)
- 사용자가 언어의 특징을 추가할 수 있어야 한다.
- 컴파일러에 예약어를 추가하는 것부터 새로운 데이터형을 정의하는 것(class와 같은 기능), 라이브러리에 새로운 함수를 추가하는 것 까지 넓은 범위를 통칭하는 성질이다.
- 일반적으로, 명령형 언어는 완전한 확장성보다는 자료 추상화, 제어 추상화와 같은 방법을 통해 부분적인 확장을 허용한다.
ex) 함수형 언어 Lisp는 확장성이 뛰어나 새로운 기능을 추가하기 용이하다.
ex) 명령형 언어 C는 새로운 키워드를 추가하기 위해서는 설계 단계부터 고쳐나가야 한다.
7. Restrictability (제약성) & Subset(부분성)
- 언어에 대해 잘 모르더라도 효과적으로 프로그램을 작성할 수 있어야 한다.
- 언어는 자기 자신의 부분집합을 정의할 수 있어야 한다.
- 이로 인해, 프로그래머는 언어를 사용하기 위해 전체를 알아야 할 필요가 사라진다.
- 전체 언어 구현에 드는 비용을 줄일 수 있고, 필요한 부분만 선택해서 구현할 수 있게 된다.
ex) Pl/1의 부분언어들 : SP/1, SP/2, ... , SP/k
8. Consistency (일관성)
- 기존 표기나 규칙과의 상식적인 일관성을 확보해야 한다.
- Law of Least Astonishment(놀람 최소의 법칙)*에 근거해 설계되어야 한다.
- 언어는 배우고 이해하는 데 어려움이 없어야 한다.
- 일관성을 위해선 가능한 한 표준화된 특징과 개념을 가져야 한다.
ex) Algol 68은 표준화된 표기를 사용하지 않아 일관성이 떨어진다는 평을 받았다. ((\(\texttt{type}\) 키워드 대신 \(\texttt{mode}\) 키워드를 사용한 점)
* Law of Least Astonishment : 어떤 사항도 예상 밖으로 행동하거나 나타나서는 안된다는 법칙이다.
Problem and Solution (문제점과 해결책)
Diagnostic Compiler(진단 컴파일러) 혹은 Check-out Compiler(점검 컴파일러)
- 프로그램의 신뢰성을 높이는 수단이다.
- 진단 컴파일러는 프로그램이 작동하는 것을 확인하는 데 많은 시간을 소비하고, 큰 프로그램 실행 시 비효율적인 문제가 발생되어 실제 현장에서 전체 프로그램에 대한 진단 컴파일을 하기엔 무리가 있다.
ex) Cornell : PL/1의 진단 컴파일러로, 프로그램이 정상적이면 실행시키고, 그렇지 않다면 구문 오류에 대한 자세한 오류 메세지를 출력하거나, 정정 작업을 시도한다.
ex) C언어 IDE가 제공하는 Debugger : 제어 흐름을 통제해 변수의 값이 변하는 추이를 확인할 수 있게 하는 등 여러가지 기능들이 제공된다.
Separated Compile(분리 컴파일)
- 효율적인 번역을 가능하게 하나, 오류를 유발할 수 있다.
- 한 사람이 S/W 시스템의 한 부분을 독립적으로 개발하여 타인이 개발한 부분과 조립될 수 있게 한다.
- Fortran, Cobol과 같은 초기 고급언어에 처음으로 도입되었다.
Intergrated Compiler(통합 컴파일러)
- 70년대 신뢰성이 강조되던 시기에 등장한 개념이다.
- 재컴파일할 때 이미 번역된 부분도 다시 컴파일한다는 단점이 있다.
ex) Ada는 코드를 Specification Part(헤더 부분) + Body Part(정의 부분)으로 나누어 분리 컴파일과 통합 컴파일 방식의 장점을 취합했다.
Optimizing Compiler(최적화 컴파일러)
- 효율적인 목적코드를 만들 수 있다.
- 컴파일 비용이 높다.
※ 일반 컴파일러로 Loop와 같은 작은 부분에 한해서 최적화를 수행하는 것이 위험성도 적고 얻는 효과는 크게 하는 방법이다.
※ 실제 컴파일러에서는여러 최적화 단계를 거친다.
신뢰성
- 언어 구문의 과한 간결함과 생략은 프로그램의 판독성을 저해한다.
- 적절한 수준의 간결성은 프로그램의 신뢰성을 제고한다.
PL 설계를 위한 C. A. R Hoare의 충고사항
- 새로운 PL은 기존의 특성에서 새로 개발한 특성을 Condolidation(통합)하는 방식으로 개발된다.
1. 언어의 특정한 특성 고안
- 새로운 특징을 개발하는 설계자는 한 번에 한 가지 특징에만 짐중해야 한다.
- 설계한 특징이 기존 언어의 장점을 해치지 않으면서 언어의 단점, 불완전성을 해결해야 한다.
- 설계한 특징들이 어떻게 구현될지를 보여줘야 한다.
- 특징들의 사용법을 안내하기 위해 구체적 사용 예시가 있는 지침서를 작성해야 한다.
- 컴파일 시간에 발견되지 않는 것들을 주의깊게 살펴야 한다.
- 많은 예제 프로그램을 작성해봐야 한다.
- 사용되는 모든 특징들을 다른 방법들과 비교하여 평가해야 한다.
2. 새로운 언어 설계
- 타인에 의해 설계된 선택적인 많은 특징들을 숙지하고 있어야한다.
- 각 특징들 사이의 약간의 Inconsistency(불일치)나 중첩되는 부분을 조정할 수 있어야 한다.
- 새 언어의 영역, 목적, 응용 프로그램 범위, 복장섭, 크기의 확장 정도에 대한 명확한 개념을 가져야 한다.
- 새 언어를 실제로 구현하고 사용자 지침서를 초보자용, 고급용 두 가지로 제공해야 한다.
- 언어를 사용할 사용자들에게 공급하기 위한 정책, 판매 수단을 계획해야 한다.
- 설계 시, 실험해보지 못한 개념은 포함하지 않는다.
언어 개발 프로세스
1. 특정 응용 영역 선택
2. 가능한 한 작은 형태의 설계 위원회 구성
3. 명확한 설계 목적 수립
4. 관심있는 소수의 사용자들에게 설계된 언어를 공개
5. 언어 정의 수정
6. 모범 컴파일러 구축 혹은 언어 의미의 형식 정의
7. 언어 정의 재수정
8. 분명하고 간결한 언어의 지침서 작성
9. 상품 가치가 있는 컴파일러를 배포
10. 언어 사용 방법을 분명하게 설명하는 언어 Primer(입문서) 작성