거의 잊혀졌지만 혁신적이었던 Self 언어와 그 독자적 개발 환경을 소개하고, 설치와 이미지 개념, REPL, Morphic UI의 철학(구체성·균일성·직접성·생동감)과 주요 도구(Outliner, Shell, Core sampler, Factory window, Radar, Transporter)까지 살펴본다.
비고:
@2019/02/02
수많은 언어의 탄생에 기여했지만 거의 아무도 모르는 언어의 소식을 전합니다. 다른 어떤 것과도 비교할 수 없는 가능성을 제공하지만 거의 아무도 사용하지 않는 그래픽 환경의 소식도 전합니다. 거의 무한대에 가까운 리플렉션을 지원하고, 한때는 수치 계산에서 C의 절반 성능을 달성했음에도 오늘날 잊혀진 가상 머신의 소식 또한 전합니다.
오늘은 Self에 대해 이야기하겠습니다. 여러 사람이 Self에 대해 이렇게 말했습니다:
Self는 (마치) Smalltalk인데, 더한 것이다.
Self는 메시지의 섬광이 흘러가는 객체의 바다다.
Self는 25년 전 과거에서 온, 여전히 단지 영감 그 이상인, 매우 진보된 미래상이다. 비전이라는 단어가 정확하다.
I liked Self. “Good OOP” is still waiting for a much better notion to replace the idea of a “Class”
(자유 번역: Self가 마음에 들었다. “좋은 OOP”는 아직 “클래스”라는 아이디어를 대체할 훨씬 더 나은 개념을 기다리고 있다.) – Alan Kay, https://news.ycombinator.com/item?id=11939851#11941380
The only other language of promise is Self, and that is unfunded and locked in non-development though so incredibly influential.
— http://www.drdobbs.com/architecture-and-design/interview-with-alan-kay/240003442?pgno=4
실전의 소프트웨어 고고학. 아무도 모르고 아무도 쓰지 않는 풍부한 보물. 수십만 줄의 잊힌 코드, 생각, 아이디어.
Self는 많은 것을 내포합니다. 그러니 여러 관점에서 살펴보겠습니다.
Self는 프로그래밍 언어, 바이트코드 인터프리터, 그리고 이 인터프리터가 실행하는 메모리 “이미지”(이하 image) 시스템입니다.
실용적으로는 가상 머신이 들어 있는 바이너리와, 환경을 구성하는 객체들이 저장된 별도의 image 파일로 구성됩니다. 일반적인 스크립트 언어와 상황이 조금 비슷한데, 스크립트를 실행하는 대신, 지난번 스크립트가 끝났던 상태의 메모리 이미지를 실행하는 셈입니다.
메모리 이미지는 전체 개발 환경과 Xlib에 바인딩된 자체 그래픽 인터페이스를 포함합니다.
또한 언어용 컴파일러, 전체 표준 라이브러리 등이 들어 있습니다.
Self 프로그래밍 언어는 프로토타입 기반의 객체지향 언어로, Smalltalk 계열에 속합니다.
_프로토타입 기반_이란, 객체 생성을 위해 클래스를 사용하지 않는다는 뜻입니다. 새로운 객체는 복사로 만들거나, 문법 수준에서 소스 코드로 새 객체를 생성합니다.
_Smalltalk 계열_이란, 모든 것이 객체에 _메시지_를 보내는 행위 중심으로 돌아간다는 뜻입니다. 몇 가지 기본적인 _문법적 설탕(syntactic sugar)_을 제외하면, 이곳에서는 메시지를 주고받는 것 외에 다른 것이 없습니다.
Self 언어는 배우기 매우 쉽습니다. 사실상 언어의 모든 특성이 주석과 설명을 포함해 A4 한 페이지에 담깁니다.
간단한 개요는 여기까지. 자세한 내용은 아래 장들에서 다루겠습니다.
인터프리터와 기본 이미지는 공식 사이트 http://www.selflanguage.org/ 에서 받을 수 있습니다. GNU/Linux와 Mac OS X용 바이너리 배포판이 제공됩니다. 또한 OpenSource 라이선스의 소스 코드도 제공되며, 이론상 어디서든 컴파일할 수 있습니다.
하지만 실제로는 그리 간단하지 않을 수 있습니다. 그래픽 서버 바인딩과, 특정 프로세서 계열에서 직접 디버깅해야 하는 JIT 컴파일러 때문입니다.
압축을 풀면 주요 파일과 보조 파일들이 보입니다. 주요 파일은 다음과 같습니다:
Selfmorphic.snap첫 번째는 인터프리터 실행 바이너리이고, 두 번째는 그래픽 환경이 사전 준비된 상태로 저장된 메모리 이미지 파일입니다.
실행 후 도움말을 표시할 수 있습니다:
$ ./Self -h
./Self: usage: ./Self [-f filename] [-h] [-s snapshot] ...
Options:
-f filenameReads filename (Self source) immediately after startup
-hPrints this message
-pDon't do `snapshotAction postRead' after reading snapshot
-s snapshotReads initial world from snapshot
-wSuppress warnings about optimized code being discarded
For debugging use only:
-FDiscards saved code from snapshot
-l logfilewrite spy log to logfile
-rDisable real timer
-tDisable all timers
-cUse real timer instead of CPU timer (for OS X)
-oOversample the timers (Run them 10x faster to flush out bugs)
-aTest the Assembler (added for Intel)
Other command line switches may be interpreted by the Self world
도움말 대신 아래와 같은 메시지가 보인다면
./Self: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory
64비트 시스템에서 32비트 프로그램 지원이 설치되어 있지 않은 것입니다. Debian에서는 다음과 같이 필요한 지원을 설치할 수 있습니다:
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libx11-6:i386 libxext6:i386 libtinfo5:i386
사실상 흥미로운 스위치는 두 개뿐입니다. .self 파일을 읽어들이는 -f(아마 자주 쓰지는 않을 것)와, 저장된 메모리 이미지를 읽는 -s. 나머지는 대부분 아주 숙련된 사용자에게만 유용한 기술적 세부사항입니다.
Self를 그냥 실행하면, 명령을 입력할 수 있는 REPL(대화형 모드)이 나타납니다:
$ ./Self
Self Virtual Machine Version 2017.1/13, Tue 16 May 17 00:45:42 Linux i386 (4.5.0-205-gd942ba2-dirty)
Copyright 1989-2016 AUTHORS (type _Credits for credits)
for I386: LogVMMessages = true
for I386: PrintScriptName = true
for I386: Inline = true
for I386: SICDeferUncommonBranches = false (not implemented)
for I386: SICReplaceOnStack = false (not implemented)
for I386: SaveOutgoingArgumentsOfPatchedFrames = true
VM#
Self는 인터프리터로서 보통 이미지 단위로 동작하고, 지금은 아무 이미지도 읽지 않았으므로, 내 앞의 인터프리터에는 몇 가지 내장 프리미티브 외에는 사실상 아무것도 없습니다. 표준 라이브러리도 없고, 메시지를 보낼 만한 객체도 거의 없습니다. 이 상태의 Self는 두 수를 더하는 것조차 못 합니다. 숫자가 무엇을 의미하는지조차 제대로 모르기 때문입니다.
도움말은 메시지(자세한 내용은 아래) help를 보내 출력할 수 있습니다:
VM# help
'
To begin using Self, you must read in the world of Self objects.
To read in the world, type:
'worldBuilder.self' _RunScript
When this process is complete, you will be at the Self prompt.
At the Self prompt, you can start the user interface by typing:
desktop open
' <0>: ( | parent* = <1>. | byte array: {10, 9, 84, 111, 32, 98, 101, 103, 105, 110, 32, 117, 115, 105, 110, 103, 32, 83, 101, 108, ... (258 more elements) } )
VM#
도움말(반환된 문자열 형태)은 그다지 유용하지는 않습니다. .self 스크립트의 경로가 들어 있는 문자열에 _RunScript 메시지를 보내, 세계를 불러오는 방법만 보여줍니다.
이는 직렬화된 소스 코드에서 새로운 “깨끗한” 이미지를 만들고 싶을 때 유용합니다. 소수의 내장 프리미티브를 이용해 표준 라이브러리를 구성하는 수천 개의 객체를 순차적으로 생성함으로써 달성됩니다. 몇 개의 공리로부터 시스템을 구축하는 것으로 볼 수도 있습니다.
동봉된 morphic.snap 파일에는 이렇게 구성된, 표준 라이브러리와 몇 가지 유용한 애플리케이션으로 이루어진 상호 연결된 객체 세계가 들어 있습니다. 또한 Unix/Linux에서는 Xlib, OS X에서는 Quartz 백엔드를 사용하는, Self로 완전히 구현된 자체 사용자 인터페이스를 포함합니다.
하지만 Self는 이 이미지를 사용하도록 강제하지 않습니다. 그래픽 인터페이스가 전혀 없는 이미지, 숫자가 로드되어 있지 않은 이미지, 혹은 웹서버만 있는 이미지 등도 만들 수 있습니다. 하나의 이미지에서 매우 많은 애플리케이션을 동시에 실행하는 것도 가능하지만, 실용상 저는 여러 개의 분리된 이미지를 운용합니다.
언어를 설명하기 전에, 먼저 Self의 사용자 인터페이스와 개발 환경의 기초를 설명하겠습니다.
Self를 처음 실행하면 흰색 화면, 일종의 데스크톱이 나타납니다. 많은 분께는 충격일 수 있겠습니다. 저도 처음 Self를 켰을 때 30초쯤 멀뚱히 보고 마우스를 이리저리 클릭하다가 그대로 종료하고 지워버렸습니다.
오늘날 시각에서 꽤 받아들이기 어려운 점은, 운영체제 Plan 9의 그래픽 인터페이스처럼 Self도 당시 새 주변기기였던 마우스에 대한 큰 열광 속에서 탄생했다는 사실입니다. 설계자들은 마우스가 매우 직관적인 조작을 제공할 것이라 기대했고, 그래서 전체 환경을 주로 마우스로 조작하도록 만들었습니다.
전통적인 GUI 앱에서 기대하는 바를 안다고 생각하신다면, 모르시는 겁니다. Self에서는 마우스가 단지 주된 입력 장치일 뿐 아니라, 메서드 코드를 입력하는 일을 제외하면 키보드를 건드릴 필요가 거의 없습니다. 어느 정도는 프로그래밍조차 마우스만으로 가능합니다.
바탕 화면을 클릭해 보면, 좌우 버튼은 아무것도 하지 않고 휠 버튼(가운데 버튼)이 메뉴를 띄웁니다.
이 메뉴에서(물론 마우스로) 기본 도구를 선택하고, 메모리 이미지를 저장하거나 종료할 수 있습니다.
이 부분을 어떻게 설명할지 꽤 오래 고민했습니다. 전체 그래픽 인터페이스가 하나의 프로그래밍 언어입니다. 프로그래밍 언어는 그래픽 인터페이스로 조작할 수 있고, 그 안에서 일어나는 일이 종종 그래픽으로 표현됩니다.
오른쪽 위에 shell이라고 적힌 직사각형은 명령을 입력할 수 있는 콘솔 같은 것입니다. 동시에, 프로그래밍 언어의 객체를 그래픽으로 표현한 소위 _outliner_이기도 합니다.
왼쪽 위의 화살표를 누르면 객체의 개별 슬롯(요소)을 펼쳐 볼 수 있습니다.
보시다시피, 이 객체에는 db*, shortcuts*, help 슬롯이 있습니다. 첫 두 개는 이른바 _parent 슬롯_으로, 찾지 못한 메시지의 위임(delegate)이 이루어지는 객체를 가리킵니다.
이 객체에 슬롯 이름과 같은 메시지를 보내면 그 내용을 반환합니다. 메시지는 그래픽으로도 보낼 수 있고, 반환도 그래픽으로 받을 수 있습니다(아래에서 시연).
객체 이름 옆의 줄임표를 누르면 객체의 문서화 주석을 볼 수 있습니다.
또한 오른쪽 위에 일련의 버튼이 보이는데, 제 시스템에 기대된 폰트가 없어 좀 이상하게 보입니다. /\ 표시 버튼은 parent 슬롯들을 표시합니다. 클릭하면 커서 위치에 나타나고, 다시 클릭해 화면에 내려놓을 수 있습니다.

