Easy Methods To Have Even bigger Muscles
개요
https://www.computerhottips.com/images/Programming-Languages-List.jpg 이렇게 뭔가 하이-테크하고 멋진 것이라 생각하는 사람들도 있는 모양이지만...
https://i.imgur.com/WrQSR.jpg?width=500 이런 거랑 밤새 눈싸움하고 있는게 현실이다. --C언어네-- --이것도 멋져 보인다면 프로그래밍에 재능이 있는걸지도 모른다.-- --다 좋은데 말이야, IRC만 없었으면 좋겠군--
Programming Language
기계(컴퓨터)에게 명령 또는 연산을 시킬 목적으로 설계되어 기계와 의사소통을 할 수 있게 해주는 언어를 뜻한다. 그 결과, 사람이 원하는 작업을 컴퓨터가 수행할 수 있도록 프로그래밍 언어로 일련의 과정을 작성하여 일을 시킨다. 까놓고 말하면 컴퓨터를 부려먹기 위한 언어. 소프트웨어를 만드는데 기본이 된다.
역사적으로 컴퓨터보다 프로그래밍 언어가 먼저 등장했다. 시대를 앞서서 에이다 러브레이스 백작부인과 같이 프로그래밍 언어에 대해 연구한 사람들도 일부 있었으나 본격적인 연구는 1930년대 즈음부터 수학자들에 의해 기계적으로 계산 가능한 함수에 대한 연구가 진행됐다. 그 결과 기계가 이해할 수 있는 언어가 탄생했으며, 바로 이 기계가 계산 가능하고 이해 가능한 언어(프로그래밍 언어)를 실행하는 기계로 언어보다 나중에 발명된 것이 바로 현대적 의미의 컴퓨터다.
공대생들이 꼭 공부하는 것들 중 하나가 이것이다. 하지만 제대로 이해하지 못 하는 사람들에게 이건 외계어일뿐. 더구나 프로그래밍 그 자체가 매우 노가다 작업이다. ~~wiki:"공밀레" 프로그래머에게 인권 따위는 없다~~
등장 배경과 역사
역사를 잘 살펴보면 프로그래밍 비스무리한것을 만들고 했던 기인들이 종종 등장하지만, 프로그래밍 언어라는 본격적인 개념은 역시 수학에서 등장하였다. 괴델은 불완전성 정리를 증명하는 과정중에 알고리즘을 추상화시킨 primitive recursive function 개념을 만들고, 이를 이용하여 증명에 성공하였는데, 수학적으로 본다면 이것이 최초의 프로그래밍 언어라 볼 수 있다.(굳이 따지자면 함수형 언어라 할 수 있다.) 그리고, 몇년 후에 컴퓨터의 아버지라 불리는 튜링은 이 primitive recursive function 과 동치인 튜링머신을 발표하고, 역시 이 튜링머신을 이용하여 불완전성 정리를 다시한번 증명해보인다.정확하게 말하면 모든 프로그램을 디버깅 할 수 있는 프로그램은 없다는 것을 말하며, 이는 어떤 체계를 통해서 이 체계의 모순성을 증명할 수 있는 방법은 없다와 동치이다. 결국 ~~끔찍한 외부 참조 에러가 발생해도~~ 디버깅은 니가 손수 해야 한다
그 이후 Computability라는 수학의 분야가 생기면서 기존의 primitive recursive function을 확장한[* 괴델이 고안한 primitive recursive function에서는 termination이 가정되어있다. 즉, 무한 루프와 같은 것이 불가능하다(!). 이는 불완전성 정리에서(그리고 실제 오늘날 수학에서) 모든 증명이 유한한 길이를 갖고있는것을 가정하고 있기때문이다. primitive recursive function에 mu-operator(말그대로 최소값을 찾아내어주는 operator이다.)를 더하면 class of recursive functions로 진화하여 무한루프 등의 알고리즘들을 다 표현이 가능하다.] Lambda calculus라거나 Unlimited register machine(URM), While-programming[* 오늘날 C나 자바등의 언어에 익숙한 사람들은 이 While-programming이 가장 친숙하게 느껴질것이다.], SKI[* S, K, I 3개의 함수로 이루어진 언어이다. Turing-complete이기때문에 다른 체제와 마찬가지로 모든 알고리듬을 표현하는것이 가능하며, I는 S와 K로부터 연역되기때문에 사실상 함수 2개로 이루어진 가장 간단한 수학적 프로그래밍 언어라 할 수 있다.] 등등 알고리듬을 표현하기 위한 여러가지 체계들이 등장한다. 참고로, Lambda calculus를 고안한 알론조 처치의 경우 혹시나 이것을 이용하면 수학의 완전성을 증명할 수 있지 않을까 해서 시도하지만, 실패한다. 문제는, 상기된 알고리듬 체계들은 모든 알고리즘을 다 표현할 수 있는 체계인가라는 의문인데, 알고리즘이라는것이 수학적으로 정의된 개념이 아니기때문에 그것을 증명하는것은 불가능하였다. 하지만 저 모든 알고리듬 체계들이 표현방식은 다르더라도 수학적으로 볼때는 다 동일하였기때문에 알론조 처치는 저 알고리듬 체계들이 '모든 알고리듬의 집합'과 동치라고 그냥 간주할것을 제안하며, 이것이 그 유명한 Church's thesis[* 증명이 아니기때문에 theorem 같은것이 아닌 thesis가 붙는다.]이다.
이렇게 수학 쪽에서 프로그래밍 언어에 대한 개념적인 기초가 닦아지는 동안, 역시 수학자인 폰 노이만은 그것들을 이용하여 실제 컴퓨터를 만들기 위한 wiki:폰 노이만 구조 Von-neumann architecture를 만든다. 그렇게 전자쪽과 결합을 하면서 현대 컴퓨터의 원형이라고 할 수 있는 물건이 만들어졌다. 이러한 전자적인 계산장치는 전기신호를 통해서 제어하였는데, 전기신호를 표현할 수 있는 방법은 신호가 들어왔다(1), 신호가 들어오지 않았다(wiki:영(숫자) 0) 정도에 불과하였다. 따라서 제어신호는 0과 1만으로 표현하는 라이프니쯔[* 이사람은 수백년을 앞서나간 무진장 흠좀무한 인간이다. 당시 기술력만 되었다면 그시절에 이미 컴퓨터라는것을 만들었을지도 모른다.]식 2진법을 사용할 수 밖에 없었고, 특정한 패턴의 전기신호는 어떠한 동작을 의미한다는 식으로 사람들이 정하고 전자계산장치는 그 신호가 입력되면 정해진 동작을 하는 형태였다.
따라서 뭔가 동작을 시키기 위해서는 이러한 제어신호를 사람이 직접 작성해야 됐는데, 초창기에는 그 0과 1의 제어신호를 사람이 직접 작성하는 형태의 기계어가 사용되었다. 이 기계어는 항목에서 볼 수 있듯이 이쪽 분야에서 가장 원시적인 언어로 기계는 바로 이해할 수 있는데 사람은 도저히 이해하기도 어렵고 알아보기도 힘든 물건이라 결국 사람이 읽기 편하도록 기계어와 특정 기호를 1:1로 대응시키는 어셈블리어가 등장하였다. 과거 기계어를 쓰는 시절보다는 보기가 좀 편해졌지만 여전히 해독하기가 난해하였고, 컴퓨터의 보급과 함께 프로그램의 수요가 늘어나는데 어셈블리어의 생산성은 심히 안습이라서 조금 더 프로그램을 짜기 쉬운 언어들이 등장하기 시작하였다.
초창기에 프로그래밍 언어들은 컴퓨터 성능의 한계로 인해 많은 제약이 따라붙었고, 어셈블리어적인 성격이 어느정도 남아있었다. 그럼에도 어셈블리어에 비하면 읽기가 편했고 이해하기도 훨씬 수월했다. 그리고 컴퓨터의 보급과 성능 발달에 맞물려 그동안 걸려있던 제약조건들도 하나씩 사라지고 보다 사람이 읽기도 쉽고, 이해하기도 쉽고, 작성하기도 쉬운 프로그래밍 언어들이 속속 등장하였다.
다만 어차피 컴퓨터가 이해할 수 있는 언어는 기계어 뿐이기 때문에 사람이 하기에 편해졌다는 것 뿐이지 실제 그 뒤에서 이루어지는 작업은 훨씬 더 복잡해지고 있다.
정리하자면 기계에게 편한 언어는 속도가 빠르지만 사람이 도저히 알아볼 수 있는 물건이 아니라서 생산성이 떨어진다. 반면 사람에게 편한 언어는 속도는 느리지만 이해하기 쉽고 생산성이 높다는 상관관계가 성립한다. ~~이것도 일종의 중역문제인가~~ 단, 여기에 함정이 있는데 기계어로 짠 [발적화]코드와 자바로 짠 [최적화]코드의 수행속도를 비교한다면 당연히 자바쪽이 빠르다. 그러니까 기계어를 사용한다 해도 그걸 다루는 프로그래머가 기계의 특성을 훤히 꿰고 있지 않으면 다른 고급언어를 사용한 결과물보다 느려질 수도 있다는 얘기다. 그래서 몇몇 개발관련 서적에서는 최적화나 성능튜닝한답시고 기계어나 어셈블리를 남용하지 말라고 조언한다. 실제로도, 지금은 컴파일러와 어셈블러가 무척 발달되어 있어서 사람이 어설프게 짠 코드보다 기계가 변환한 코드쪽이 더 낫다. 그리고 이건 사족인데 [난해한 프로그래밍 언어]보다는 [어셈블리어]가 훨씬 읽기 쉽다...
구조
프로그래밍 언어론에 따르면 프로그래밍 언어의 표기는 구문(syntax)과 의미론(semantics)의 두 가지 관점에서 이루어진다. 구문이란 것은 언어의 외형적인 표기 방법을 일컬으며, 의미론은 구문이 내포하고 있는 의미, 즉 그 코드가 수행하는 작업을 뜻한다. 구문이 문법에 비유된다면 의미론은 글에 담긴 정보라고나 할까.
더욱 작은 단위로는 문자열(string), 문장(sentence), 어휘항목(lexeme) 등이 있으며 어휘항목의 종류를 통틀어 토큰(token)이라 한다. 어휘항목에 속한 요소에는 식별자(identifier), 리터럴(literal), 연산자(operator), 특수어(special word) 등이 있다.
분류
해석 방식에 따른 분류
크게 인터프리터 언어와 컴파일 언어로 분류할 수 있다. 절대적인 것은 아니어서, 언어 명세가 어느 한 쪽을 완전히 배제한 형태가 아니면 양쪽을 오가는 것도 가능하다. 예를 들면 C 인터프리터도, 펄 컴파일러도 있다. 아래의 구분은 언어가 주로 구현되는 형태를 따른 것이다. 그리고 그 중간적인 성격을 갖는 바이트코드 컴파일러도 있다.
* 컴파일 언어 : C언어, 델파이등이 포함된다. 소스 코드를 미리 기계어로 번역[* 이 때문에 Native Language라고 불리기도 한다.]해서 수행하기 때문에 수행 속도가 빠르고 보안성이 높다. 하지만 소스 코드의 극히 일부분을 수정해도 재컴파일이 필요하기 때문에 인터프리터 언어보다는 개발이 신속하지 못하다는 단점이 있다.[* 특히 C++ 언어의 컴파일 속도는 매우 느리다. 언어 스펙이 복잡할 뿐더러 런타임에 최대한의 속도를 보장하기 위해 컴파일타임에 최대한 많은 작업을 수행하려 하기 때문이다.]
* 인터프리터 언어 : 소스 코드를 한 줄 한 줄 읽어 그때그때마다 번역해서 수행한다. BASIC이나 JavaScript같은 언어가 이런 형태이다. 소스 코드를 한 줄씩 읽어서 수행하기 때문에 대체로 컴파일 언어보다 성능이 떨어지지만 소스 코드 수정 후 곧바로 실행이 가능하기 때문에 유연성은 더 높은 편. 예를 들어 프로그램이 자기 자신을 수정해서 수행하는 변태적인 행동도 가능하다. 컴파일 기반 언어는 모듈화를 통해 이걸 흉내낼 수 있지만 아무래도 인터프리터 언어의 그것보다는 융통성이 떨어진다. 이 때문에 컴파일 언어에서 수정이 잦은 부분은 외부 프로그램과 각종 값을 주고받는 API를 짠 뒤 이 외부 프로그램을 인터프리터 언어로 짜서 실행시키는 경우가 많다. 이런 용도로 사용되는 언어를 스크립트 언어라고 부르고, 이 때문에 스크립트 언어는 거의 인터프리터 방식이다. wiki:"파이썬" Python, wiki:"루비(프로그래밍 언어)" Ruby, Lua, JavaFX등등 인터프리터 언어 진영에는 수많은 언어들이 포진해있다.
* 바이트코드 언어 : 자바와 C#이 이 계열 언어이다. 소스 코드를 컴파일하는 것까지는 컴파일 언어와 같은데 이 결과물이 가상 기계용 기계어 코드 즉 바이트코드형태이며 이걸 가상기계(인터프리터)[* 자바가상머신, 닷넷 프레임워크 등으로 불림] 실제 기계어로 한줄한줄 번역하며 수행한다. 왜 이런 삽질을 하느냐면 어른의 사정 때문...은 아니고 바이트코드를 번역하는 가상기계는 매우 고속으로 동작하게 만들 수 있고 가상기계만 실제 장비에 맞추어 개발해 놓으면 실제 장비에 상관없이 똑같은 코드를 실행시킬 수 있기 때문이다. 또한 실행시간에 해석하여 수행하는 것은 인터프리터 언어와 마찬가지이므로 컴파일 언어에 비해 동적인 기능[* 대표적으로 Java와 C#은 자동으로 메모리를 관리하는 GC가 있다.]을 수행할 수 있다.
* wiki:"JIT" 저스트 인 타임(JIT Just In Time) 컴파일 : 인터프리터/바이트코드 언어에서 수행속도를 높이기 위해 사용되는 기술. 인터프리터 언어의 소스코드나 바이트코드를 해석하는 가상기계가 현재 실행중인 장비에 맞추어 컴파일 하여 수행한다. 개발은 개발속도가 빠른 인터프리터 언어나 바이트코드 언어로 진행하지만 실제 실행단계에서는 기계어 코드로 변환되어 실행되므로 성능의 향상이 있으며 컴파일 언어보다 빠르다는 주장도 있다.
정적 타입, 동적 타입
* 정적 타입 언어 : 자료형(Type)이 고정돼 있는 언어. 설명이 어려운데 간단히 얘기하자면 정수형으로 정의한 1은 계속 정수형 1로 남아있다. 이걸 실수형 1.0으로 바꾸려면 명시적인 형변환(Type casting)을 해줘야 한다. 묵시적 형변환이 이루어지는 경우도 있지만 제한적이다.
* 동적 타입 언어 : 자료형이 그것을 처리할 함수(또는 메서드)에 따라 그때그때 바뀌는 언어. 예를 들어 정수형 1을 정의했어도 그걸 처리할 함수가 문자열을 받아들이게 설계돼있다면 자동으로 정수형 1을 문자 1로 바꿔준다.
정적 타입 언어가 별 거 아닌 것처럼 느껴질 수도 있지만 실은 프로그래머들을 짜증나게 하는 주범이 바로 형변환(Type casting)이기 때문에 동적 타입 언어는 이런 점에서 매우 강점을 가진다.[* 실생활에서 느껴볼 수 있는 형변환의 예로는 동영상 인코딩을 들 수 있다. 또는 한글 문서를 워드 문서로 바꾼다거나.] 특히 객체 지향 언어에서는 동적 타입 및 그것의 일반화버전이라 할 수 있는 덕 타이핑(Duck typing)이 프로그래머에게 수많은 혜택을 준다. 예를 들어 오리라는 타입과 닭이라는 타입이 있고 둘 다 날아오르는 기능이 있다면 정적 타입 언어에서는 상위 인터페이스를 추출하는 등의 부가 작업이 쩌는데 덕 타이핑을 지원하는 언어에서는 그냥 넣어버리면 알아서 난다. 물론 단점도 있는데 고래 같이 못 나는 타입을 집어넣으면 실행시간 오류(런타임 에러)를 뱉어버린다는거. 정적 타입 언어는 이런 문제가 없다...고 알려져있지만 거짓말이고 정적 타입 언어도 닭은 닭인데 통닭 같이 못 나는 타입을 집어넣는 바람에(기술적으로는 해당 메서드가 구현이 안된 객체) 런타임 에러가 나올 수 있다.
절차 지향 언어
절차 지향 언어는 프로그래밍을 하면서 순서대로 진행 해가는 것을 최 우선 목표로 한 언어를 말한다.[* 말 그대로 절차를 지향하는 언어를 말한다.][* 참고로 모든 프로그램은 순서대로 진행되야 하기에 해당 프로그램을 만들었을 '모든 프로그래밍 언어는 절차적이다' 라고 말할수 있다.]
객체 지향 언어
객체 지향 언어는 프로그래밍을 함에 있어서 객체를 만들고 객체를 조립하는 것을 목표로 한 언어들을 말한다. 객체 지향 언어의 특징은 추상화, 캡슐화, 상속성, 다형성이 있다. 추상화는 객체 내부를 숨겨서 어떻게 일을 하는지 몰라도 결과를 내보낸다.[* 속사정은 상관없이 결과만 나오면 된다. 언어에서부터 프로그래머의 인생이 나오는것 같다.] 캡슐화는 객체 내부에 필요한 데이터등을 묶어서 한번에 관리 할수 있게 해준다.[* 각자 따로 자생하며 논다. 프로그래머도 따로 논다.] 상속성은 모 객체를 이용해서 추가 기능을 더 붙이거나 약간의 수정을 가향 객체를 만들수 있다.[* 파생 상품을 비교적 쉽게 만들수 있다. 아쉽게도 프로그래머는 상속성이 없어서 고급 프로그래머를 대량 생산할수 없다.] 다형성은 내부 내용이 다르더라도 출력이 같거나 비슷하면 각 객체의 같은 이름으로 붙일수 있다는 것이다.[* 비슷한 일을 하는 것들을 같은 방식으로 불러낼수 있다. 프로그래머도 프로그래밍 일 하러 갔는데 웹사이트 만들고 구축한다.]
더 자세한것은 OOP 항목을 참조.
선언형 언어
선언형 언어(Declarative language)는 명령형 언어와 대비되는 개념으로, 함수형 언어와 논리 프로그래밍(Logic programming)등이 여기에 속한다. 업계에서 자주 쓰이지는 않지만, 학계에서는 명령형 언어를 대체할 수 있을거라는 희망떡밥으로 수십년간 큰 인기를 끌고있다.~~지치지도 않나~~
순수 선언형 언어의 특징으로는 referential transparency[* C의 #define을 상상하면 된다. scope 안에서 '='로 정의된 변수들은 아무생각없이 우항으로 대체하는것이 가능하다. 명령형 언어에서는 assignment 때문에 불가능하다.]가 꼽힌다.
함수형 언어
명령형 언어가 튜링머신에 기반하고 있다면, 함수형 언어는 람다 칼큘러스에 기반하고 있는 언어에 대한 총칭이다. 현업에서 많이 쓰이는 명령형 언어와는 대조적으로 몇가지 특징이 있다.
* 순수 함수형 언어는 변수가 없다.
순수 함수형 언어에는 변수와 변수를 바꾸는 대입 연산자(C 언어를 예로 들면 =)가 없다. 명령형 언어에서 a=3 이 a 에 3 을 대입하라는 명령인 반면, 순수 함수형 언어에서는 a=3 을 수학에서 let a be 3. 같이 a 를 3 으로 '정의'하는것으로 본다. 즉 한번 a를 뭐라고 정의했으면 그 정의는 유효 범위 내에서 값이 바뀌지 않는다! 그 유효 범위 안쪽에서 a를 다시 재정의 할 수도 있지만 내부 유효범위 한해서 'a'의 정의가 바뀌는거지 대입되는게 아니다. 안쪽과 바깥쪽이 서로 다른 의미의 'a'가 되는 것.
이게 불러오는 가장 큰 차이가 명령의 '순서'가 의미없다는점이다. 명령형 언어에서는 맨 윗줄에 a=3 이 있더라도 저 아래에 등장하는 a 가 여전히 3임을 보장할수가 없다. 그렇기때문에 a 값을 다른값으로 업데이트 하기 '전'과 '후'의 결과 자체가 완전히 달라지고 순서가 매우 중요하지만, 순수 함수형 언어에서는 첫줄에 a=3 이 있으면 scope(유효범위) 전체에서 a 는 그대로 3 이다.[* 하위 scope 에서 덮어쓰기는 가능하다. 함수형 언어에서는 이것을 shadow 라 표현한다. 명령형 언어에서는 포인터등을 이용하여 하위 scope 에서 아예 상위 scope 변수 자체의 값을 바꿔버리는것도 가능하지만, 함수형 언어에서는 하위 scope 에서 shadow 된 값은 해당 scope 바깥에 절대 영향이 없다. ] 그렇다면 굳이 a 를 사용하기 '전'에 미리 정의할 필요가 없고, scope 내의 아무곳에나 정의가 되어있기만 하면 그걸 그냥 갖다 쓰는 방식으로도 아무런 문제가 없다.
이런 특징에서 나오는 장점으로 표현식의 의미가 명료해진다는 것이 있다. 또, 제어 흐름을 생각하지 않고 프로그래밍 할 수 있다는 장점이 있다. 디버깅을 할 때도, 명령형 언어에서 버그를 잡을 때는 변수들의 전후 변화를 생각하면서 머리를 싸맬 때, 함수형 언어는 값의 변환만을 살펴보면 쉽게 디버깅 할 수 있다.
* destructive update
두번째로 동반되는 명령형 언어와의 차이점이 destructive update 인데, 예를 들어 일반적인 명령형 언어에서 a=a+1는 a라는 변수에 a를 1만큼 증가시키라는 의미를 가지며 이 명령을 시행하는 시점에서 변수 a의 값이 바뀌게 된다.
하지만 순수 함수형 언어인 하스켈의 경우 a=a+1는 말그대로 a는 자기자신보다 1만큼 더한 수라는 것을 의미하며, a의 값을 구해서 출력하게 하면 a=a+1=(a+1)+1=((a+1)+1)+1=(((a+1)+1)+1)+1=... 이런 식으로 무한 루프에 빠져서 영원히 a의 값을 출력하지 못하고[* 참고로, 이것은 lazy evaluation 때문이 아닌, 함수형 언어의 특징이다. 사실 함수형 언어에서는 저 a 역시 함수로 취급하고 있다고 보면 된다. f(x,y) 를 이변수함수로, f(x) 를 일변수함수로 보듯이, a 역시 f(void) 같은 파라메터가 0 개인 함수로 보면 된다.(사실, 함수형 언어에서의 a=3 은 그냥 C 언어에서의 int a(void) { return 3; } 으로 보면 된다.) 실제 수학에서도 상수가 따로 있는게 아니라, 함수기호와 관계기호만 언어차원에서 정의하고, 함수기호의 파라메터가 0 일경우 이걸 그냥 '상수'로 부르는 경향이 있다. 사실 이런 특징이 일반 프로그래머 기준으로 좀 괴악하기때문에 대중적인 함수형 언어들중에는 순수성을 포기하고 명령형 언어적인 부분을 포함하여 destructive update 를 허용하는 경우도 있다. ], 이미 a의 다른 정의가 있었다면 컴파일러가 중복 정의가 있다며 에러를 뱉어낸다. 하스켈같은 경우, 아예 a=a 를 ㅗ(논리학에서의 falsum) 로 정의한다.[* 물론, 물론, 언어 내의 Boolean type 의 false 가 아닌, 컴퓨팅 모델 자체의 false 값을 의미한다. ]
일반 프로그래머의 상식으로는 도저히 이해가 안 될 결정인데 일부러 [난해한 프로그래밍 언어]라도 만들 생각으로 만들었을까? 그게 아니고 함수형 언어는 수학의 '함수'를 프로그래밍 언어 설계에 적극적으로 반영한 것이다. 수학의 함수는 입력이 같으면 출력도 같다. 그러니까 f(x)=x+1 인 함수를 정의했다면 f(1)=2다. 다른 답은 없다. 함수형 언어의 함수도 마찬가지로 function boo(1)의 수행 결과가 2였다면 언제 어느때든 boo(1)은 2만 나온다. 하지만 함수형 언어가 아닌 언어에서는 boo(1)이 3도 나올 수 있고 4도 나올 수 있다. 그러니까 C언어로 치면 이런 함수에 해당한다.
int inc(int a) { static int c=0; c=c+a; return c; }
이 함수에 1을 넣어 여러 번 호출하면 1, 2, 3, 4, 5, ...가 나온다. 순수 함수형 언어는 이게 안된다는 얘기다. 이 특징으로 얻는 이득으로 함수형 언어는 '캐싱'[* 좀더 전문적인 용어로는 Memoization이라고 한다. Memorization이 아닌 것에 주의!]이 가능하다. 그 함수를 호출한 파라메터(f(x)에서 x)을 알고 있고 그것을 수행한 결과를 안다면 다음에 호출할 때는 그냥 캐싱한 결과값을 돌려주면 된다. 두번째로 입력이 같으면 출력이 같다는 게 언어 차원에서 보장되기 때문에 손쉽게 병렬화([멀티스레딩])이 가능하다. 최근에 함수형 언어가 다시 각광받는 이유중에 하나로, 멀티코어 프로세싱이 요구되고 있는 현 상황에서 떠오르는 강력한 장점으로 꼽힌다. 그리고 함수를 first-class datatype[* C라면 int, char 등의 타입]으로 분류하기에 함수를 다른 함수에 인수로 바로 넘겨줄 수도 있고, 함수를 만드는 함수(함수를 반환값으로 가지는 함수)를 정의할 수도 있으며 생산성이 매우 뛰어나다. 코드가 매우 간결해지며[* 심지어 하스켈 같은 경우 너무 간결해서 오히려 이해하기가 어려운 것 같다는 불평이 나오기도 할 정도] 버그가 잘 생기지 않는 견고한 코드가 나오는 경향이 있다. 절차형 언어와는 달리 눈에 핏발을 세우고 변수가 어떻게 변화하나 추적할 필요가 없다며 함수형 언어 팬들은 자랑하고는 한다.
자료구조 상 destructive update[* 한번 정의한 변수의 값을 차후에 다른 값으로 업데이트 하는것 ] 가 허용되지 않기때문에 효율적인 자료구조와 알고리듬도 명령형 언어에 비해 상당히 달라지게 된다. 일반적으로 저런 destructive update를 사용하는 자료구조를 ephemeral data structure 라 하며, 순수 함수형 언어에서 사용되는 자료구조를 persistent data structure[* 자료구조를 업데이트 할때마다 계속 새로 자료구조를 만드는 모델. 순수 함수형 언어의 경우 한번 정의한 값을 바꿀수가 없어서 이게 선택이 아닌 필연이 된다. fully persistent data structure 에서는 심지어 이전버전의 자료에도 접근할 수 있다. ] 라 한다.
참고로 객체지향 언어와 함수형 언어는 서로 배타적인 개념이 아니다. 최근 함수형 패러다임이 유명세를 타면서 C++ 이나 파이썬등 명령형 언어들에서 앞다투어 함수형 언어의 기능을 탑재하고 있다. 또, F#, Scala, OCaml같이 OOP와 함수형 프로그래밍을 짬뽕해놓은 멀티패러다임 언어들도 많다. 사실 자바스크립트도 함수형 패러다임을 포함한다. ~~다들 명령형으로 짜서 잘 모를뿐이지 그러니까 굳이 함수형 언어를 안배우더라도 함수형 패러다임과 알고리듬정도는 배워두면 어느정도 도움이 된다.~~
함수형 언어의 시초는 아주 옛날에 개발된 [LISP]부터 시작해서 그 dialect 인 스킴([Scheme]) 이 있었고, 그 Scheme 의 dialect 인 자바가상머신에서 실행되는 클로저([Clojure]), 전화교환기용 언어에서 출발한 [Erlang], 타입 검증용 언어에서 시작된 [ML], 극단적인 언어 디자인으로 유명한 [Haskell] 등이 있다. 객체지향의 한계가 슬슬 드러나고 있는 현재 함수형 언어의 여러 특징들이 다시 주목받고 있다.
논리 프로그래밍
명령형 언어가 튜링머신에, 함수형 언어가 람다 칼큘러스에 기반하고 있다면, 논리 프로그래밍은 수리논리학의 First order logic(1차언어)를 모델로 사용하는 프로그래밍 언어의 총칭이다. 사실, 이바닥의 알파와 오메가인 Prolog 이 1차언어에 기반하고 있기때문에 이런식의 정의가 많이 쓰이지만, 실제론 Higher order logic, F-logic, linear logic 등 여러가지 다른 논리를 사용하는 언어도 있고, 이것들도 대부분 Prolog 에 기반하고 있는 경우가 많아서 다 논리 프로그래밍 언어라 한다. Clause의 집합이 곧 프로그램이 되며, Clause는 이쪽에서 가장 유명한 Prolog 언어를 예로 들면, Head :- Body 형식으로 정의된다. 이것은 If Body, then Head 즉, To solve Head, solve Body 식으로 해석할 수 있다. 사실 일반 프로그래머 눈에 위의 함수형 언어보다 더더욱 괴악해보이게 마련인데, 그래도 튜링 컴플리트이며, C++ 등과 같은 General purpose 언어이다. 함수형 언어에서 프로그램을 함수들의 집합으로 보고 있다면, 논리 프로그래밍에서는 프로그램을 공리들의 집합으로 보고 있다고 이해하면 된다. 명령형 언어와는 거리가 멀지만, 함수형 언어와는 의외로 가까운편이고 실제 코드도 꽤 비슷한 양상을 띈다. 실제 코딩시의 함수형 언어와 가장 큰 차이라면 아무래도 함수형 언어가 '함수'를 사용할때, 논리 프로그래밍 언어에서는 '관계'(Relation)[* 수학적으로, 함수는 관계의 부분집합이다. ]를 사용한다는 부분이 가장 큰 차이일것이다. 관계를 사용함으로서 연역되는 차이는, 함수의 경우 하나의 인풋에 하나의 출력값만을 보장하게끔 정의가 되어있지만, 관계는 이런 제약이 존재하지 않기때문에 보통 조건을 모두 만족시키는 결과값을 전부 내놓는다. 이런 특징때문에 Constraint programming 에도 많이 쓰이고, 특히 일반적인 프로그래머가 가장 쉽게 접할 수 있는 예시가 데이터베이스 쿼리이다.
다만, Prolog 은 논리 프로그래밍 언어지만, 순수 선언형 언어는 아니다. Mercury 라는 순수 선언형 논리 프로그래밍 언어도 있고, 스페인에서 팍팍 밀어주는 Ciao 도 순수 선언형 서브시스템을 지원한다. 이쪽 언어들은 Prolog 을 제외하면 Prolog 을 기반으로 해서 여러가지 실험적인 확장을 시킨것들이 많기때문에, 유저 매뉴얼이 곧 논문인 경우가 많고, 수리논리학 이론을 잘 모를경우 접근하기 힘든것들이 대부분이다. 하지만, 그렇다고 이론단계에만 머물러 있는 프로그래밍 패러다임은 아니고, Sicstus 같은 상용 컴파일러도 있으며, NASA 같은곳에서도 사용하는등 의외로 쓰이는곳이 있는편이다.
특수 목적 언어
일반적이지 않은 환경에서 쓰이는 언어로 데이터베이스용 언어인 [SQL], [GPU]를 제어하는 언어인 [CUDA]같은 것도 있다. 인공지능 언어인 [Prolog], 그래픽스 처리를 위해 고안된 [Processing], 공학용 시뮬레이션에 특화된 MATLAB, 웹 사이트를 만드는데 특화된 [PHP]같은 것도 특수 목적 언어에 속한다.
특수 목적 언어의 특징은 해당 분야에서는 뛰어난 생산성을 보인다는 것이다. 그러나 해당 분야를 벗어나면 개발이 불가능하거나 오히려 더 난해해진다. 물론 프로젝트 하나를 통째로 한 가지 언어로만 개발하라는 법은 없기 때문에 여러 개의 특수 목적 언어와 일반 목적 언어(대부분 C언어)를 조합해서 프로젝트를 진행하는 경우도 많다.
종류
구체적인 언어의 종류는 해당 항목을 참조바람.
코드 예제
해당 항목을 참조바람.
기타
사실 프로그래머들이 아니면 다른 사람들이 볼 일이 없지만 최근에는 진부함을 탈출하려고 하는 몇몇 한국 양판소에서도 쓰인다. 물론 쓰인다고 해서 꼭 양판소라고는 할 수 없다. 라노베 계열중에서도 알기 쉬운 현대마법같은 작품도 있지만, 수작에 들어가는 편. 하지만 진짜(?) 양판소라면...더 이상의 자세한 설명은 생략한다.