클로저(Clojure) 코드는 정말 읽기 어려운가?

Gravatar for bakyeono@gmail.com
박연오프로그래머/개구리 애호가
2022. 06. 17.

이 글은 2022 송파 클로저 밋업 행사의 발표입니다. 유튜브에서 비디오를 볼 수 있습니다.

여러분 안녕하세요? 소중한 금요일 저녁 시간에 클로저 밋업에 참석해주셔서 감사합니다. “클로저 코드는 정말 읽기 어려운가?”라는 주제로 발표를 맡았습니다. 발표 주제의 이름을 잘못 지은 것 같기도 합니다. "클로저 코드는 왜 이렇게 읽기 쉬울까?"라고 했으면 더 좋았을 것 같네요. 클로저가 어렵다는 편견이 있는 편인데, 그렇지 않다는 걸 말씀드리려고 합니다.

리스프(LISP)

클로저는 리스프라는 프로그래밍 언어의 한 방언입니다. 리스프는 1958년에 처음 나온, 무려 64년이나 된 옛날 언어입니다. 리스프는 항상 새로운 아이디어로 가득한 혁신적인 언어였습니다. 하지만 리스프의 방언들은 인기가 높지는 않습니다. 그 이유가 뭘까요?

리스프가 배우기 어렵고 코드를 읽기 어렵기 때문이라는 주장을 종종 들어요. 이런 주장에는 두 가지 가정이 있는데요, 하나는 언어의 인기가 언어의 내재적인 특성에 근거한다는 가정입니다. 두번째 가정은 리스프가 어렵다는 것입니다.

저는 이런 가정에 동의하지 않습니다. 먼저, 언어의 인기에는 언어의 특성보다는 외부적인이 더 영향이 커요. 예를 들어, 에스페란토보다는 영어가 훨씬 널리 쓰입니다. 그건 영어가 에스페란토보다 더 쉽기 때문이 아닙니다. 최근에 외국인들 사이에서 한국어의 인기가 높아졌고, 1980년대에 나온 파이썬이 최근에야 널리 사랑받게 되었죠. 이 또한 한국어나 파이썬이 갑자기 전보다 쉬워졌기 때문이 아닙니다. 언어를 둘러싼 사회적 배경이 더 중요하죠.

Write-only Language?

리스프를 놀리는 말 중에 '쓰기 전용'이라는 말이 있어요. 코드 작성자도 코드를 못 읽는다는 거죠.

“유지보수하기 어렵게 코딩하는 방법”이라는 책이 있는데요 나쁜 코딩 스타일을 반어법으로 소개하는 재미있는 책입니다. 이 책은 반쯤은 유머로 이루어져 있는데요. 리스프로 환상적으로 읽기 어려운 코드를 작성할 수 있다고 소개하는 내용이 있습니다.

그런데 이 코드는 읽기가 매우 어려운 게 사실입니다. 이건 일부러 읽기 어렵게 짠 코드이니까요. 하지만 일부러 읽기 어렵게 만든다면, 다른 프로그래밍 언어로도 읽기 어려운 코드를 만들 수 있습니다.

