페미위키:위키독 보존 프로젝트/아름드리 위키/문서/프로그래밍 언어

최근 편집: 2016년 8월 25일 (목) 18:05

개요

자동 계산을 위해 고안된 언어. 흔히 프로그래밍 언어는 컴퓨터를 작동시키기 위한 언어 혹은 컴퓨터에게 명령을 내리기 위한 언어로 알려져있으나, 이런 정의는 현대의 프로그래밍 언어 이론에 비추어볼 때 부족한 정의이다. 예를 들어 함수형 언어의 시조라 할 수 있는 람다식의 경우 문법과 실행 규칙만이 존재할 뿐, 이 언어로 만든 프로그램을 작동시키기 위한 특정한 기계는 상정되어있지 않다. 대부분의 고급 언어들은 컴퓨터라는 기계를 작동시키는 것만을 목적으로 디자인되는 것이 아니라 어떤 일(계산)을 자동으로 하기 위해 디자인 되며, 그 일들을 실제로 자동으로 수행하기 위해 해당 언어로 작성된 임의의 프로그램을 컴퓨터가 돌릴 수 있도록 뒷받침하는 프로그램들이 추가로 작성된다.

구성

각 언어에는 자신을 정의하는 문법(Syntax)과 의미(Semantics)가 존재하며, 현재 널리 쓰이는 대부분의 프로그래밍 언어는 타입 시스템(Type System)을 함께 가지고있다.

문법(Syntax)

프로그램이 어떻게 생겼는지를 정의한다. 대개 정규 언어(Regular Language)로 프로그램에 쓸 수 있는 단어들을 정의하고, 문맥 자유 언어(Context Free Language)로 각 단어들을 어떤 구조로 조합해야 하는지를 정의한다. 단어 규칙의 예로는, "수는 0 한글자만 있거나 0이 아닌 숫자로 시작해서 0에서 9까지 임의의 숫자들 몇 개를 줄세운 것이다"와 같은 규칙을 정의할 수 있다. 단어들을 조합하는 규칙은 영어 문법에서 어떤 동사를 쓸 때는 주어+동사+목적어 순으로 배열해야 한다거나, 어떤 동사는 목적어를 받지 않는다는 등의 규칙들을 생각하면 된다.

  • 구체적 문법(Concrete Syntax): 위의 구문 규칙들을 아주 세세하게 빠짐없이 나열한 것이다. 실제로 프로그램 코드를 읽어들이기(Parse) 위해 필요하다.
  • 요약된 문법(Abstract Syntax): 프로그램을 일단 읽어들이고 나면 프로그램 코드는 나무(Tree) 자료구조가 된다. 이때부터는 위와 같은 세세한 문법은 필요가 없으므로 잡다한 규칙들을 가지치기한 간단한 문법으로 표현한다. 대개 의미를 정의하거나 타입 규칙을 정의할 때는 요약된 문법을 이용한다. 예를 들어 x := a + b + c와 같은 식을 읽기 위해서 구체적 문법은 변수는 어떻게 생겼는지, 더하기가 두 개 이상 연결되어있으면 어떤 순서로 더해야하는지 등을 모두 정의해야한다. 그러나 요약된 문법을 이용하면 위의 식은 Assign("x", Add(Add("a", "b"), "c"))와 같이 표현할 수 있으며, 이것은 사람이 보기엔 복잡해보여도 컴퓨터가 이해하기엔 훨씬 쉬운 형태이다.

의미(Semantics)

프로그램을 어떻게 해석해야하는지를 정의하며, 주어진 식이나 명령을 보고 무엇을 해야하는지를 정의하는 규칙들의 집합이다. 예를 들어 "x := a + 1" 이라는 프로그램 코드를 보면 "1) 변수 a의 값을 읽어보고 2) 그 값이 정수라면 1을 더한 값을 계산해 3) 변수 x에 저장해야한다"와 같이 해석할 수 있어야한다. 이런 규칙들은 자연어(주로 영어)로 작성된 줄글로 주어지기도 하지만(C 언어의 경우), 새로 만들어지는 프로그래밍 언어들(Haskell 등)은 수식을 이용해 수학적으로 엄밀하게 정의하는 경우도 많다.