이것이 실제로 _parent 슬롯_인지 여부는 parent 슬롯의 오른쪽 상자들을 클릭해 표시할 수 있습니다:
모든 아웃라이너에 표시되는 중요한 버튼이 E입니다. 이 버튼은 입력 필드를 열어 객체에 메시지를 보낼 수 있게 해줍니다.
여기서 help 메시지를 보내는 모습을 볼 수 있습니다. 아래 Get it을 클릭하거나 CTRL+enter를 누르면 전송됩니다. 그러면 셸이 문자열 객체의 아웃라이너를 반환합니다. Do it을 누를 수도 있는데, 그러면 메시지는 실행하되 반환값은 버립니다.
반환된 객체에 메시지를 보내거나, 다른 객체에서 참조하는 등 다양하게 다룰 수 있습니다.
덧붙이자면, 전체 환경은 거대한 2D 평면처럼 동작하며, WIN+화살표 키로 스크롤하고 점프할 수 있습니다.
오늘날 시각에서는 사용자 인터페이스가 투박하고 이상해 보일 수 있습니다. 다만 지난 세기에 만들어졌고 그 이후 거의 손대지 않았다는 점을 함께 말해야 합니다.
그렇다고 해서 제공할 것이 없다는 뜻은 아닙니다. 오히려 잘 고안된 원칙들에 기반한 일관된 시스템입니다. 그 원칙에는(그 외에도) 다음이 있습니다:
화면에 특정 객체의 아웃라이너가 표시되어 있다면, 다른 객체들도 그것을 표시용으로 그대로 사용합니다.
좋은 예로 nil의 아웃라이너를 표시해 보겠습니다:
두 슬롯이 하나의 동일한 객체를 가리키는 걸 명확히 볼 수 있습니다. 특정 객체의 아웃라이너는 화면에 두 번 이상 나타나지 않는 것이 원칙입니다.
Morphic(인터페이스가 구현된 프레임워크)의 기초를 한 번 이해하면, 나머지는 비교적 직관적으로 추론할 수 있습니다.
예를 들어, 글자는 객체입니다. 글자들의 줄은 어떤 레이아웃에서 문자열 객체들로 구성됩니다. 그 줄 자체도 레이아웃에 들어 있고, 그 레이아웃은 창의 레이아웃에 들어 있습니다. 모든 것은 동적으로 바꾸고 조정할 수 있습니다.
객체들은 마치 현실에 존재하는 물리적 객체처럼 행동합니다. 예를 들어, 창에서 레이블을 떼어내 그 복사본을 빈 공간에 그냥 둘 수 있습니다. 허튼 기능이 아닙니다. 물리적 객체처럼, Self의 그래픽 인터페이스도 탐구하고 분해할 수 있습니다. 그리고 물리적 객체처럼, 그것을 사용해 새로운 인터페이스를 구성할 수 있습니다.
언어가 프로토타입과 메모리 이미지 개념에 기반하기 때문에, 이렇게 조합한 그래픽 인터페이스를 계속 사용하거나, 그 위에 애플리케이션을 만들 수 있습니다.
생동감은, 모든 객체가 수정되면 즉시 다시 그려진다는 점, 그리고 사용자 인터페이스 전반에 다양한 애니메이션이 사용된다는 점에서 옵니다.
애니메이션은 대개 배경에서 눈에 띄지 않게 작동합니다. 예를 들어, 창이 변형될 때 이동 경로에 회색 블롭을 그려, 움직임에 속도와 방향 감각을 부여합니다. 이러한 기법은 애니메이션 만화에서 영감을 얻었습니다.
또 다른 예로, 구문 오류 같은 에러 박스를 화면에 내려놓으면, 잠시 후 스스로 화면 밖으로 미끄러져 나가 방해하지 않게 됩니다.
철학에 대하여개인적으로 매우 흥미로운 점은, 사용자가 그래픽 환경을 완전히 탐색하고 마음대로 수정할 수 있게 하려는 강한 의지입니다. 제작자들은 사용자들이 비교적 단순한 방식으로 자신만의 그래픽 인터페이스를 만들 수 있도록 꽤 성공적으로 시도했습니다. 이는 보통 사람에게는 파워포인트를 넘어서기 거의 불가능한 일입니다.
원칙이 마음에 드셨다면, 더 읽어볼 것을 추천합니다:
Self 이미지와 프로젝트 저장소에는 흔히 쓰이는 도구들도 있습니다.
Outliner는 객체를 편집하는 도구입니다. 기본적으로 객체의 개별 메서드와 프로퍼티를 표 형식으로 보여주지만, 그 이상을 할 수 있습니다:
아웃라이너에서 슬롯의 컨텍스트 메뉴 예시:
Shell은 의미 있는 부모 슬롯들이 미리 설정된 아웃라이너일 뿐입니다. 표준 객체의 E로 셸을 열면, 모든 메시지는 해당 아웃라이너가 표현하는 객체의 콘텍스트에서 실행됩니다. 부모 슬롯이 없는 객체라면 true, false 같은 기본 객체도 사용할 수 없습니다. Shell은 확장된 네임스페이스를 제공해 훨씬 편리합니다.
바탕 화면 어디에서든 휠 클릭으로 호출해, New shell 항목을 선택하면 됩니다.
Core sampler는 거의 모든 _morph_에서 컨텍스트 메뉴(오른쪽 클릭)로 호출할 수 있는, 겉보기엔 특이한 도구입니다.
처음 보면 이상한 조준선과 회색 박스인데, 마우스로 탐색하고 싶은 구체 요소 위에 갖다 대기만 하면 Morphic 인터페이스 전체를 구성 요소별로 반사(reflection)해 보여줍니다:
이 메뉴에서 각 레이어의 속성을 즉시 바꾸고, 레이아웃을 수정하며, 복사본을 만들 수 있습니다.
자세한 내용이 궁금하다면, 예전에 썼던 글을 참고하세요: Reflexe grafických rozhraní (체코어).
빈 공간을 휠 클릭해 나오는 메뉴에서 factory window 도구를 선택할 수 있습니다. 이 도구는 키보드 단축키 목록과 함께, 사용 준비된 각종 _morph_들을 보여줍니다. 클릭해 빈 화면으로 끌어오면, 그것들을 조합해 사용자 인터페이스를 만들 수 있습니다.
왼쪽 중앙쯤에 있는 일종의 화면도 볼만합니다. radarView 애플리케이션의 프로토타입으로, 그냥 집어 화면으로 끌어다 놓을 수 있습니다.
_morph_는 _shell 아웃라이너_의 콘솔에서 다음 명령으로 열 수도 있습니다:
desktop w addMorph: radarView
desktop w hands first addMorph: radarView
첫 번째 명령은 화면 가장자리에 _morph_를 표시하고, 두 번째 명령은 커서 위치에 삽입해 원하는 곳에 내려놓을 수 있게 합니다.
명령은 Self를 실행한 터미널 콘솔에 직접 입력할 수도 있습니다:
Transporter는 메모리 속 객체들을 다시 소스 코드로 “내보내” 일반 VCS(예: Git)로 버전 관리할 수 있게 해 주는 Self 방식입니다.
작동 방식은, 특정 객체들을 어떤 모듈의 일부라고 주석(어노테이션)해 두면, 그 모듈을 “가져오고” “내보낼” 수 있게 되는 것입니다.
실제 예시는 이 글의 범위를 넘지만, 사용법은 여기에서 볼 수 있습니다: Exporting Self objects to source files using the Transporter
내보낸 코드는 다소 읽기 어렵습니다. transporter 지시어들의 시퀀스로 저장되기 때문입니다. 예시는 여기: http_client.self.
덧붙이자면, 이는 프로그래머의 편집을 목적으로 하지 않는 직렬화입니다. 주된 목적은 배포와 버전 관리입니다. 바람직한 편집 방식은 항상 Self 환경 내부에서 객체를 직접 조작하는 것입니다.
이 도구들 외에도, Self 저장소의 objects/applications 폴더에서 여러 도구를 찾을 수 있습니다. 예를 들면:
등등. 프로젝트들은 대체로 중단되었고 진행 정도도 제각각입니다. 어떤 것은 동작하고, 어떤 것은 시간 속에 묻힌 듯합니다.
우선 위키가 있습니다. 체코어 위키에는 아직 정보가 많지 않지만, 영어 위키에는 미니 언어 튜토리얼을 포함한 상당히 포괄적인 소개가 있습니다:
일관된 몇 안 되는 문서 출처로 _Self papers_가 있습니다. 논문 아카이브로, 깃허브 저장소에서 직접 볼 수 있습니다:
또는 더 보기 좋은 웹 버전:
초보자에게 가장 좋은 자료는 Self handbook입니다:
그래픽 인터페이스 입문 자료로 쓸만한 몇 안 되는 문서로, 제가 임시로 올려둔 오래된 튜토리얼이 있습니다:
메일링 리스트 전체를 읽고 싶다면, 아카이브는 여기에 있습니다:
상당한 양의 정보가 있지만, 읽는 데 몇 주가 걸릴 수 있음을 경고합니다. 요약본이 궁금하다면 Self 블로그에서 볼 수 있습니다:
다른 글들도 함께:
대략 이것이 전부입니다. 다른 곳에서 무엇을 찾든, 대체로 오래되었고, 업데이트되지 않았으며, 오늘날에는 관련성이 떨어집니다.
흥미로운 _페이퍼_로는 다음이 있습니다:
그리고 반드시 놓치지 말아야 할 것들:
그리고 이것들은 말 그대로 눈을 뜨이게 해줬습니다:
1편은 여기까지. 다음 편(Self의 환경과 프로그래밍 언어 (2편; 언어))에서는 프로그래밍 언어 자체와 표준 라이브러리, 철학적·형이상학적 함의 등을 살펴보겠습니다. 전체적으로 한두 편이 더 나올 예정이며, 나눔 방식에 따라 달라질 수 있습니다.
이 시리즈와 병행해 또 하나를 시작했습니다: 프로그래밍 언어는 이렇게 만든다 1: 동기
관련 토론: