본문 바로가기

수업 기록/(21-2)프로그래밍언어구조론

[프로그래밍 언어 구조론]제 2장. 언어 설계 원칙


2. 언어 설계 원칙

  • 학습목표
    • 좋은 언어를 이해하기 위한 기본 원칙에 대해 알아본다
      • PL 디자인 기준의 역사
      • 효율성과 규칙성
      • 보안 및 확장성
      • 사례 연구: C++/파이썬

좋은 언어란?

  • 언어설계(언어디자인)
    • 좋은 언어의 바람직한 속성은 무엇입니까?
  • 인생사 새옹지마~~
    • 파스칼 성공, Modula-2 실패
    • Algol60 성공, Algol68 실패
    • FORTRAN 성공, PL/I 실패
  • 성공언어의 사연들
    • 화용: 번역가의 가용성, 가격, 품질
    • 킬러 애플리케이션: Unix for C, Internet for Java, …
    • 디자인 컨셉의 통일성(필요조건)

배경

  • 다음 기준 중 하나라도 충족하면 언어가 성공한 것으로 정의됩니다.
    • 그것은 디자이너의 목표를 달성합니다
    • 응용 분야에서 널리 사용됩니다.
    • 성공적인 다른 언어의 모델 역할
  • 새로운 언어를 만들 때 전체적인 목표를 정하고 디자인 과정 내내 염두에 두십시오.
    • 이것은 특수 목적 언어에 특히 중요합니다.
    • 대상 응용 프로그램 영역에 대한 추상화는 언어 디자인에 구축되어야 합니다.
  • 이 장의 내용
    • 몇 가지 일반적인 설계 기준을 소개합니다.
    • 설계자에게 잠재적인 도움으로 세부 원칙을 제시합니다.

설계기준 변천사 (1/2)

  • 초기에는 기계가 매우 느리고 메모리가 부족했습니다.
    • 프로그램 속도와 메모리 사용량이 주요 관심사였습니다.
  • 실행 효율성: 기본 설계 기준
    • 초기 FORTRAN 코드는 기계 코드에 다소 직접 매핑되어 컴파일러에 필요한 번역량을 최소화합니다.
  • 쓰기 가능성(Writability): 프로그래머가 계산을 명확하고 정확하며 간결하고 빠르게 표현할 수 있도록 하는 언어의 품질
  • 초기에는 쓰기 가능성이 효율성보다 덜 중요했습니다.
    • Algol60은 논리적으로 명확하고 간결한 방식으로 알고리즘을 표현하도록 설계되었습니다: 통합된 블록 구조, 구조화된 제어문, 보다 구조화된 배열 유형 및 재귀
    • COBOL은 프로그램을 일반 영어처럼 보이도록 하여 프로그램의 가독성을 높이려고 했습니다. 이것은 그것들을 길고 장황하게 만들었습니다

설계기준 변천사 (2/2)

  • 1970년대와 1980년대 초반에는 신뢰성과 함께 단순성과 추상화가 강조되었습니다.
    • 번역가가 번역 전에 프로그램의 정확성을 부분적으로 증명할 수 있는 메커니즘과 함께 언어 구성에 대한 수학적 정의가 도입되었습니다.
    • 이는 강력한 데이터 타이핑으로 이어졌습니다.
  • 1980년대와 1990년대에는 논리적 또는 수학적 정확성이 강조되었습니다.
    • 이것은 함수형 언어에 대한 새로운 관심으로 이어졌습니다.
  • 지난 25년 동안 가장 영향력 있는 디자인 기준은 추상화에 대한 객체 지향 접근 방식입니다.
    • 라이브러리 및 기타 OO 기술을 사용하여 기존 코드의 재사용성을 높였습니다.
  • 효율성의 초기 목표 외에도 거의 모든 설계 결정은 여전히 ​​가독성, 추상화 및 복잡성 제어를 고려합니다.

효율성(1/2) Efficiency

  • FORTRAN의 주요 목표, 여전히 C/C++에서 중요한 목표
  • 효율성 Efficiency : 일반적으로 대상 코드의 효율성을 생각합니다.
    • 컴파일 시간에 적용되는 강력한 데이터 유형(Strong data typing)은 런타임에서 작업을 실행하기 전에 데이터 유형을 확인할 필요가 없음을 의미합니다.
    • 초기 FORTRAN에서는 실행 시작 시 메모리 공간을 한 번 할당할 수 있도록 모든 데이터 선언과 서브루틴 호출을 컴파일 시간에 알려야 했습니다.
  • 실행 효율성을 돕는 기능
    • 정적 데이터 유형 Static data types 은 효율적인 할당 및 액세스를 허용합니다.
    • 수동 메모리 관리 Manual memory management 는 "가비지 수집"의 오버헤드를 방지합니다.
    • 간단한 의미 Simple semantics  는 실행 중인 프로그램의 간단한 구조를 허용합니다.