수학적으로 의미가 정의된 언어의 장점은 그 실행 규칙이 이견의 여지 없이 명확하다는 것이다. 실제로 C 언어의 경우 각종 질문 사이트에 프로그램을 이렇게 작성하면 왜 이렇게 작동하냐며 질문하는 글들이 꾸준히 올라오며, 수백 페이지에 달하는 C 표준 문서<button data-placement="auto bottom" data-content="심지어 공식 버전은 유료이다. C11 표준 참조. http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57853" data-container=".wiki-fnote">1</button> 를 정독한 사람은 거의 없기 때문에 대부분 자신의 경험에 의존해 프로그램을 작성한다. 이런 불편함 때문에 표준 정의가 줄글로만 주어지는 프로그래밍 언어의 경우, 이를 수학적 정의로 표현해보려는 연구가 따로 진행되기도 한다.<button data-placement="auto bottom" data-content="http://fsl.cs.illinois.edu/index.php/An_Executable_Formal_Semantics_of_C_with_Applications" data-container=".wiki-fnote">2</button> 

타입 시스템(Type System)

주어진 프로그램이 말이 되는 프로그램인지 확인해주는 시스템이다. 프로그램이 제대로 생겼는지를 문맥 자유 언어만으로 표현하는 것에는 한계가 있다. 예를 들어 "토니가 아이언맨 수트를 먹습니다."라는 문장은 문법적으로 겉보기에는 아무 문제가 없지만 내용은 이상하다. 아이언맨 수트는 먹는게 아니라 입는 것이라는 간단한 체크를 해줄 수 있다면 프로그램 작성과 실행에 도움이 될 것이다.

 

여러 가지로 분류가 가능한데, 우선 타입 규칙을 얼마나 빡빡하게 세워놨는지에 따라 분류할 수 있다. 이 분류는 어떤 뚜렷한 경계선이 있다기보다는 어떤 두 타입 시스템에 대해 한 쪽이 다른 한 쪽에 비해 빡빡하다는 식으로 비교할 때 사용할 수 있다.

  • 강한 타입 시스템(Strong Type System): 모든 타입 규칙을 명확히 준수해야한다. 한 타입의 값을 다른 타입의 값으로 바꾸려면 그런 의도가 있다는 것을 명시해야한다. 예를 들어 1 + 2.5 = 3.5 와 같은 계산도 자동으로 해주지 않고, 반드시 int_to_real(1) + 2.5 = 3.5와 같이 명시적으로 타입 변환을 해주어야한다. 프로그래머 입장에서 프로그램을 작성할 때는 귀찮지만 버그를 미연에 방지해주는 효과가 좋다.
  • 약한 타입 시스템(Weak Type System): 타입 규칙이 널널하다. 타입의 영역에서 의미가 모호한 프로그램도 프로그래머를 믿고 대충 제일 그럴싸한 방식으로 계산해준다. 예를 들어 "1" + 2와 같은 식은 대개 강한 타입을 쓰는 언어에서는 말이 안 되는 프로그램이다. 앞의 "1"을 숫자 1로 해석하면 정수 3이 나와야하고, 뒤의 2를 문자열 "2"로 해석한 다음 +를 문자열 연결 연산으로 해석하면 "12"가 나와야할 수도 있기 때문이다. 그러나 약한 타입을 쓰는 언어에서는 이런 경우들에 대해 적당한 규칙을 주고 적당히 실행되게 한다. 짧은 프로그램을 빠르게 작성할 때는 편리할 수 있으나, 대규모 프로그램에서는 사소한 부분에서 의도와 다른 일이 일어나서 전체 프로그램을 오류에 빠지게 하는 골칫거리가 되기도 한다.

 

또는 타입 정보를 코드에 얼마나 명시하기를 요구하는지에 따라서도 분류할 수 있다.

  • 타입 체크(Type Check): 아래에 설명되어있는 타입 유추 기능이 없을 경우, 모든 타입 정보를 코드에 명시해야한다. C 언어의 경우가 그렇다. 변수 a를 아무렇게나 선언해둘 수 없고 int a; 와 같이 타입을 알려주어야한다. 그럼 컴파일러의 타입 체커는 명시된 타입끼리 코드에서 모순이 없는지를 간단히 체크한다.
  • 타입 유추(Type Inference): 프로그램 각 부분의 타입이 무엇인지를 자동으로 유추한다. OCaml, Haskell 등 함수형 고급 언어들이 제공하는 경우가 많다. 변수의 타입을 코드에 따로 써주지 않아도 앞뒤 문맥에 따라 정확한 타입을 알아내고 프로그램이 말이 되는지를 체크한다. 물론 이런 시스템을 가진 언어라 해도 프로그래머의 편의를 위해 코드에 타입을 명시해도 된다.

 

