이 글의 내용은 다음을 참조하여 작성되었습니다.🙇♂️
머릿속에서 일어나는 일, 곧 단순한 아이디어들을 가지고 쓸모 있는 아이디어를 만들어 내는 일은 주로 다음 세가지로 나뉜다.
- 여러 단순한 아이디어를 한 아이디어로 묶어내기 -> 모든 복잡한 아이디어의 근원
- 단순한 아이디어와 복잡한 아이디어를 하나로 묶지 않고 함께 꺼내 쭉 훑어보도록 펼쳐놓는 것 -> 관계에 대한 모든 아이디어 이끌어내기
- 실제 속에 복잡하게 얽혀 있는 다른 것들에서 아이디어를 분리하는 것 -> abstraction(추상화)라고 한다.
모든 일반적인 아이디어들은 이렇게 만들어진다고 한다.
계산 프로세스가 무엇인지부터 생각해보자.
계산 프로세스 : 컴퓨터 속에 있는 것, 데이터를 조작하면서 어떠한 일을 한다.
프로세스는 사람이 만든 규칙에 따라 움직이고,
이 규칙을 가리켜 "프로그램"이라 한다.
프로그램을 통해 우리의 주문대로 컴퓨터의 혼을 만들어내는 것이다.
프로세스(computational process)는 사람 대신 머리를 쓰고 질문에 답하거나 은행에서 돈을 찾거나 공장에서 로봇을 움직여 물건을 만들어가기도 하는 형태로 우리 세상살이에 영향을 준다.
이런 까닭으로 프로세스를 다스리는 프로그램은 마치 넋을 다스리는 마법사와 같다.
프로그램은 조심스럽게 써내려간 여러 식(symbolic expression)으로 이루어지는데, 이런 식을 적을 때 쓰는 언어를 프로그래밍 언어라고 한다.
컴퓨터가 고장나지 않는다면 프로세스는 프로그램에서 정한대로 일을 해내기 때문에.
초보 프로그래머는 견습 마법사처럼, 자신의 마법이 어떤 결과를 일으킬지 미리 알아차리는 방법을 배워야 한다.
조금만 실수(error,bug,glitch)를 저질러도 전혀 생각지도 못한 복잡한 결과가 튀어올 수 있기 때문이다.
진짜로 일하는데 쓸 프로그램은 깊은 지식고 경험을 갖춘 사람이 조심해서 짜야 한다.
CAD 프로그램 속에 작은 오류 하나가 설계한 비행기를 곤두박질치게 하거나 댐을 무너져 내리고 산업 로봇이 망가지는 사고로 이어질 가능성은 얼마든지 있다.
그러므로 소프트웨어 기술자라면 스스로 만든 프로세스가 맡은 일을 틀림없이 해낸다고 믿을 수 있도록 제대로 프로그램을 짤 줄 알아야 한다.
자기가 설계한 시스템이 앞으로 어떻게 움직일지 정확하게 그려낼 수 있어야 하고, 생각지도 못한 문제 때문에 끔찍한 사고가 터지지 않게끔 프로그램의 구조(structure)를 제대로 잡을 수 있고,
또한 미처 알아차리지 못한 문제가 터질 때에도 프로그램 속에서 그 문제를 일으키는 오류가 어디에 숨어 있는지 찾아낼 줄 알고, 제대로 고칠 줄도 알아야 한다.(오류잡기.debug)
그래서 잘 설계된 자동차나 핵 원자로 처럼, 제대로 된 컴퓨터 시스템은 모듈 방식(modular manner)에 따라 부품 단위로 만들고 고쳐 쓰고 갈아 끼울 수 있도록 설계 되어 있다.
크게 인기 있는 언어도 아닌데,
프로그램 구축에 왜 하필 Lisp를 고른 걸까?
Lisp 언어만이 갖춘 여러 특징에 그 까닭이 숨어있다도 한다.
그러한 개념과 기술들을 탐구하는데 Lisp가 가장 편리한 수단인 이유는?
프로그래밍 언어는 그저 컴퓨터에 할 일을 지시하는 수단이 아니다. 프로세스에 대한 사람의 생각을 짜임새 있게 담아내는 그릇이기도 하다.
언어를 설명할 때는 다른 무엇보다
단순한 생각을 모아 복잡한 생각을 엮어내는 수단에 무게를 두어야 한다.
좋은 프로그래밍 언어가 갖춘 세 가지 표현 방식
- primitive expression(기본 식) : 언어에서 가장 단순한 것
- means of combination(엮어내는 수단) : compound element, 간단한 것을 혼합하여 복잡한 형태로 만든다.
- means of abstraction(요약하는 수단) : 복잡한 것에 이름을 붙여 하나로 다룰 수 있게끔 간추림
기억해야겠다.
프로그램 짜는 것의 바탕은?
Expression -> Combination -> Abstraction
좋은 프로그래밍 언어에는 기본 데이터와 기본 프로시저를 나타내고, 프로시저들과 데이터들을 엮어서 더 복잡한 것을 만들고,
이를 간단하게 요약하는 수단이 반드시 있어야 한다.
나중에 프로그램과 데이터를 딱 자르지 않아도 된다는 것을 알게 된다고 한다.
numerical data(숫자 데이터)를 가볍게 다루면서 프로시저 짜는 방법에 초점을 둔다.
너즁애 compound data를 쓸 때에도 같은 방법으로 프로시저를 짠다는 것을 알게 될 것이다.
가볍게 Scheme 실행기를 띄워놓고 가지고 논다고 생각해보자.
식을 입력하면, 실행기는 그 식을 evaluation(계산, 셈)하여 값을 출력한다.
십진수를 나타내는 식은 실행기가 곧바로 셈할 수 있는 기본 식이다.
;;; primitive expression
;: 486
;;; compound expression
;: (+ 137 349)
;: (- 1000 334)
;: (* 5 99)
;: (/ 10 5)
;: (+ 2.7 10)
수를 나타낸 식과, 기본 프로시저를 나타낸 좀 더 복잡한 식이다.
이렇게 엮은 식은
맨 왼 쪽에 있는 식은 operator(연산자),
나머지 식은 operand(피연산자)가 된다.
엮은 식을 계산한 값은 피연산자의 값이 되는 argument(인자)에 연산자가 나타내는 프로시저를 적용하여 얻는다.
prefix notation(앞가지 쓰기, 연산자를 피연산자 왼쪽에)
;: (+ 21 35 12 7)
;: (* 25 4 12)
combination(식 속에 다시 식 여러 겹으로 엮어 늘리기)
;: (+ (* 3 5) (- 10 6))
;: (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))
실행기가 얼마나 복잡한 식을 받아들일 수 있는지 정해 놓은 규칙은 없다. 사람이 알아보기 어려워서 실수로 계산을 틀리기 쉽더라도 interpreter(언어 실행기)는 아무 문제없이 식의 값을 구하여 답한다.
같은 식을 쓰더라도 정리해서 적으면 사람도 알아보기 쉽다.
pretty-printing(가지런히 쓰기, 들여쓰기)
;: (+ (* 3
;: (+ (* 2 4)
;: (+ 3 5)))
;: (+ (- 10 7)
;: 6))
식이 아무리 복잡해도 실행기가 하는 일은 같다.
읽고, 셈하고, 출력을 되풀이한다고 한다.
언어 실행기는 계산한 값을 보여달라고 따로 청하지 않아도 알아서 셈한 값을 찍어 준다는 사실을 알아두자.