프로그래밍의 일곱 가지 원형 언어 (2022)

3 hours ago 2
  • 개별 문법보다 기초 패턴 묶음의 차이가 더 중요하며, 프로그래밍 언어는 반복·재귀·구성 방식에 따라 일곱 가지 원형 언어로 나뉨
  • ALGOL, Lisp, ML, Self, Forth, APL, Prolog가 핵심 분류이며, 각 계열은 대표 언어를 기준 표본으로 삼아 다른 언어의 계통을 판단
  • 익숙한 원형 언어를 공유하는 새 언어는 배우기 쉽지만, 낯선 원형으로 이동하면 새로운 사고 경로와 상당한 학습 시간 필요
  • ALGOL은 대입·조건문·반복문 중심의 함수 조직, Lisp는 매크로와 리스트 코드, ML은 일급 함수와 재귀, Self는 메시지 전달 객체, Forth는 스택 기반 문법, APL은 n차원 배열, Prolog는 사실과 탐색 구조가 특징
  • 모든 프로그래머에게 ALGOL 계열 언어 숙련이 우선순위이며, 그다음으로 SQL을 배우고 이후 낯선 원형 언어를 꾸준히 익히는 방식이 장기적으로 유리함

프로그래밍의 일곱 가지 원형 언어

  • 프로그래밍 언어를 고를 때는 개별 문법 차이보다 기초 패턴 습득이 더 중요하며, 비슷한 계열 언어 사이에서는 배열 순회나 조합 순회 같은 기본 구조가 거의 같은 형태
  • 서로 다른 언어군은 반복, 재귀, 프로그램 구성 방식이 크게 다르며, 이런 기초 패턴 묶음이 서로 다른 원형 언어를 이룸
  • 익숙한 원형 언어를 공유하는 새 언어 학습은 비교적 쉬운 전환이지만, 낯선 원형 언어로 이동할 때는 상당한 시간과 새로운 사고 경로 필요
  • 소프트웨어 분야에서 인식하는 원형 언어는 ALGOL, Lisp, ML, Self, Forth, APL, Prolog 일곱 가지
  • 각 원형 언어는 특정 대표 언어를 기준 표본처럼 삼아 분류하며, 다른 언어들은 그 표본과 비교해 계통을 판단하는 방식
  • ALGOL

    • 프로그램이 대입, 조건문, 반복문의 순서열로 구성되며, 함수 단위로 조직되는 형태
    • 많은 언어가 여기에 모듈 시스템, 새 데이터 타입 정의 방식, 다형성, 예외나 코루틴 같은 대체 제어 흐름 구조 추가
    • 현재 널리 쓰이는 대부분의 프로그래밍 언어가 이 원형 언어 계통에 속하는 형태
    • ALGOL 자체로는 ALGOL 58, ALGOL 60, ALGOL W, ALGOL 68 포함
    • Assembly language, Fortran, C, C++, Python, Java, C#, Ruby, Pascal, JavaScript, Ada가 이 계통에 연결
    • 가장 오래된 원형 언어로, Ada Lovelace의 Babbage 해석기관용 프로그램 정식화까지 거슬러 올라가는 계보
    • EDVAC와 초기 Univac으로 이어지는 Eckert-Mauchly 구조 컴퓨터의 기계어와 어셈블리어, Grace Hopper의 A-0부터 Fortran과 COBOL에 이르는 초기 고급 언어 시도가 모두 이 형태
    • 1960년대 학계에서 구조적 프로그래밍이 발전하며 이런 언어들을 더 관리 가능하게 만들었고, 그 결과가 ALGOL 60이며 이후 계열 구성원 대부분이 여기서 파생
    • 시간이 지나며 다른 원형 언어의 기능을 흡수하는 경향 존재
      • 1980년대에는 Self 계열 개념이 클래스 형태로 접목되어 데이터 타입 정의와 다형성 구현 수단으로 사용
      • 2010년 이후에는 ML 계열 개념도 등장
  • Lisp

    • 괄호로 감싼 전위 표현식과 리스트 표현이 결합된 문법
      • (+ 2 3)
      • (defun square (x) (* x x))
      • (* (square 3) 3)
    • 공백으로 구분된 항목을 괄호로 감싼 리스트 표현이 언어에 내장되어 있어 코드 자체가 리스트 형태
    • 매크로가 리스트를 받아 수정한 뒤 수정된 코드를 컴파일러에 넘길 수 있어, 프로그래머가 언어 의미를 재정의할 수 있는 구조
    • 대부분의 코드 작성에서는 다른 원형 언어, 보통 ALGOL이나 ML처럼 동작하는 경향이 있지만, 매크로 시스템이 구별점
    • Common Lisp의 loop 문법도 언어 내장 기능이 아니라 매크로로 정의된 형태
    • 초기 Lisp 변종이 많았지만 커뮤니티는 Common Lisp로 합의 형성
    • Sussman과 Steele은 함수로 어디까지 가능한지 탐구해 Scheme 생성
    • 수치 계산용 Lush, AutoCAD 스크립트 언어 AutoLISP, Emacs 편집 동작 구현 언어 Emacs Lisp 같은 특수 목적 Lisp 사용
    • 최근에는 Clojure가 Lisp 계열의 세 번째 주요 분기로 부상
    • Fortran보다 약 1년 뒤에 등장한, 오늘날까지 사용되는 두 번째로 오래된 언어 계열
    • 출발점은 자기 자신의 식을 평가할 수 있는 수학 구조를 어떻게 표기할지에 대한 수학적 질문
    • John McCarthy가 1958년에 답을 제시했고, 이후 컴퓨터에 구현
    • 초기 Lisp는 수학적 배경 탓에 당시 기계와 잘 맞지 않았으며, 메모리와 CPU 사이클 문제는 수학에는 없던 주제였고 가비지 컬렉션 같은 기법이 필요해진 상황
    • 1970년대 후반과 1980년대 초반에는 Lisp 실행만을 위해 밑바닥부터 설계된 기계가 존재
    • 오늘날 통합 개발 환경의 많은 요소가 그 기계들에서 발명
    • 같은 시기 인공지능 연구의 주된 수단이 Lisp였고, 1980년대 인공지능 과열이 성과를 내지 못하자 분야와 함께 Lisp도 AI Winter로 추락
    • 이후에도 살아남았으며, 컴퓨터 성능 향상과 다른 언어들의 기능 수용으로 구현 난점이 줄어든 상태
  • ML

    • 함수가 일급 값이며, 다양한 함수와 태그드 유니언을 표현할 수 있는 Hindley-Milner 계열 타입 시스템 보유
    • 모든 반복이 재귀로 수행되는 형태
      • sum [] = 0
      • sum (x:xs) = x + sum xs
    • 반복 패턴을 캡슐화한 함수를 정의하고, 다른 함수를 받아 동작을 구현하는 방식도 사용
      • map _ [] = []
      • map f (x:xs) = (f x) : (map f xs)
    • 일부 언어인 Miranda와 Haskell은 기본적으로 지연 평가 사용
    • 다른 언어들은 타입 시스템을 여러 방향으로 확장
      • OCaml은 Self 원형 언어 개념과의 결합 시도
      • Agda와 Idris는 값과 타입을 섞는 종속 타입 시스템 채택
      • 1ML은 모듈과 타입 결합
    • ML에서 CaML, Standard ML, OCaml이 파생
    • Miranda, Haskell, Agda, Idris 같은 관련 계열도 이어짐
    • ML은 영국 Cambridge에서 개발된 정리 증명 프로그램의 메타언어였으며, 이름도 여기서 유래
    • 이후 그 맥락을 벗어나 독자적 언어로 퍼졌고, 특히 영국과 프랑스를 중심으로 유럽에서 인기 확보
  • Self

    • 프로그램이 서로 메시지를 주고받는 객체 집합으로 구성되며, 모든 동작이 이 방식으로 구현
    • 새 객체는 기존 객체에 메시지를 보내 생성하는 구조
    • 조건문도 true 객체와 false 객체 중 하나를 참조하는 변수를 통해 수행
      • 두 객체는 참일 때 실행할 함수와 거짓일 때 실행할 함수를 매개변수로 받는 메시지 수신
      • true 객체는 첫 번째 함수를, false 객체는 두 번째 함수를 실행
      • 호출 코드는 어느 객체인지 모르고 단지 메시지를 전송하는 형태
    • 반복문도 같은 방식이며, 적절한 객체를 만들어 적절한 위치에 넣으면 언어 의미 전체 재정의 가능
    • 이런 언어들은 보통 소스가 텍스트 파일이 아니라 라이브 환경에 저장
    • 프로그래머는 라이브 시스템을 수정하고, 파일을 컴파일해 시스템을 만드는 대신 그 상태를 저장
    • 중요한 예시는 Smalltalk와 Self
    • 많은 언어가 이 언어군의 메시지 전달 방식을 일부만 도입하며, 이런 부분 도입을 보통 객체지향 프로그래밍이라고 부름
    • 이들 대부분은 Smalltalk 기반이며, JavaScript만은 예외로 Self의 클래스 없는 객체 시스템에서 유래
    • Common Lisp의 객체 시스템은 메시지를 받는 객체 하나만이 아니라 모든 매개변수를 기준으로 런타임이 실행 코드를 선택하도록 일반화
    • Erlang은 실행 흐름이 객체 사이를 옮겨 다니는 대신, 병렬 실행 스레드가 명시적으로 메시지를 듣고 보내는 방향으로 전환
    • 원조 언어는 Smalltalk이며, 1970년대 후반과 1980년대 Xerox Parc에서 개발
    • 1980년대에는 여러 상용 Smalltalk 시스템이 있었고, IBM은 다른 언어용 프로그래밍 도구인 VisualAge 컬렉션 개발에 Smalltalk 사용
    • 오늘날 Smalltalk는 주로 오픈소스 Pharo Smalltalk 형태로 존속
    • Smalltalk를 빠르고 효율적으로 실행하는 연구가 많이 진행되었고, 그 정점이 Strongtalk 프로젝트
    • Strongtalk의 발견은 Java의 HotSpot JIT 컴파일러 기반이 되었다는 역사적 중요성
    • Smalltalk는 이전 언어의 값과 타입 개념을 이어받아 클래스를 구현했고, 모든 객체는 타입을 부여하는 클래스를 가졌으며 클래스가 그 타입 객체를 생성
    • Self는 클래스 개념을 제거하고 객체만으로 구성
    • 더 순수한 형태라는 이유로 이 원형 언어의 표본으로 Self 선택
  • Forth

    • 스택 언어는 Lisp의 역상 같은 형태이며, Hewlett Packard의 역폴란드 표기 계산기 문법과 문법을 공유
    • 데이터 스택을 가지며, 42 같은 리터럴을 쓰면 스택에 푸시되고 함수 이름은 명시적 매개변수 없이 스택을 대상으로 동작
    • 간단한 산술도 2 3 + 5 *처럼 뒤집힌 형태
    • 함수 정의도 매우 간결한 형태
      • 대부분의 Forth 변종에서 :가 새 단어 정의
      • square는 dup과 * 호출과 같은 의미
      • dup는 스택 최상단 복제, *는 최상단 두 항목 곱셈
    • 파서를 가로채 자기 코드로 교체할 수 있어 문법 전체 교체 가능
    • Fortran 부분집합이나 패킷 레이아웃, 상태 기계 전이를 나타내는 ASCII 다이어그램 직접 파싱 방식 같은 작은 언어를 정의한 Forth 프로그램이 흔한 형태
    • Forth의 여러 변종, PostScript, Factor, Joy 포함
    • Joy는 스택 대신 합성의 수학적 정식을 쓰는 순수 함수형 언어
    • Forth는 1970년에 전파망원경 제어용으로 처음 작성
    • 이후 임베디드 시스템 전반으로 널리 확산
    • Forth 시스템은 부트스트랩이 충분히 쉬워서 여러 프로그래머가 각자 목적에 맞게 만든 변종이 수십 종 존재
    • PostScript는 1980년대 프린터에 문서를 설명하는 유연한 수단으로 등장
    • PostScript는 여러 면에서 Forth보다 더 제한적이지만, 그래픽 레이아웃 관련 기본 연산을 언어에 정의
  • APL

    • 언어의 모든 것이 n차원 배열
    • 연산자는 한두 개의 기호로 구성되며, 배열 전체에 대한 고수준 연산 수행
    • 표현이 매우 압축적이어서 기호열 자체가 다른 이름을 붙이지 않아도 연산의 표지가 되는 형태
    • 예시로 변수 x의 평균 계산은 (+⌿÷≢) x 형태
    • APL, J, K가 대표 사례
    • 배열에 대한 고차 연산은 MATLAB, NumPy, R 같은 여러 환경으로 일부 수출된 상태
    • APL은 1960년대 Kenneth Iverson이 만든 수학 표기법에서 시작했고, 이후 컴퓨터에 구현
    • 무거운 계산을 수행하는 사람들 사이에서 이후 계속 틈새 지지층 유지
    • 후손 언어 K는 금융 환경에서 매우 인기 있었던 형태
  • Prolog

    • 프로그램이 사실들의 집합으로 구성
      • father(bob, ed).
      • father(bob, jane).
    • 변수로 다른 사실에서 사실을 유도하는 비접지 사실도 사용
      • grandfather(X, Y) :- father(X, Z), father(Z, Y).
    • Prolog 런타임은 이 사실들과 질의를 받아 결과를 찾기 위해 탐색 수행
    • 사실 정의 구조를 적절히 선택하면 튜링 완전성 성립
    • Prolog에서 사실을 이루는 항은 그 자체로 고유한 데이터 타입이며, 생성한 뒤 런타임에 넘길 수 있음
    • 이 점이 Lisp의 매크로나 Forth의 파서 교체와 유사한 위치
    • Prolog 프로그램은 본질적으로 탐색이므로, 데이터베이스 질의처럼 탐색 순서 조정과 성과 없는 경로의 조기 차단을 중심으로 튜닝
    • Prolog, Mercury, Kanren 포함
    • 이 원형 언어 계열의 실제 프로그래밍 대부분은 Prolog 자체에서 이뤄지며, 커뮤니티의 통일성이 매우 강한 상태
    • 1970년대 프랑스의 논리학자들이 프로그램을 1차 논리로 표현할 수 있음을 깨닫고 구현 시도 시작
    • 1980년대 일본의 5세대 컴퓨터 프로젝트가 Prolog에 크게 베팅했지만 프로젝트 실패와 함께 Prolog 평판도 하락
    • 그와 별개로 수십 년 동안 Prolog 런타임을 대부분의 경우 효율적으로 만들기 위한 연구와 새 기능 추가 연구 지속
    • 수치 제약 같은 기능이 더해지며 제약 논리 프로그래밍으로 이어짐
    • Prolog는 틈새 영역에서 계속 등장
      • Java의 타입 검사는 여러 해 동안 Prolog로 구현
      • Facebook의 초기 소스 코드 검색 도구도 Prolog 기반