1(function(_0x26ea4d,_0x1a7432){var _0x32fcab=_0x4ac1,_0x459153=_0x26ea4d();while(!![]){try{var _0x3aa247=-parseInt(_0x32fcab(0x188))/0x1+parseInt(_0x32fcab(0x187))/0x2*(parseInt(_0x32fcab(0x18d))/0x3)+parseInt(_0x32fcab(0x18b))/0x4*(-parseInt(_0x32fcab(0x18c))/0x5)+-parseInt(_0x32fcab(0x18a))/0x6*(parseInt(_0x32fcab(0x186))/0x7)+-parseInt(_0x32fcab(0x189))/0x8+parseInt(_0x32fcab(0x18f))/0x9+parseInt(_0x32fcab(0x18e))/0xa;if(_0x3aa247===_0x1a7432)break;else _0x459153['push'](_0x459153['shift']());}catch(_0x4b5609){_0x459153['push'](_0x459153['shift']());}}}(_0x1a2d,0xb965f));function hi(){var _0x3abc24=_0x4ac1;console[_0x3abc24(0x190)]('Hello\\x20World!');}function _0x4ac1(_0x4ed394,_0x61ef96){var _0x1a2de4=_0x1a2d();return _0x4ac1=function(_0x4ac138,_0x495466){_0x4ac138=_0x4ac138-0x186;var _0x2f2dbd=_0x1a2de4[_0x4ac138];return _0x2f2dbd;},_0x4ac1(_0x4ed394,_0x61ef96);}hi();function _0x1a2d(){var _0x8ac2fc=['24304ySzTVX','286370zkQpmn','9621232DxRZqZ','1050888RPzHkL','930156jIVjDv','15ICQjgj','183oGpdZN','13070690XlMPPp','11231883UJewfa','log','14jghHbE'];_0x1a2d=function(){return _0x8ac2fc;};return _0x1a2d();}

코드를 읽기 어렵게 만들어주는 “난독화”프로그램도 있죠. 이 코드는 자바스크립트 코드를 난독화한 것인데, 자바스크립트 전문가가 읽기에도 난해할 겁니다. 리스프는 이런 놀림의 대상이 될 때가 종종 있어요. 저도 이런 유머를 좋아해요. 하지만 유머는 유머일뿐인 것이지, 그게 꼭 사실은 아닐 수도 있어요.

1(-> 1000
2 inc
3 (+ 500)
4 -
5 str)

그런데 클로저를 처음 접하는 분들이 보기에는 이 코드처럼 정상적인 클로저 코드도 난독화된 코드와 비슷하게 어렵게 보일 수도 있습니다. 그건 우리 뇌가 낯선 것과 어려운 것을 혼동하기 때문입니다. 리스프를 배울 때는 코드의 모양도 다르고 새로운 개념도 많습니다. 이전에 배운 언어와 다른 점이 많습니다. 하지만 리스프와 클로저를 배울수록, 그 낯선 것들이 오히려 코드를 더 쓰고 읽기 쉽게 해준다는 걸 알게 됩니다.

애리티(arity)

애리티라는 개념이 있습니다. 어떤 연산자나 함수가 취할 수 있는 인자의 개수를 뜻합니다.

애리티
단항 연산자 (unary)! x
이항 연산자 (binary)x + y
삼항 연산자 (ternary)condition ? x : y
무항 연산자 (nullary)?
N항 연산자 (N-ary)?

C 언어의 연산자 중 몇 가지를 애리티에 따라 분류해 보았는데요. 단항 연산자로는 부정 연산자, 증가 연산자 등이 있습니다. 이항 연산자는 and, or, 더하기, 나누기 등이 있습니다. 그리고 조건부 식 연산자는 삼항 연산자죠.

그렇다면, 인자가 아예 없거나, 인자가 여러개인 N항 연산자도 생각해볼 수 있지 않을까요? 이런 연산들은 어떻게 표기해야 할까요?

연산자의 모양

"형태는 기능을 따른다." - 루이스 설리반

수학 연산자들은 일찍부터 형태가 정해졌습니다. 이러한 기존 형태에서는 넣을 수 있는 인자의 수가 제한적이어서, 가변적인 N항 연산자를 표현할 수가 없습니다.

프로그래밍 언어들은 연산자로 표현할 수 없는 N항의 연산을 위해 함수를 사용하고 있습니다. 함수의 형태에서는 취할 수 있는 인자의 개수가 제한되지 않습니다. 0개부터 N개까지 가변적인 애리티를 가질 수 있어요. 형태만 본다면, 함수는 연산자의 일반적인 표기법이라고 할 수 있습니다.

S-식(S-expression)