타입을 언제 확인하는지에 따라서도 분류할 수 있다.

  • 정적 타입 시스템(Static Type System): 프로그램을 실행하기 전에 코드를 보고 미리 타입이 맞는지를 확인한다. 실행 전에 타입 오류가 없음을 확인해주므로 프로그래머의 디버깅 부담을 덜어주지만, 언어의 디자인에 따라 이런 미리 확인하기가 수학적으로 불가능한 경우도 있다.
  • 동적 타입 시스템(Dynamic Type System): 일단 실행해보고, 실행 도중에 타입에 문제가 있으면 오류를 낸다. 실행 중에 각 값의 타입을 실행기가 들고 다녀야한다. Python, JavaScript 등 실행기 기반 언어들이 이런 타입 시스템을 사용하는 경우가 많다.

분류

저급언어와 고급언어

명확한 기준선이 있는 것은 아니지만 대개 기계어에 가까울 수록 저급언어, 기계어에서 멀리 떨어져있을 수록 고급언어라고 부른다. 일상 언어에서 쓰이는 저급하다, 고급스럽다의 의미와는 별 상관이 없다. 기계어와 어셈블리어는 명백히 저급언어라고 볼 수 있다. C 언어는 기계어나 어셈블리어에 비하면 매우 고급언어이지만, 현대의 프로그래밍 언어 신기술들이 적용된 언어들에 비하면 기계어에 더 가깝기 때문에 시각에 따라서는 저급언어로 볼 수도 있다. Haskell 등의 순수 함수형 언어, 혹은 Coq 등의 증명에 사용되는 언어들은 코드가 거의 수식에 가깝기 때문에 상당히 고급언어에 속한다.

스타일에 따른 분류

많이들 언어 자체를 두고 순차형 언어, 객체지향 언어, 함수형 언어 등으로 분류하곤 한다. 그러나 현재 유지보수 되고있는 대부분의 프로그래밍 언어들은 각 스타일의 장점들을 각자의 방법으로 차용하고 있으므로 엄밀히 말하면 언어 자체의 분류라기보다는 프로그램을 작성하는 스타일의 문제라 할 수 있다. 예를 들어 순차형 언어라 알려져있는 C 언어로도 객체지향의 방식으로 프로그램을 작성할 수 있으며, 함수형 언어인 OCaml 언어로 순차형 스타일의 프로그램을 작성할 수 있다. 그러나 각 언어가 디자인 될 때 중요하게 여겨진 포인트들은 있으므로 그에 따라 대략적으로 언어를 분류해볼 수는 있다. 예를 들어 C에 굳이 객체지향 프로그래밍을 위한 문법을 잔뜩 넣어 새로 만들어낸 C++이라면, C++로 순차형 프로그래밍을 충분히 할 수 있음에도 객체지향 언어라고 불러도 무리가 없을 것이다.

순차형 언어

C 언어 등. [작성중]

객체지향 언어

C++, Objective-c, Java 등. [작성중]

함수형 언어

LISP, Scheme, ML, Haskell, Coq, Scala 등. [작성중]

 

실행 방식에 따른 분류

컴퓨터에서 프로그램을 실행하는 방식은 크게 두 가지가 있다. 하나는 프로그램을 기계가 바로 이해할 수 있는 최하위 언어로 미리 번역해둔 다음 번역된 프로그램을 기계에 바로 얹는 방식이다. 또 하나는 프로그램을 적당한 수준으로 번역해두거나 원본 그대로 두고, 이 프로그램을 대신 실행해주는 프로그램에 얹는 방식이다. 대부분의 언어는 이론적으로 두 가지 방식을 모두 사용할 수 있으나, 언어에 따라 목적에 맞게 둘 중 한 가지 방식에 더 적합하게 디자인되고 그 방식으로 사용된다. 혹은 두 가지 방법을 모두 사용할 수 있는 언어들도 있다.

컴파일러

C, C++, Rust 등. [작성중]

인터프리터

Java, JavaScript, Python 등. [작성중]

JIT 컴파일러

C#, Java 8 이상, 일부 Python 구현, 일부 Javascript 구현 등 [작성중]

프로그래밍 언어들