Simula는 합성 대신 상속을 도입해 가비지 컬렉터를 단순화하고 메모리를 절약하며 인트루시브 리스트를 지원하려 했다. 상속의 기원이 성능 최적화였음을 역사적 자료를 통해 짚어본다.
Simula 언어는 인트루시브 리스트를 지원하고, 메모리를 절약하며, 가비지 컬렉터를 단순화하기 위한 방법으로 상속을 발명했다. 상속은 Simula가 발명했다는 것은 잘 알려져 있다. Simula에 관한 HOPL(History of Programming Languages) 세션은 그 발명의 동기를 들려준다. 한번 살펴보자.
Simula는 합성 대신 상속을 만든 이유가 가비지 컬렉터를 더 단순하게 만들 수 있었기 때문이었다. Simula에는 단순한 참조 카운팅과 가비지 컬렉션 구현이 있었다:
충분한 프로그래밍 유연성을 제공하는 그런 [수동 메모리 할당] 방식은 찾을 수 없었기에, 우리는 Weizenbaum(1962)에서 차용한 아이디어인 참조 카운트 방식을 구현했고, "최후의 수단" 가비지 컬렉터도 덧붙였다.
참조 카운팅과 가비지 컬렉션을 사용하는 것은 괜찮지만, 그들의 GC는 다소 지나치게 단순했다:
프로세스는 그 동적 부모, 즉 그 프로세스를 야기한 생성 표현식을 담고 있던 블록 인스턴스보다 오래 살아남을 수 있었다. 그 결과 프로세스는 형식 매개변수를 통해 존재하지 않는 데이터에 접근할 수 있었다. 선택된 해결책은 프로세스에 대해 모든 형태의 이름에 의한 호출 매개변수(프로시저, 레이블, 스위치를 포함)를 금지하는 것이었다.
문제는, 객체(시뮬라 용어로는 "프로세스") 내부에 스택에 할당된 변수에 대한 포인터를 저장한 다음, 그 변수가 유효한 범위를 벗어나면서 그 객체를 반환할 수 있다는 점이다. 그러면 그 변수를 담고 있던 스택 프레임이 해제된다. 그 결과 나중에 그 객체를 사용하는 것이 안전하지 않게 된다: "프로세스가 존재하지 않는 데이터에 접근할 수 있다." 당대에도 Lisp에서 사용된 것 같은 더 정교한 가비지 컬렉터에서는 이런 문제가 발생하지 않는다.
Simula의 단순한 GC 구현은 함수 전달을 포함해 많은 것들을 인자로 넘기는 것을 금지함으로써 이 문제와 기타 문제들을 해결했다. 어떤 의미에서, Simula가 확장한 ALGOL 60에 존재하던 1급 함수 지원을 없앤 셈이다. 예상할 수 있듯, 이는 언어의 표현력을 떨어뜨렸다:
시뮬레이션 프로그램을 작성하면서, 프로세스들이 데이터 속성과 동작 모두에서 많은 공통 특성을 공유한다는 점을 관찰했지만, 다른 측면에서는 구조적으로 달라 따로 선언으로 기술해야 했다. [...] 특히 프로시저 매개변수를 포함한 이름에 의한 호출 매개변수가 프로세스에 대해 금지되었기 때문에(그럴 만한 이유는 2.3.3절 참조), 매개변수화만으로는 충분한 유연성을 제공할 수 없었다.
그들의 GC 한계 때문에, 클래스 커스터마이즈를 위해 "매개변수화"(오늘날 우리가 합성이라 부르는 것)를 사용할 수 없었다. 대신, 상속을 고안해야 했다.
Simula가 상속을 떠올린 직접적인 계기는 인트루시브 리스트 사용을 단순화하기 위해서였다. 인트루시브 리스트는 오늘날까지 쓰이는 영리한 해킹으로, 유연성은 떨어지지만 연결 리스트를 더 효율적으로 만든다. 첫 번째 버전의 Simula, 즉 상속과 다른 OOP 기능을 발명하기 전에는 "집합(sets)"이라는 자료구조를 지원했는데, 이는 객체(시뮬라 용어로는 "프로세스")의 임의 연결 리스트였다:
돌이켜보면 "다중 구성원(multimembership)" 집합을 도입한 것은 실수였다. 우선 "집합"은 사실상 프로세스가 여러 번 등장할 수 있는 프로세스 시퀀스였고, 대부분의 목적에는 단순한 프로세스 체인이 더 적절했을 것이다. [...] 모든 프로세스 포인터가 별도로 할당된 "엘리먼트(element)" 객체를 통해 간접화되어 있었기 때문에, 프로세스 참조에는 상시적인 오버헤드가 있었다.
전통적인 연결 리스트에서 흔히 그렇듯, 연결 리스트 노드(인용문에서 "엘리먼트")는 별도로 할당되어, 메모리 사용량을 늘리고 단편화를 유발했다. 반면, 인트루시브 리스트에서는 리스트 노드를 별도로 할당하지 않는다.
이후 버전의 Simula에서는, 하나의 객체가 여러 연결 리스트에 동시에 속할 수 없고 비효율적인 "엘리먼트" 객체가 필요하지 않은 더 단순한 자료구조가 낫다는 결론을 내렸다:
엘리먼트/집합 개념은 리스트 처리를 위한 기본 범용 메커니즘으로서는 너무 투박했다. 시뮬레이션 모델링에서도, 프로세스 자체에 한 번에 하나의 집합에만 속할 수 있는 고유한 집합 소속 기능을 결합하고 단순한 프로세스 포인터를 사용하는 것이 더 낫다는 경험적 증거가 있었다.
그들은 전통적인 연결 리스트를 인트루시브 리스트로 대체하기로 했다. 인트루시브 리스트는 전통적 연결 리스트보다 메모리와 시간 면에서 훨씬 효율적이다. 그러나 인트루시브 리스트에서는, 리스트에 올라갈 클래스의 정의 안에 연결 리스트 노드가 포함되어 있어야 한다. 전통적인 합성 기법(설령 Simula에서 가능했더라도)만으로는 이것이 충분치 않았다. Simula의 저자들도 이를 어떻게 구현할지 알지 못했다가, 다음과 같은 아이디어가 떠오르기 전까지는:
해결책은 1966년 12월, "프리픽싱(prefixing)"이라는 발상과 함께 갑자기 찾아왔다. 우리는 다리 위의 톨게이트와, 트럭이거나 버스인 자동차들이 줄 서 있는 상황을 떠올리고 있었다. (이 예시는 Dahl과 Nygaard, 1968에 다시 등장한다.) "집합 헤드(set head)"와 가변 개수의 "링크(link)"로 구성된 "간소화된" 리스트 구조를 적어 내려가던 중, 다양한 프로세스(트럭, 버스)를 각각 하나의 "링크"에 "접착(gluing)"하여 각 링크-프로세스 쌍을 하나의 블록 인스턴스로 만드는 메커니즘으로 두 가지 문제를 모두 해결할 수 있음을 깨달았다. [...] 보통 새로운 아이디어는 그 강건함을 시험하기 위해 거친 공격을 받곤 했다. 프리픽스 아이디어만은 예외였다. 우리는 즉시, 이제 완전히 새로운 언어적 접근을 위한 필요한 토대를 갖추었다는 것을 깨달았다.
Simula에서 상속은 "프리픽싱"이라 불렸고, 기반 클래스는 "프리픽스"였다. 즉, 상속이라는 발상은 객체가 인트루시브 리스트의 노드인 "링크"를 상속받게 함으로써 인트루시브 리스트를 구현한다는 생각에서 비롯되었다. 이렇게 해서, 원래라면 복잡한 성능 해킹인 인트루시브 리스트를 손쉽게 사용할 수 있었다.
성능상의 이유로 기능을 만드는 것이 반드시 나쁜 일은 아니다. 그리고 물론, Simula는 다른 많은 면에서도 천재적이었다. 그들은 상속만이 아니라 현대 OOP의 거의 모든 부분을 발명했고, 객체에 보편적인 동시성을 도입한 점은 더 많이 본받아야 할 특히 멋진 기능이다. 하지만 오늘날 상속을 성능 기능으로 말하는 사람은 거의 없다. 사람들은 코드 재사용과 확장성의 이점을 이야기한다.