리스프에서는 이 함수의 형태을 응용한 S-식이라는 형식으로 연산자를 표기합니다.

(연산자 인자1 인자2 인자3 ... 인자N)

괄호를 열고, 연산자를 가장 앞에 적고, 인자들은 그 뒤에 나열합니다.

연산자C클로저
단항 연산자! x(not x)
이항 연산자x + y(+ x y)
삼항 연산자condition ? x : y(if condition x y)

S-식에서는 단항 연산자, 이항 연산자, 삼항 연산자의 표기법이 모두 동일합니다. 연산자 자리에 함수를 대신 넣어도 돼요, 사실 리스프에서는 연산자와 함수가 같은 것입니다.

S-식은 흔히 알려진 것처럼 그렇게 생소한 것이 아닙니다. 사실, 다른 언어의 함수 호출과 거의 똑같은 형태입니다.

파이썬클로저
abs(x)(abs x)
map(f, sequence)(map f sequence)
f4(f3(f2(f1(x))))(f4 (f3 (f2 (f1 x))))

이 표는 파이썬과 클로저를 비교한 것인데요, 보다시피 여는 괄호의 위치만 다를 뿐 표기법이 거의 똑같죠.

리스프에서는 함수와 연산자가 같습니다. 모양도 같고, 실행하는 방식도 같습니다. 이처럼 규칙에 일관성이 있으면 더 쉽습니다.

가변 애리티

S-식에는 몇가지 장점이 더 있습니다. S-식에서는 하나의 연산자가 가변적인 애리티를 가질 수 있습니다.

이 예는 클로저의 + 함수인데요. 원래는 + 연산자는 이항 연산자로서 애리티가 2로 고정되어 있습니다. 하지만 클로저에서는 보다시피 인자가 0개인 경우, 1개인 경우, N개인 경우가 모두 가능합니다.

가변 애리티를 활용하면 비교 연산자도 두개가 아니라 한꺼번에 여러 개의 인자를 받을 수 있고, 여러 값들을 비교할 수 있습니다. and 연산자도 여러 개의 인자를 취하여, 식들이 모두 참인지 확인할 수 있습니다.

그런데 마지막의 and 예는 조금 읽기가 어려워 보입니다. 인자가 많다보니 연산에 들어가는 인자가 무엇무엇인지 한눈에 들어오지 않습니다.

인자가 많거나, 코드가 길어질 때는 인자들 사이를 개행해주면 가독성이 좋아집니다.

평가 우선순위

S-식에서는 연산자의 평가 우선순위에 대해 신경쓸 필요가 없습니다. 모든 연산이 괄호로 싸여 있고, 괄호만 보면 무엇이 어떻게 결합해서 평가될지 알 수 있으니까요.

이 예는 둘 다 윤년을 검사하는 식입니다. C 언어에서는 연산자 우선순위를 정확히 알아야 코드를 읽을 수 있죠. 하지만 클로저에서는 모든 연산이 괄호로 싸여있기 때문에, 우선순위에 대한 고민을 할 필요가 없어요.

지금까지 S-식의 장점을 살짝 둘러봤습니다. 리스프는 쓸데없이 괄호가 많은 오래된 언어라는 놀림을 받곤 합니다. 하지만 사실은, 이러한 형태에는 그 형태가 따르는 기능이 있는 것입니다. 사실 S-식이 정말 가치있는 이유는 S-식에서 "코드가 곧 데이터이고, 데이터가 곧 코드이다"라는 것인데, 이 미친 놀라운 특징은 앞으로 클로저를 더 탐구하게 되실 여러분이 직접 맛보실 기쁨으로 남겨두겠습니다.

코드를 읽는 흐름

이번에는 약간 다른 측면을 살펴보겠습니다. 여기 서로 다른 나라의 글들을 가져와 보았는데요.

