프로그래밍 언어의 개념을 공부하는 이유
- 아이디어 표현 능력 향상
- 적절한 언어를 선택하는데 필요한 배경 지식 습득
- 새로운 언어 학습 능력 향상
- 프로그래밍 언어의 구현 방식에 대한 깊은 이해와 이를 바탕으로한 기존 언어에 대한 이해도 향상
프로그래밍 분야
- 과학 분야 (큰 수의 부동 소수점 계산 등) → Fortran
- 경영 분야 (레포트 생성, 10진수 숫자와 문자 사용) → COBOL
- 인공 지능 (심볼릭 컴퓨테이션 = 기호를 이용한 논리 전개, 링크드 리스트 사용) → LISP
- 시스템 프로그래밍 (지속적인 사용을 위해 효율성이 필요) → C
- 웹 소프트웨어 → HTML, PHP, Java
프로그래밍 언어의 평가 기준
Readability
프로그램을 읽고 이해하기 쉬워야 한다.
- simplicity (전반적으로 간단한 문법)
한가지 기능은 그 기능 하나만 제공해야하고, 하나의 기능으로 여러가지 일을 이것 저것 할 수 있으면 안된다.
연산자 오버로딩도 최소화하는 것이 좋다.
- orthogonality (직교성)
하나의 언어 안에 있는 여러 기능을 복합적으로 섞어 사용했을 때 서로 충돌이 나지 않아야 한다.
example)
1. 더하기와 곱하기를 같이 썼을 때 에러가 나지 않는다.
2. 파이썬에서는 숫자와 문자열의 덧셈을 지원하므로 직교성이 높다. 하지만 C언어는 문자열과 숫자 덧셈을 지원하지 않으므로 직교성이 낮다.
- Data Types
용도에 맞는 데이터 타입이 다양하게 미리 정의되어있어야 한다.
- Syntax Consideration
문법과 키워드를 만들 때, 그 이름에 맞는 기능이 직관적으로 이해되도록 만들어야 한다.
Writability
프로그램을 작성하기 쉬워야 한다.
- simplicity & orthogonality
배워야할 구조, 데이터 타입, 규칙이 단순해서 적을 수록 작성하기 쉽다.
각각의 기능은 서로에 영향을 주지 않아야 한다.
- abstraction(추상화)를 지원해야 한다.
세부 사항을 모두 알지 못해도, 세부 사항을 함축해 단순화시킨 (추상화한) 기능을 통해 원하는 기능을 사용할 수 있게 해야한다,
- expressivity (표현력)
우리가 원하는 기능을 달성할 수 있을 만큼 강한 표현력(Expressivity)을 가져야 한다.
하지만 강력한 표현력을 가지려면 다양한 연산자와 기능이 필요하므로, 첫번째 단순함을 추구해야한다는 내용과 상충된다. 따라서 적절한 trade-off 가 필요하다.
Reliability
언어가 제공하는 기능이 항상 일관되게 실행된다는 믿음을 가질 수 있어야 한다.
- Type Checking
타입을 검사해서 정해진 자료형에는 정해진 형태의 데이터만 넣을 수 있도록 한다.
- Exception Handling
수를 0으로 나누는 등, 불가능한 연산에 대해서는 사람에게 에러가 난다는 것을 알려줘야 한다.
또 에러가 났을 때, 에러 상황을 처리할 수 있는 수단을 제공해야 한다.
- Aliasing
같은 메모리 주소에 대해 서로 다른 접근 방법(변수)을 제공해야 한다.
- Readability & Writability
읽기 쉽고, 쓰기 쉬워야 한다. (사람이 자주 사용하는 언어와 비슷해야 한다.)
Cost
프로그램을 작성, 테스트, 디자인, 신뢰성 향상시키는 비용과 시간이 적어야 한다.
- 언어를 사용하는데 필요한 교육량
- 프로그램을 작성하고, 실행하는데 걸리는 시간
- 충분한 신뢰성. (신뢰성이 낮으면 잦은 에러가 발생하므로 에러를 고치기 위해 많은 비용을 지불해야 한다.)
- 프로그램을 유지보수하는 비용
다른 요소들
- 이식성 (portablilty)
하나의 프로그램이 다른 기계로 옮기기 쉬워야 한다.
- 일반성 (generality)
다양한 분야의 어플리케이션에 적용 가능해야 한다.
- 언어의 공식적인 정의가 탄탄하게 잘 정의되어 있어야 한다.
언어 디자인에 영향을 끼치는 요소
컴퓨터 구조 (Computer Architecture)
컴퓨터의 언어로 바뀌는 프로그래밍 언어는 컴퓨터 구조(보통 폰 노이만 구조)에 영향을 받는다.
폰 노이만 구조는 프로그래밍의 모든 명령어와 데이터를 메모리에 저장한 뒤, 메모리에서 명령어와 데이터를 하나씩 꺼내와 실행하는 구조이다. (fetch - execute cycle)
프로그래밍 언어도 이 구조에 맞춰서 명령형 언어로 주로 디자인된다.
(메모리 공간을 '변수'로 만드는 것, 변수에 값을 할당하는 것, 폰 노이만 구조의 jump, iteration (반복문) 등)
프로그램 설계 방법론
프로그램 디자인 방법론의 발전은 언어의 디자인에 영향을 미친다.
1950년대
낮은 컴퓨터의 성능 => 간단한 프로그램만 실행 가능
언어도 성능에 맞게 단순한 기능만을 제공했다.
1960년대
컴퓨터의 성능이 증가 => 언어도 사람이 읽기 쉽고 쓰기 쉽도록 변화
structured programming (구조화 프로그래밍)
top - down 디자인
step - wise refinement (함수를 작은 단위로 자르는 기법) 이 등장
1970년대
많은 데이터를 처리하기 위해 data abstraction 기법이 등장
절차 지향 -> 데이터 지향으로 프로그래밍 방법론이 진화
1980년대 중반
객체 지향 방법론 등장 (데이터 추상화 +상속 + 다형성)
각각의 시기마다 등장한 각각의 개념들은 그 개념들에 맞는 새로운 언어의 탄생으로 이어졌다.
프로그래밍 언어의 종류
Imperative (명령형 언어)
변수, 할당, 반복문 등의 기능을 제공한다.
객체지향 언어, 스크립트 언어, 비주얼 언어도 이 범주에 들어간다.
( C, Java, Perl, Javascript, Visual Basic, .NET, C++ )
Functional (함수형 언어)
수학의 함수처럼 함수에 값을 주고 실행하면 값을 내보낸다.
이를 연속해서 실행하면 함수의 합성을 통해 원하는 답을 낼 수 있다.
함수를 정의하고, 함수의 합성으로 프로그래밍하는 언어
(LISP, Scheme, ML, F#)
Logic (논리형 언어)
rule-based, 논리 규칙을 계속해서 적용해 답을 내는 언어이다.
(Prolog)
Markup / programming hybrid
마크업 언어는 보여줄 화면을 표현하는 언어, 동적 기능은 제공하지 않는다.
마크업 언어에 동적 기능이 추가된 JSP 같은 언어가 등장하였다.
( JSTL, XSLT )
프로그래밍 언어 디자인 시, Trade-Off
1. 신뢰성이 높으면 실행 비용이 증가한다.
자바는 배열 요소를 참조하는 것에 대해 적절한 인덱스를 사용했는지 검사한다.
이는 실행 시간에 영향을 주는 대신, 프로그램에 대한 신뢰성 향상으로 이어진다.
반면 C는 배열 범위를 벗어난 인덱스를 작성해도 실행하기 전까지는 에러가 나지 않는다.
실행 비용을 낮춘 대신, 신뢰성을 포기한 것이다.
2. 작성하기 쉬운 언어는 읽기 어렵다.
APL 은 행렬을 곱했다가 역행렬을 만드는 복잡한 연산을 연산자 하나로 해주는 강력한 기능을 제공한다.
이런 강력한 기능은 프로그램을 작성하기 쉽게 만들어주지만, 프로그램을 읽고 이해하는데에는 좋지 않다.
3. 너무 유연하면 신뢰성이 떨어진다.
C++의 포인터는 메모리에 대해 다양한 조작이 가능하므로 강력한 기능을 제공하지만, 신뢰성을 잃기 쉽다.
프로그래밍 언어의 구현 방법
컴파일 방식
전체 프로그램을 기계어로 번역한다.
실제로 실행이 될 때 컴파일하는 JIT 시스템도 컴파일 방식에 포함된다.
거대한 상업용 어플리케이션에서 사용한다.
번역 시간은 오래 걸리지만, 실행시간은 빠르다.
컴파일은 다음 단계들을 가진다.
1. lexical analysis : 문자열 집합의 소스코드에서 렉시컬 유닛 (키워드) 를 찾아 변환한다.
분석을 하다가 키워드가 아닌 문자 (변수명, 함수명 등)를 만나면 symbol table에 저장한다.
2. syntax analysis : 렉시컬 유닛들을 가지고 문법 구조를 나타내는 parse tree를 만든다.
3. semantics analysis : intermediate code(중간코드)를 생성한다.
4. code generation : 중간코드로부터 하드웨어에 맞는 기계어를 생성한다.
컴파일된 machine code 를 실행할 때는 '로더' 라고 하는 프로그램을 사용해 프로그램을 메모리에 적재하고, CPU가 실행할 수 있게 해준다.
또 크기가 너무 큰 프로그램은 작게 모듈을 나눠 코드를 작성하는데, 각각의 모듈을 하나의 프로그램으로 합칠 때는 Linking 과정을 통해 하나의 프로그램으로 합치게 된다.
컴파일 방식에서는 '폰 노이만 병목현상' 이 발생할 수 있다.
이 현상은 CPU의 속도는 충분히 빠른데, 메모리에서 데이터를 가져오는 속도가 CPU의 속도를 따라오지 못해 전체 프로그램 성능이 충분히 나오지 못하는 현상을 말한다.
Preprocessor (전처리기)
전처리기는 컴파일하기 전에 미리 처리할 동작을 명시하는 코드 (매크로) 이다.
보통 기존에 내장된 다른 전처리기 매크로를 확장하기 위해 실행된다.
대표적으로 C언어의 #include, #define 같은 전처리기가 있다.
Pure Interpretation
인터프리터라고 하는 프로그램을 이용해 프로그램을 한 줄씩 interprete(번역) 한다.
효율성이 중요하지 않거나, 작은 프로그램들이 이 방식을 사용한다.
전체 번역을 하지 않고, 코드를 한줄 한줄 그때그때 바로 번역해서 실행하는 방식이다.
런타임 에러를 쉽게, 그리고 즉시 발견할 수 있다.
단점은 실행 속도가 컴파일 방식에 비해 최대 100배까지도 느리고, 더 많은 메모리 공간을 필요로 한다.
하이브리드 방식
컴파일 방식과 인터프리터 방식의 장점을 가져온 방식이다.
(빠른 실행 시간 + 실시간 에러를 검출 & 유연성)
고급 언어를 interpretation 하기 쉬운 중간 언어로 번역한다.
순수 인터프리터 방식보다 더 빠르게 실행할 수 있다.
작거나 중간 사이즈의 시스템인데, 효율성은 별로 필요하지 않을 때 사용한다.
ex) 자바(고급 언어)는 바이트코드(중간 언어)로 번역한 뒤, 이 중간 언어를 인터프리터 방식으로 JVM에서 실행한다.
ex) Perl 은 interpretaiton 이전에 에러를 검출하기 위해 부분적으로 컴파일한다.
JIT (Just In Time) 시스템
처음에 고급 언어를 중간 언어로 번역한다.
그리고 실제 중간 언어로 번역된 프로그램이 필요한 순간에 컴파일하여 실행한다.
정말 필요한 순간에 나중에 컴파일을 하기 때문에 delayed compilers 라고도 한다.
.NET 도 이 방식으로 구현되어있다.
프로그래밍 환경
소프트웨어 개발을 할 때 사용되는 도구들의 모음 (IDE)
UNIX는 더 나은 개발 환경을 위해 GUI를 채택하고, 마이크로소프트는 Visual Studio, NetBeans 같은 IDE를 제공한다.
'CS > 프로그래밍언어론' 카테고리의 다른 글
[프로그래밍언어론] 6. C언어의 BNF 문법과 파싱 예제 (0) | 2024.04.17 |
---|---|
[프로그래밍언어론] 5. Lexical and Syntax Analysis (0) | 2024.04.17 |
[프로그래밍언어론] 4. Semantics (0) | 2024.04.16 |
[프로그래밍언어론] 3. Syntax 와 BNF, Parse Tree (1) | 2024.04.13 |
[프로그래밍언어론] 2. 프로그래밍 언어의 진화 (0) | 2024.04.12 |