효율성(2/2) Efficiency

  • 프로그래머 효율성: 얼마나 빠르고 쉽게 읽고 쓸 수 있습니까?
    • 구문의 간결함은 프로그래머 효율성에 기여합니다.
    • Python은 중괄호나 세미콜론이 필요하지 않으며 들여쓰기와 콜론(:)만 필요합니다.
  • 표현력 Expressiveness : 복잡한 과정과 구조를 얼마나 쉽게 표현할 수 있습니까?
  • 프로그램의 신뢰성 Reliability 은 효율성 문제가 될 수 있습니다.
    • 신뢰할 수 없는 프로그램은 진단하고 수정하는 데 프로그래머의 시간이 필요합니다.
  • 프로그래머 효율성은 오류를 얼마나 쉽게 찾고 수정할 수 있는지에 따라 영향을 받습니다.
  • 약 90%의 시간이 프로그램 디버깅 및 유지 관리에 사용되기 때문에 유지 관리성 maintainability 은 PL 효율성의 가장 중요한 지표가 될 수 있습니다.

규칙성(1/2) Regularity

  • 언어의 기능이 얼마나 잘 통합되었는지
  • 더 큰 규칙성은 다음을 의미합니다.
    • 특정 구성의 사용에 대한 제한 감소
    • 구성 간의 이상한 상호 작용 감소
    • 언어 기능이 작동하는 방식에서 일반적으로 덜 놀라움
  • 규칙성의 기준을 만족하는 언어는 최소 놀라움의 원칙을 고수한다고 합니다.
  • 규칙성은 세 가지 개념으로 세분될 수 있습니다.
    • 일반성–Generality
    • 직교 설계–Orthogonal design
    • 획일성–Uniformity

규칙성(2/2) Regularity

  • Generality 일반성:
    • 구성의 가용성이나 사용에 있어 특별한 경우를 피하고 밀접하게 관련된 구성을 하나의 보다 일반적인 구성으로 결합함으로써 달성
  • Orthogonal design 직교 설계:
    • 예상치 못한 제한이나 동작 없이 의미 있는 방식으로 구성을 결합할 수 있습니다.
  • Uniformity 획일성:
    • 비슷한 것은 비슷해 보이고 의미는 비슷하지만 다른 것은 다르게 보이는 디자인
  • 다음 3개 중 하나가 없으면 기능 또는 구성을 불규칙한 것으로 분류할 수 있습니다.

Generality(일반)의 예

  • Procedures and functions 절차 및 기능
    • 파스칼은 함수와 절차를 중첩하고 다른 함수와 절차에 매개변수로 함수와 절차를 전달할 수 있지만 변수에 할당하거나 데이터 구조에 저장할 수는 없습니다.
  • Operators 연산자
    • C에서는 ==로 두 구조를 직접 비교할 수 없습니다. 따라서 이 연산자는 일반성이 부족합니다.
  • Constants 상수
    • Pascal은 상수에 할당된 값을 표현식으로 계산하는 것을 허용하지 않는 반면 Ada는 완전히 일반적인 상수 선언 기능을 가지고 있습니다.

Orthogonality(직교)의 예

(언어 구성은 다른 컨텍스트에서 다르게 작동하지 않아야 합니다.)

  • 함수 반환 유형
    • 파스칼: 스칼라 또는 포인터 유형만
    • C, C++: 배열 유형을 제외한 모든 데이터 유형
    • Ada, Python: 모든 데이터 유형
  • 변수 선언의 배치
    • C: 지역 변수는 블록의 시작 부분에서만 정의됩니다.
    • C++: 사용 전 블록 내부의 임의 지점에서의 변수 정의
  • 기본참조 유형
    • Java: 기본 유형은 값 의미론 value semantics  을 사용하고(할당하는 동안 값이 복사됨), 객체 유형(또는 참조 유형)은 참조 의미론 reference semantics을 사용합니다(할당은 동일한 객체에 대한 두 개의 참조를 생성함).
  • 직교성 Orthogonality 은 Algol68의 주요 설계 목표였습니다.
    • 여전히 모든 의미 있는 방식으로 구성 요소를 결합할 수 있는 언어의 가장 좋은 예입니다.

Uniformity(통일성)의 예

(언어 구성의 모양과 행동의 일관성)

  • 추가 세미콜론
    • C++는 클래스 정의 뒤에 세미콜론이 필요하지만 함수 정의 뒤에는 세미콜론 사용을 금지합니다.
  • 할당을 사용하여 값 반환
    • 파스칼은 할당문에서 함수 이름을 사용하여 함수의 값을 반환합니다.
      • 표준 할당문처럼 혼란스럽게 보입니다.
    • 다른 언어는 return 문을 사용합니다.