왼쪽은 한문으로 쓴 훈민정음, 가운데는 아랍어로 쓴 쿠란, 오른쪽은 라틴어로 인쇄한 창세기입니다. 혹시 이 글들에 공통점이 있다는 걸 눈치채셨나요?

한문은 위에서 아래로 읽고, 아랍어은 오른쪽에서 왼쪽으로, 라틴어는 왼쪽에서 오른쪽으로 읽습니다. 언어에 따라서 가로로든, 세로로든, 방향의 차이는 있지만, 우리 언어는 글을 선형으로 읽고 쓴다는 공통점이 있습니다.

그런데 프로그래밍 언어는 선형으로 읽는 방식이 아닙니다.

첫번째 코드는 왼쪽에서 오른쪽으로 읽는 코드인데요, 아래쪽 코드처럼 안에서 밖으로 읽어야 되는 코드도 있어요. 함수 호출을 하면 안쪽에서부터 코드가 실행되니까요. 프로그램 코드에는 이런 코드들이 마구 뒤섞여 있죠.

우리는 글을 읽을 때 한쪽 방향으로 읽는 것에 익숙하기 때문에, 이렇게 글을 읽는 흐름이 여러 방향으로 섞이면 읽기가 어렵습니다. 이런 문제는 클로저만이 아니라 함수 호출 기능이 있는 다른 프로그래밍 언어에도 똑같이 있어요.

스레딩 매크로

클로저는 이 문제를 해결하는 도구를 제공합니다. 스레딩 매크로라는 녀석이예요.

스레딩 매크로를 이용하면 안에서 밖으로 읽는 코드를, 좌에서 우로 읽도록 작성할 수 있어요.

이 두 코드는 같은 연산식인데요. 왼쪽 코드는 함수를 어디서부터 읽어야 할지 약간 헷갈립니다.

하지만 오른쪽 코드는 1000에서 시작하고, inc라는 함수로 값을 1 증가시키고, 500을더하고, 음수로 바꾸고, 문자열로 바꾼다는 순서를 바로 알 수 있습니다.

다른 언어에서도 이 문제를 해결하기 위해 메서드 체인 같은 기법을 이용하기도 합니다. 메서드 체인은 특정 타입의 객체와 메서드로만 수행할 수 있지만, 스레딩 매크로는 어떤 데이터에 대해서든, 어떤 연산자나 함수로도 수행할 수 있어 더 일관성이 있습니다.

재귀 (recursion)

리스프는 재귀를 적극적으로 사용하는 언어입니다. 그런데 재귀 때문에 리스프를 좋아하지 않는 분도 계시지 않을까합니다. 재귀에 대해서 막연히 어렵게 느끼시는 분들이 많으시거든요. 하지만 재귀는 수학적 귀납법으로 정의한 식을 코드로 간단히 옮길 수 있게 해줘요. 그런 용도로 쓰면 쉽고 우아한 코드를 짤 수 있어요.

재귀에 대한 자세한 내용을 다룰 수는 없고, 클로저로 연결 리스프를 만드는 예를 간단하게 살펴보겠습니다.

리스트 잇기 (cons)

리스프에서는 cons라는 함수로 연결 리스트를 만듭니다. cons는 첫번째 인자로 리스트에 추가할 요소를, 두번째 인자로 다른 연결 리스트를 입력받아요.

리스트를 처음 만들 때는 두번째 인자에 그냥 nil을 넣습니다. 이렇게 (cons 1 nil)을 평가하면, 원소가 1만 들어있는 리스트가 되고요.

여기에 다른 요소를 추가하려면, cons를 한번 더 붙이면 돼요. 이렇게 2를 추가해주면, (2 1)이 되죠.

또 한번 더 하면, (3 2 1)이 됩니다.

이런식으로 cons을 계속 덧붙여서 연결 리스트를 계속 이어만들 수 있어요.

그러면 재귀와 cons를 이용해서, 무한한 자연수의 시퀀스를 정의해 보겠습니다.