어떻게 활용할 것인가

  • 대부분의 프로그래머에게 이 언어군 일부 또는 전부는 매우 이질적으로 보일 수 있지만, 각각이 만드는 사고 경로와 새로운 가능성을 위해 일정 시간 투자할 가치 존재
  • ALGOL 관점에서는 완전히 달라 보이는 두 대상이 다른 관점에서는 사소한 비교가 되는 경우가 매우 흔한 형태
  • 우선순위

    • 모든 프로그래머는 ALGOL 계열 언어 하나를 잘 알아야 하는 상태
    • 그다음으로 Prolog 계열 언어인 SQL 학습 권고
      • 경력에서 ALGOL 다음으로 가장 큰 효용을 준다는 위치
  • 이후 확장

    • 위 두 계열을 익힌 뒤에는 매년 낯선 원형 언어 계열의 새 언어 하나를 배우는 방식이 장기적으로 이득
    • 각 계열에서 제안된 언어와 순서는 다음과 같은 형태
      • Lisp: PLT Racket
      • ML: Haskell
      • Self: Self
      • Prolog: Prolog
      • Forth: gForth
      • APL: K, ok를 통해 사용
  • 순서 조정

    • 수치 계산을 많이 한다면 K를 더 일찍 배우는 편이 적합
    • 임베디드 프로그래밍이 많다면 gForth를 더 일찍 배우는 편이 적합
    • 다만 순서 자체나 정확히 어떤 언어를 고를지는 중요하지 않은 상태
    • Haskell 대신 Standard ML이나 OCaml, PLT Racket 대신 Common Lisp, gForth 대신 Factor를 배워도 무방한 범주
  • 각주에 포함된 보충

    • SQL을 배운 뒤에도 Prolog 자체를 여전히 배울 필요 존재
      • 실제 사용 방식이 SQL과 상당히 다르기 때문
    • Forth를 깊이 이해하려면 Forth 구현체를 직접 만들어보는 접근이 일반적이라는 독자 의견 포함
      • Forth는 한 사람이 비교적 짧은 시간 안에 바닥부터 구현할 수 있을 만큼 작다는 언급
      • gForth는 ANS Forth를 배우기 좋은 구현체
      • 학습 자료로 McCabe의 FORTH Fundamentals, Volume 1 언급
      • 함께 살펴볼 Forth로 PygmyForth, eForth, colorForth 언급
Read Entire Article