불규칙의 원인 Causes of Irregularities

  • 많은 불규칙성은 언어 디자인의 어려움에 대한 사례 연구입니다.
    • C++의 추가 세미콜론 문제는 C와 호환되어야 하는 필요성의 부산물이었습니다.
    • Java의 기본 유형 및 참조 유형의 불규칙성은 효율성에 대한 디자이너의 관심의 결과입니다.
  • 특정 목표에 너무 집중할 수 있음
    • Algol68은 일반성과 직교성의 목표를 달성했지만, 이로 인해 다소 모호하고 복잡한 언어가 생성되었습니다.

보안(1/2) Security (1/2)

  • 특정 기능에 제한이 부과되지 않으면 안정성이 영향을 받을 수 있습니다.
    • 파스칼: 보안 문제를 줄이기 위해 포인터가 제한됩니다.
    • C: 포인터는 훨씬 덜 제한적이므로 오용 및 오류가 발생하기 쉽습니다.
    • Java: 포인터가 완전히 제거되었지만(객체 할당에 암시적임) Java에는 더 복잡한 런타임 환경이 필요합니다.
  • 보안: 신뢰성과 밀접하게 관련됨
  • 보안을 염두에 두고 설계된 언어:
    • 프로그래밍 오류 방지
    • 오류를 발견하고 보고할 수 있습니다.
  • 보안에 대한 우려로 인한 유형, 유형 검사 및 변수 선언

보안(2/2) Security (2/2)

  • 보안에만 집중하면 언어의 표현력과 간결성이 손상될 수 있습니다.
    • 일반적으로 프로그래머가 코드에서 가능한 한 많은 것을 힘들게 지정하도록 합니다.
  • ML과 Haskell은 보안을 유지하면서도 최대한의 표현력과 일반성을 허용하는 기능적 언어입니다.
    • 다중 유형 객체를 허용하고 선언이 필요하지 않지만 정적 유형 검사를 수행합니다.
  • 의미상 안전함:
    • 프로그래머가 언어 정의를 위반하는 명령문이나 표현식을 컴파일하거나 실행하지 못하도록 하는 언어
    • Python, Lisp, Java

확장성 Extensibility

  • 확장 가능한 언어: 사용자가 기능을 추가할 수 있습니다.
    • 새로운 데이터 유형 및 새로운 연산(함수 또는 프로시저)을 정의하는 기능
    • 언어의 기본 제공 기능을 확장하는 새 릴리스
  • 구문 및 의미 체계에 추가를 허용하는 언어는 거의 없습니다.
    • Lisp는 매크로를 통해 새로운 구문과 의미를 허용합니다.
  • 매크로: 컴파일될 때 다른 표준 코드로 확장되는 코드 조각의 구문을 지정합니다.

사례 연구: C++

  • C++: 1979-80년 Bell Labs의 Bjarne Stroustrup이 만든 C의 객체 지향 확장
  • 그는 다음과 같은 이유로 새 언어를 기반으로 하기로 결정했습니다.
    • 유연성, 효율성, 가용성, 휴대성
  • 그는 Simula67 언어에서 클래스 구성을 추가하기로 선택했습니다.
  • C++의 디자인 목표:
    • 클래스, 상속 및 강력한 유형 검사의 형태로 좋은 프로그램 개발 지원
    • C 또는 BCPL 순서에 따른 효율적인 실행
    • 높은 이식성, 쉽게 구현, 다른 도구와 쉽게 인터페이스

C++: 첫 번째 구현 First Implementations

  • 1979-80년에 일반 C 코드를 생성한 Cpre라는 전처리기의 형태로 첫 번째 구현
  • 1985년: 전처리기를 보다 정교한 컴파일러로 교체했습니다(여전히 이식성을 위해 C 코드를 생성함).
    • Cfront라는 컴파일러, C++라는 언어
    • 메소드의 동적 바인딩, 유형 매개변수, 일반 오버로딩
  • C++의 디자인 목표:
    • 가능한 한 C 호환성 유지
    • 실제 경험에서 점진적인 개발을 거쳐야 함
    • 추가된 기능은 런타임 효율성을 저하시키거나 기존 프로그램에 부정적인 영향을 주어서는 안 됩니다.
    • 한 가지 스타일의 프로그래밍을 강요해서는 안 됩니다.
    • 유형 검사를 유지하고 강화해야 합니다.
    • 단계적으로 학습할 수 있어야 함
    • 다른 시스템 및 언어와의 호환성을 유지해야 합니다.