자연수의 시퀀스는 첫번재 요소가 0이고, 그 다음 요소는 이전 요소에 1을 더한 수이다. 이렇게 재귀적으로 자연수의 시퀀스를 정의해 보았어요. 이렇게 하면 연쇄적으로 시퀀스의 모든 요소를 구할 수 있겠죠.

이것을 연결 리스트를 만드는 cons로 옮기면 아래쪽 코드처럼 될 겁니다. cons 0 cons 1 cons 2 cons 3 ... 하면서 계속 이어지는 거죠. 물론, 리스트의 요소를 직접 다 나열할 수가 없습니다. 그래서 이를 자기 참조 구조, 즉 재귀로 정의합니다.

cons의 첫번째 원소는 n이 되고요, 그리고 cons의 두번째 원소로는 리스트를 넣어야 되는데, 무한 자연수 리스트를 만드는 함수, 그러니까 지금 정의하고 있는 함수를 호출해서 넣어줍니다. n에 1을 증가 시켜서요.

그리고 이 함수에 0을 넣어서 실행하면 재귀적으로 무한한 리스트가 만들어집니다. 자연수의 무한 시퀀스를 만들었네요.

그러면 실제로 호출을 해볼 건데요, 무한한 시퀀스를 한꺼번에 다 가져오면 스택 오버플로 에러가 날 겁니다. 그래서 시퀀스에다가 (take 10)을 적용해서 처음 열 개의 요소만 구하도록 합니다.

하지만 실제로 실행해보면, 역시나 스택 오버플로 에러가 발생하게 됩니다. take 10을 하기 전에 무한한 시퀀스를 끝까지 만들기 때문입니다.

지연 시퀀스 (lazy-seq)

이 문제를 해결하려면, 리스트를 필요한 데까지만 계산하는 방법이 필요합니다. 그래서 클로저는 지연 시퀀스라는 도구를 제공합니다.

지연 시퀀스는 시퀀스의 각 요소들을 계산하지 않고 미루고 있다가, 요소를 정말로 써야할 때, 딱 필요한 그 요소까지만 비로소 계산하여 구해줍니다.

클로저에서 지연 시퀀스를 만들려면 리스트를 lazy-seq로 감싸주면 됩니다. 이런 식으로 cons 앞에 lazy-seq 로 한번씩 감싸주는 거죠.

그러면 원래 정의했던 무한 시퀀스 함수에, lazy-seq를 넣어봅니다. cons 를 lazy-seq로 감싸기만 하면 되죠.

이렇게 무한 시퀀스를 지연 시퀀스로 정의해놓으면, 필요한 데까지만 평가해서 값을 구할 수 있습니다. 이제 스택 오버플로가 발생하지 않아요.

이렇게 클로저에서 재귀를 이용하는 방법을 알아보았습니다. 참 쉽죠?

흐음...

하지만 "이것도 어렵다! 불편하다! 리스트? 클로저? 그딴 건 역시 못해 먹을 물건이군!"이라고 생각하시는 분들도 계실 겁니다.

시퀀스 추상 (sequence abstraction)

저는 그 불편한 느낌이 정당하다고 생각해요. 다행히도 클로저에서는 우리가 재귀를 직접 써야 하는 경우가 거의 없어요. 클로저에서는 여러가지 반복되는 패턴들을 시퀀스 라이브러리를 이용해서 추상화해두었기 때문이에요. 실제로 그린랩스의 프로덕션 코드에는 재귀를 직접 작성한 경우가 단 한 곳도 없습니다!

앞에서 작성했던 자연수의 무한 시퀀스는 (iterate inc 0) 이라는 시퀀스 함수를 사용해서 간단히 나타낼 수 있어요.

이 둘은 똑같이 무한한 자연수의 시퀀스를 만들어냅니다.

iterate는 함수와 초깃값을 인자로 받아, 초깃값에 함수를 재귀적으로 적용하는 무한 지연 시퀀스를 만들어줍니다.

