프로그래밍 언어의 발전
컴퓨터 시스템과 운영체제가 발전해온 역사와 프로그래밍 언어가 어떻게 변화되어 왔는지에 대해서 자세히 알아보도록 하겠다. 프로그래밍의 역사를 살펴보는 것은 앞으로의 프로그래밍 언어론을 공부하는데 있어 도움이 될 것이다.
컴퓨터 시스템과 운영체제의 발전
컴퓨터 시스템은 계산을 빠르게 수행하기 위한 목적으로 만들어졌다. 최초의 컴퓨터로 잘 알려진 진공관식 전자 컴퓨터인 에니악이 1943~1946년에 만들어졌고, 실제 최초의 전자식 컴퓨터인 콜로서스도 1943~1945년에 만들어졌다.
그리고 저장 프로그램 방식의 최초 컴퓨터인 에드박이 1949년에 만들어졌다. 에드박은 프로그램과 처리기가 분리되어있다는 중요한 특성을 가진다. 이때, "프로그램" 개념이 정립되었다. 프로그램과 처리기가 분리되기 시작했기에 프로그램을 관리하고 컴퓨터를 운영하는 관리자가 필요하게 되었는데, 이를 "오퍼레이터(Operator)"라고 부른다. 컴퓨터 시스템이 발전하여 오퍼레이터가 하는 일 또한 프로그램이 대신하게 되었는데 이를 "운영체제""라고 한다.
초기의 운영체제는 원래 오퍼레이터가 하는 "일괄처리 운영체제"였다. 한 번에 한 프로그램만 가능하였기에 여러 프로그램을 동시에 실행하는 운영체제의 필요성이 대두되었고, "시분할 운영체제"가 등장하였다. 이로 인해 사용자의 응답시간이 상당히 단축되었다. 시분할 운영체제는 여러 사용자의 다수 프로그램을 실행 중인 상태로 만들어야 했기에 "프로세스" 개념이 등장하였다. 또한 시분할 운영체제로 인해 "클라이언트-서버" 방식의 컴퓨터 환경이 등장하였다.
컴퓨터 제작 기술이 발전하고 가격이 저렴해져 개인용 컴퓨터가 보급되었다. 초창기 PC에는 "DOS" 운영체제가 사용되었고. 그 다음 세대 PC에는 GUI 환경이 등장하였다.
1950년대: 초기 프로그래밍 언어
ㅇ1950년대에는 Fortran, LISP, Algol 등 초창기 프로그래밍 언어가 등장하였고, 프로그래밍 언어의 발전이 시작되었다.
- Fortran: 수식 계산에 초점을 둔 언어로 산술 연산, 내장 함수, 제어문의 초기 형태를 제시하였다.
- Algol: 알고리즘 기술을 위해 개발된 언어로, 구조화 프로그래밍 개념을 적립하였다.
- LISP: 리스트 처리를 위한 언어로, 최초의 함수형 언어이다.
1960년대: 프로그래밍 언어의 발전
시분할 환경이 지원되며 더 많은 사용자가 컴퓨터를 사용할 수 있게 되었고 컴퓨터 프로그래밍 교육이 본격적으로 시작되었다.
- Cobol: 데이터 처리를 위한 언어로, 복합 데이터 타입을 나타내기 위한 "레코드" 개념이 처음 제시되었다.
- PL/I: 다양한 용도의 프로그래밍 언어를 모두 포함하고자 하는 목적으로 만들어졌으나, 이는 프로그래밍 언어가 과도하게 복잡해지는 결과를 낳았다. PL/I는 MULTICS 시분할 운영체제 개발에 사용되었으나, MULTICS 또한 매우 복잡한 시스템이었기에 실패했고 그 후, unix 운영체제가 개발된다.
- BASIC: 교육용 언어로서 큰 인기를 끌었다.
- Simula: 객체, 클래스, 상속 개념을 포함한 언어로 후에 객체지향 언어에 큰 영향을 끼쳤다.
1970년대: 프로그래밍 언어의 단순화
UNIX 운영체제와 PC의 보급에 따라 컴퓨터 사용자층도 두터워지고 프로그래밍 언어도 급속도로 발전한다. 이 시기에 다양한 패러다임의 언어가 등장하기 시작하였다.
- Pascal: 이전 언어로 제시된 레코드를 통한 자료구조, 다양한 제어구조, 구조화 프로그래밍을 지원함으로 교육용 언어로 매우 인기있는 언어였다.
- C:시스템 프로그램을 작성하기 위한 언어로 하드웨어의 세부 사항까지 다룰 수 있도록 설계되었다. C언어의 이러한 특징은 C의 인기를 매우 높였고 후속으로 개발되는 다양한 언어에 막대한 영향을 끼쳤다.
- Prolog: 인공지능 분양의 자동 추론, 자연어 처리 등을 위해 개발된 언어로, 선언적 특성을 지닌다.
- Smalltalk: 모든 것을 객체로 간주하였기에, 프로그래머에게 큰 환영을 받지 못하였다.
- Ada: 안전성이 중요한 응용 프로그램에 사용되는 언어이다.
- ML: 타입 시스템을 갖춘 현대 프로그래밍 언어로, 오늘날 SML, OCaml로 발전되어 사용되고 F# 언어에도 큰 영향을 끼쳤다.
- Scheme: LISP의 동적 영역 규칙을 채택하지 않고 정적 영역 규칙을 통하여 프로그램 가독성을 높이고 꼬리 재귀호출 최적화를 통해 수행 성능도 향상시켰다.
1980년대: 현대 프로그래밍 언어의 등장
PC가 발전하며 GUI 환경도 발전하였는데, GUI로 등장하게 된 객체지향 개념은 프로그램 구성 방법에 매우 큰 변화를 가져왔다.
- Common LISP: Common LISP는 LISP의 다양한 변종 언어를 통합하고자 하는 시도로부터 출발한 언어로 병렬 프로그래밍, 객체지향 프로그래밍을 지원했다.
- Objective-C: Smalltalk의 객체지향 개념이 C에 도입되어 Objective-C가 탄생하였다. 초기 Objective-C는 C의 프로그램으로 변환되는 형태로 구현되었으나, MacOS와 iOS의 발전에 따라 Apple 애플리케이션 작성 언어로 발전하였다.
- C++: C의 모든 기능을 포함하는 형태로 정의되었기에 C의 인기에 힘입어 C++은 널리 보급되었다. C++ 또한 객체지향 개념과 성넝의 조화를 동시에 추구하였고 "템플릿", "람다 함수", "타입 추론" 등 점차 기능이 확대되며 매우 복잡한 언어가 되었고 계속해서 언어를 보강해나가며 확장되며 다듬어지는 중이다.
- Perl: 문자열 처리에 특화된 언어로, 패턴 매칭 기능이 강점인 언어이다.
1990년대 이후: 프로그래밍 언어의 대중화
프로그래밍 언어 설계가 고급 개발자들의 영역으로 이전되었고, 각종 스크립트 언어들의 소스코드를 활용하며 이러한 경향은 가속화되었다.
- Java: 프로그래밍 언어의 대중화를 이끈 언어로 JVM을 바탕으로 JVM이 탑재되는 곳이면 어디든 실행이 가능하다.
- JavaScript: 웹 브라우저에서 실행될 목적으로 만들어진 언어로, 전통적인 객체지향 언어보다는 프로토타입 기반 언어이다. 현재 웹 언어의 표준이며 클라이언트, 서버 모두 아우르는 프로그래밍 언어이다.
- Python: Java와 마찬가지로 프로그래밍 언어의 대중화에 아주 큰 영향을 끼친 스크립트 언어로, 다중 패러다임 언어를 표방한다. 모듈 시스템을 지원하여 쉽게 공유가능하며 Python으로 개발된 방대한 라이브러리는 pip 패키지 매니저를 통해 쉽게 설치 가능하다. 또한 교육용 언어로서도 매우 각광받는 언어이다.
- Haskell: 순수 함수형 언어로, 모나드 개념을 통해 JVM 언어 발전에 기여하였다.
프로그램 동작 원리
컴퓨터는 하드웨어와 소프트웨어로 구성된다. 하드웨어는 소프트웨어를 수행하는 물리적 장치이고, 소프트웨어에 의해 컴퓨터의 구체적인 기능이 달라진다. 하드웨어는 여러 장치가 하나의 시스템을 이루도록 연결되었다. 컴퓨터 전원을 켜면 컴퓨터는 운영체제릊 메모리로 적재하여 수행한 후, "인출-해석-실행 주기"를 반복한다.
이때, "인출-해석-실행 주기"란 메모리에 적재된 명령어 하나를 CPU로 읽어들이고, 이를 해석하고 실행하는 주기를 말한다.
CPU가 이해하고 수행하는 언어는 "기계어"이다. 기계어를 사람이 이해하는 것은 사실상 불가능이고 기계어 명령어에 대응하는 기호를 만들었는데, 이것이 "어셈블리어"이다. 어셈블리어가 기계어보다 이해하기는 쉽지만 이 또한 CPU에 종속적이기에 CPU가 달라지면 어셈블리어도 달라진다. 이는 "이식성"을 0에 수렴시킨다.
이러한 문제를 해결하기 위해 고급 프로그래밍 언어가 개발되었다. 사람에 가까운 표현으로 프로그램을 나타내고 특정 기계에 종속적이지 않기에 다른 기계에서도 수행 가능하다. 그러나, 특정 프로그래밍 언어로 작성된 프로그램을 CPU가 수행하게 하려면 기계어로 번역해야 하는데, 기계어로 변환하는 방법은 "컴파일러", "인터프리터" 이렇게 두 가지 방식으로 나뉜다.
- 컴파일러(Compiler): 작성된 프로그램 전체를 기계어로 번역하는 방법으로, 프로그램 수행 효율이 높다. 컴파일러를 통해 기계어 코드가 생성된 이후에는 소스 프로그램을 열람할 필요도 없고, 소스 프로그램 없이도 프로그램 수행이 원활하다. "효율성" 부분과 "소스코드 보호" 차원에서도 매우 유용하다.
- 인터프리터(Interpreter): 각 프로그램 단위 문장을 기계어로 해석하여 수행하는 방법으로, 소스 프로그램 전체가 전달될 필요가 없고, 프로그램을 실행하는데 필요한 실행 단위 하나만 받아서 수행이 가능하다. 즉, 해석 및 실행 단위가 주로 프로그램 문장이 된다.
상용 프로그램은 통상 컴파일 방식을 통해 판매되고 컴파일 방식이 인터프리터 방식에 비해 수행 효율이 높다.
"하이브리드 구현 방식"은 인터프리터 방식과 컴파일러 방식을 조합한 프로그래밍 언어 구현 방식으로, 실제 기계보다 추상화 수준이 높은 가상기계를 가정하여 가상기계 상에서 수행되도록 중간 코드 형태로 프로그램을 컴파일하고 가상 기계는 인터프리터 방식으로 중간 코드를 수행한다.
하이브리드 구현 방식은 플랫폼 가상화와 함께 매우 큰 인기를 얻고 있고, 이는 비싼 컴퓨터를 사는 대신 일정 기간 동안 네트워크에서 자신만의 서버를 구축할 수 있기 때문이다.
프로그래밍 언어의 요구 사항과 설계 원칙
소프트웨어 개발 및 유지보수 측면에서 프로그래밍 언어가 만족해야 하는 요구 사항은 다음과 같다.
- 표현 풍부성(Expressiveness)
- 유지 보수성(Maintainability)
- 실행 가능성(Executability)
위의 요구사항을 만족하기 위해 프로그래밍 언어가 고려해야 하는 설계 원칙은 다음과 같다.
- 규칙성(Regularity): 규칙성은 언어의 기능이 얼마나 잘 조합될 수 있는가에 대한 문제로, 연관된 기능이 더 일반적인 형태로 확장되는 "일반성", 언어의 기능을 자유롭게 조합할 수 있는 "직교성", 비슷한 것을 같은 방식으로 지원하는 "일관성"에 의해 지원된다.
- 추상화 지원(Support of Abstraction): 실세계의 대상을 간략히 추상화하여 나타낼 수 있는 기능을 제공해야 하며, 추상화된 모형을 대상으로 특정 연산을 수행할 수 있도록 해야 한다.
- 복잡도 제어(Complexity Control): 실세계 대상을 해결하는 방법은 매우 복잡해지는 경우가 많기에, 프로그래밍 언어는 복잡도를 제어해야 한다.
프로그래밍 언어의 평가 기준
프로그래밍 언어를 몇 가지 기준으로 평가하는 것은 어렵다. 이는 설계자들이 중시하는 것이 다를 수 있기 때문이다. 그럼에도 보편적으로 받아들여지는 프로그래밍 언어 평가 기준들이 있는데, 다음과 같다.
- 작성력: 프로그램 수식이나 문장, 기능을 쉽게 표현할 수 있는 특성
- 가독성: 작성된 프로그램을 보고 쉽게 이해할 수 있도록 하는 특성
- 신뢰성: 작성된 프로그램이 오류에 빠지는 가능성을 줄이는 특성
- 직교성: 언어 기능이 서로 간섭하지 않고 자유롭게 조합될 수 있는 특성
- 일관성: 유사한 기능을 같은 형태로 나타날 수 있는 특성
- 확장성: 사용자가 원하는 새로운 기능을 추가할 수 있는 특성
- 효율성: 작성된 프로그램이 효율적으로 수행될 수 있도록 하는 특성
- 유연성: 프로그래머가 표현하고 싶은 내용을 유연하게 수용하는 특성
- 이식성: 프로그램을 다른 실행 환경으로 이전할 수 있는 특성
프로그래밍 언어의 평가 기준이 상충될 경우, 언어 설계자는 적절한 타협점을 택해야 한다. 이때, 상충되는 평가 기준 사이에서 적절한 수준을 정하여 택하는 것을 프로그래밍 언어 평가 기준 사이의 "절충"이라고 한다.
효율성과 신뢰성, 가독성과 작성력, 신뢰성과 유연성은 서로 상충될 수 있다. 이러한 상충되는 평가 기준 중 적당한 타협점을 찾아 "절충"해야 한다.