C++: 성장 및 표준화 Growth & Standardization

  • Cpre 및 Cfront는 교육용으로 무료로 배포되어 언어에 대한 관심을 불러일으켰습니다.
    • 1986: 최초의 상업적 구현
    • 언어의 성공은 표준 언어를 만들기 위한 공동의 노력이 필요했음을 나타냅니다. 
  • C++는 사용이 빠르게 증가하고 계속해서 발전하고 여러 가지 다른 구현이 있었기 때문에 표준화가 문제였습니다.
    • 1989년: Stroustrup은 참조 설명서를 제작했습니다.
    • 1990-1991: ANSI 및 ISO 표준 위원회는 표준화 노력을 위한 기본 문서로 매뉴얼을 채택했습니다.
    • 1994년: 컨테이너 및 알고리즘의 표준 라이브러리 추가
    • 1998: 제안된 표준이 실제 ANSI/ISO 표준이 됨

C++: 회고 Retrospective

  • C++가 성공한 이유는 무엇입니까?
    • 객체지향 기술에 대한 관심이 폭발적으로 증가할 때 도입됨
    • 운영 환경에 얽매이지 않는 간단한 구문
    • 그 의미는 성능 저하를 초래하지 않았습니다.
    • 유연성, 하이브리드 특성 및 기능 확장에 대한 디자이너의 의지가 인기를 얻었습니다.
  • 비방하는 사람들은 C++에 기능이 너무 많고 유사한 작업을 수행하는 방법이 너무 많다고 생각합니다.

사례 연구: 파이썬

  • 1986년 Guido van Rossum이 개발한 범용 스크립팅 언어: 스크립팅 언어용 번역기 및 가상 머신
  • 그의 목표 중 하나는 Python이 C와 같은 시스템 언어와 Perl과 같은 스크립팅 언어 사이의 다리 역할을 할 수 있도록 하는 것이었습니다.
  •  
  • 키/값 쌍의 사전 포함: 해싱을 통해 구현되었기 떄문에, 위치별이 아닌 내용 또는 연관성으로 구성된 객체의 컬렉션을 나타내기에 유용함
  • 설계 목표는 다음과 같습니다.
    • 간단한 정규 구문
    • 강력한 데이터 유형 및 라이브러리 세트
    • 초보자가 사용하기 쉬운

Python: 상호 작용 및 이식성 Interactivity and Portability

  • Python은 일반적으로 큰 시스템을 작성하지 않고 대신 짧은 프로그램을 작성하는 사용자를 위해 설계되었습니다.
    • 개발 주기는 I/O 작업에 대한 최소한의 오버헤드로 즉각적인 피드백을 제공합니다.
  • Python은 두 가지 모드로 실행할 수 있습니다.
    • 상호 작용을 극대화하기 위해 Python 셸에서 표현식 또는 명령문을 실행할 수 있습니다.
    • 파일에 저장된 더 긴 스크립트로 구성하고 터미널 명령 프롬프트에서 실행할 수 있습니다.
  • 이식성의 설계 목표는 두 가지 방법으로 달성되었습니다.
    • Python 컴파일러는 소스 코드를 Python 가상 머신(PVM)에서 실행되는 기계 독립적 바이트 코드로 변환합니다.
    • 응용 프로그램별 라이브러리는 데이터베이스, 네트워크, 웹, GUI 및 기타 리소스 및 기술에 액세스해야 하는 프로그램을 지원합니다.

Python: 동적 대 손가락 입력 Dynamic vs. Finger Typing

  • Python은 Lisp 및 Smalltalk에서 볼 수 있는 동적 입력 메커니즘을 통합합니다.
    • 모든 변수는 유형이 지정되지 않습니다.
    • 모든 변수는 모든 것의 이름을 지정할 수 있지만 모든 것 또는 값에는 유형이 있습니다.
    • 런타임에 유형 검사가 발생합니다.
  • 그 결과 프로그래머의 오버헤드가 줄어듭니다.
    • "손가락 입력" 감소
    • 프로그래머는 코드 세그먼트를 훨씬 빠르게 시작하고 실행할 수 있습니다.

파이썬: 회고 Retrospective

  • Python은 대규모 또는 시간이 중요한 시스템을 위해 C 또는 C++를 대체하기 위한 것이 아닙니다.
  • 런타임 유형 검사는 시간이 중요한 응용 프로그램에 적합하지 않습니다. 
  • 정적 유형 검사가 없으면 대규모 소프트웨어 시스템의 테스트 및 검증에 문제가 될 수 있습니다.
  • 초보자 또는 비프로그래머를 위한 사용 용이성의 설계 목표가 크게 달성되었습니다.

수업 과정

  • 2장의 연습문제는 개념적인 사고를 연습하는 것이 대부분인데, 다음의 문제를 풀어보세요.
    • 2.8: declaration construct에 기반한 언어설계 요인 고찰
    • 2.14: comment construct에 기반한 언어설계 요인 고찰