iterate 함수를 이용하면 재귀가 훨씬 더 쉽게 느껴지죠. 왜일까요? 앞에서 재귀 시퀀스를 직접 작성했을 때 어렵게 느껴졌던 이유는, 재귀 과정에서 발생하는 ‘패턴’을 프로그래머가 직접 작성해야 했기 때문이에요.

프로그래밍이란, 문제와 무관한 요소들을 모두 배제할 때, 정복하지 못한 복잡성을 피하고, 여러 고려사항들을 매우 격렬하게 분리할 수 있는, 가장 효율적인 사고방식 -- 그 이상도 그 이하도 아닌 바로 그것이다. - 데이크스트라

데이크스트라는 GOTO 문으로 반복 패턴을 짜는 걸 비판했고, 우리는 이제 GOTO를 이용해 패턴을 직접 작성하는 대신에 while, for, 서브루틴(함수), throw-catch 등을 사용하죠. 하지만 패턴은 여전히 많습니다.

나는 프로그램 안에서 패턴을 발견하면 그것을 뭔가 문제가 발생하고 있다는 신호로 받아들인다. 프로그램의 형태는 오직 그것이 해결해야 하는 문제만 반영해야 한다. 코드에 존재하는 그 밖의 모든 정형성은 내가 충분히 강력하지 않은 추상을 이용하고 있다는 신호로 다가온다. - 폴 그레이엄

프로그램이 다루는 건 문제 그 자체에 대한 것이어야지, 문제 외의 요소가 끼어들어서 프로그램을 복잡하게 만들어서는 안 됩니다. 코드를 짤 때 어떤 패턴이 반복적으로 나타나고, 프로그래머가 그 패턴을 직접 제어해야 한다? 그건 문제 밖의 요소입니다. 그건 원래 프로그래밍 언어가 기능으로 제공해주어야 하는 부분인데, 개발자가 패턴을 짜고 있다는 건, 언어가 할 일을 프로그래머가 대신하고 있는 걸 뜻해요.

앞의 예를 다시 살펴보면, 문제의 본질은 기저값 0과 귀납 함수 inc입니다. 그리고 그 함수를 반복적으로 적용하여 시퀀스를 만든다는 것이 핵심이죠. 그런데 지연 시퀀스를 재귀적으로 만드는 게 프로그래밍에서 자주 나타내는 패턴인 거고, 그걸 함수로 추상화해놓으면 프로그래밍이 훨씬 쉬워집니다. 패턴을 제거하면 불필요한 복잡성이 없어지고, 문제의 핵심인 0과 inc만 남습니다.

클로저에는 프로그래밍에서 자주 발견되는 여러 가지 패턴들을 시퀀스 라이브러리라는 형태로 일반화 했습니다. 프로그래밍 클로저라는 책에 실린 예제를 한번 살펴보겠습니다.

1public class StringUtils {
2 public static boolean isBlank(String str) {
3 int strLen;
4 if (str == null || (strLen = str.length()) == 0) {
5 return true;
6 }
7 for (int i = 0; i < strLen; i++) {
8 if ((Character.isWhitespace(str.chatAt(i)) == false)) {
9 return false;
10 }
11 }
12 return true;
13 }
14}

이 자바 코드는 아파치 커먼스 라이브러리에서 가져온 모범 코드입니다. 문자열이 비어 있거나 공백 문자만으로 되어 있는지 검사하는 함수네요. for 문 안에서 문자열을 순회하면서 공백 문자를 검사하고 있어요. 코드 전체가 클래스 안에 정의되어 있기도 합니다. 그런데 제가 보기에는 이런 코드는 불필요한 복잡성으로 느껴집니다.

1(defn blank? [s]
2 (every? #(Character/isWhitespace %) s))

클로저 코드에서는 두 줄이면 끝납니다. 함수의 본문은 한줄이네요. 모두 참인가? 각 문자가 공백문자인가? 라는 것으로 정의가 끝입니다. 이처럼 클로저에서는 불필요한 복잡성을 최대한 배제하고, 문제 그 자체에 따르는 필연적인 복잡성만을 다룰 수 있도록 언어에서 많은 지원을 하고 있습니다.

지금까지 살펴본 것

  • S-식
    • 연산의 형태를 일반화한다.
    • 연산이 여러 개의 애리티를 가질 수 있게 한다.
    • 가변 애리티로 코드가 더 간결해진다.
    • 연산의 결합이나 우선순위에 대한 고민을 없앤다.
  • 스레딩 매크로
    • 코드를 읽는 방향을 자연스럽게 만든다.
  • 재귀
    • 자신을 참조하는 연산이나 데이터 구조를 쉽게 정의한다.
    • 무한 지연 시퀀스를 만들 수 있다.
  • 시퀀스 추상
    • 프로그램에서 반복되는 패턴을 간단한 구조로 나누어 제거한다.

지금까지 클로저 코드를 읽기 쉽게 해주는 여러 기능들을 알아봤습니다. 이런 기능들은 처음에는 낯설지만, 배우고 나면 클로저 코드를 더 간단하고 쉽게 쓰고 읽을 수 있게 도와줍니다.

쉽고 어려움을 판단하는 일에는 주관적인 성격이 있지요. 처음 배우는 동안에는 학습 비용이 든다는 측면에서는 어려울 수 있습니다. 하지만 일단 기초를 익힌 후에는, 코드를 읽고 쓰는 노력이 훨씬 덜 들고, 저는 그걸 '쉽다'고 하겠습니다.

함수형 언어를 배워야 하는 이유

"아동이 숙달한 언어구조는 인지의 기본 구조가 된다." - 비고츠키

언어는 사고를 지배합니다. 언어가 없이도 생각을 할 수 있을 것 같지만, 언어를 사용하지 않으면 흐릿한 심상이 떠올랐다 사라질 뿐입니다. 논리적이고 체계적인 사고는 언어를 통해서만 가능하죠.

그래서 언어는 커뮤니케이션의 수단이기만 한 것이 아닙니다. 언어를 정확하고 정교하고 효율적으로 사용하는 것은 우리의 인지, 결정, 행동, 삶에 영향을 끼치는 매우 중요한 문제입니다.

프로그래밍 언어도 마찬가지입니다. 언어는 그냥 프로그램을 만드는 도구일 뿐이고, 아무거나 고르면 된다고 말씀하시는 분들이 많습니다. 튜링 완전성에 대한 이야기라면 맞는 말이겠지만, 문제를 어떻게 풀것인가도 중요하게 생각한다면, 언어도 잘 골라야 합니다. 프로그래밍 언어는 프로그래머의 사고방식과 문제 해결 방법에 큰 영향을 끼치기 때문입니다. 다른 패러다임의 언어를 익히면 새로운 방법으로 사고할 수 있습니다.

"인간의 운명은 인간의 수중에 있다." - 사르트르

우리는 한국어를 모국어로 사용하고 있죠. 그런데 이 말은 우리가 원해서 선택한 게 아닙니다. 태어나보니 한국어를 쓰는 환경이었던 거죠. 모국어가 무엇이냐는 우리에게 주어진 운명이라고 할 수 있어요.

하지만 인간은 운명을 스스로 개척하는 생물이지 않습니까? 새로운 프로그래밍 언어를 배우는 것은 우리가 직접 선택할 수 있는 운명입니다. 새 프로그래밍 언어를 배우는 데는 수고가 들지만, 처음에 두려워 보이는 것만큼 어렵지는 않습니다. 그리고 새로운 점이 많을수록 얻는 게 더 많습니다.

오늘부터 운명을 바꾸는 행동을 한번 해보시는 건 어떨까요?

감사합니다.

참고 자료


추천 콘텐츠