Disclaimer: 이 글은 KAIST CS320 "Programming Language" 수업의 과제의 일환으로 작성된 것입니다. 과제물로 복사-붙여넣기 하여 제출하지 않기를 바랍니다.
YouTube lecture link: https://www.youtube.com/watch?v=_ahvzDzKdB0&feature=youtu.be
Introduction: 강연 요약
스틸의 OOPSLA 1998 명강연에서 그는 영어에서 단일 음절 단어만을 primitive로 하는 사례를 통해 아주 작은 언어로 복잡한 표현을 하는 것이 얼마나 답답한 일인지를 보여준다. 예를 들어, 단일 음절 단어가 아닌 woman을 wo- + man으로 나타내어야 하는 것과 같은 예시가 있다. 그는 사람들이 다양한 프로그램을 잘 짜는데 사용할 수 있는 언어를 만든다면, 작은 언어와 큰 언어 중 무엇을 설계해야 하는지에 대한 질문을 던진다.
작은 언어는 배우기도 쉽고, 구현도 쉬울 것이지만, 실제로 복잡한 프로젝트를 하기에는 사용자가 새로운 개념을 정의하고 언어를 키워야 하는 일이 잦을 것이다. 반면, 큰 언어는 한 번 익히면 다양한 것을 쉽게 표현할 수 있지만, 언어를 설계하는게 어렵고, 언어의 보급도 느려서 세상에 나올 때 쯤이면 이미 먼저 나온 다른 언어들이 이미 세상을 지배하고 있을 것이다.
그의 결론은, 작은 언어도, 큰 언어도 아닌 성장하는 언어가 답이라는 것이다. 그는 언어의 자연스러운 성장은, 사용자가 만든 새로운 추상화가 원래 있던 언어의 요소 처럼 보여야 한다고 주장하고, 우리는 완제품 언어가 아닌 언어의 성장 패턴을 설계해야 함을 꼬집는다. 그의 철학에 따르면 좋은 프로그래머란 자신의 도메인에 맞는 작은 언어(일종의 DSL일 수 있다)를 만들고, 그 언어에 맞춰 코드를 작성할 수 있는 사람일 것이다.
Go 언어 톺아보기
Go 언어는 2009년 Google에서 처음 발표된 후, 2012년 Go 1.0이 출시되며 부터 본격적으로 쓰이기 시작한 프로그래밍 언어이다. Go 언어 창시자들은 C++의 복잡함을 벗어나고자 Go를 설계했으며, 그렇기에 작은 언어를 표방하며 시작되었다고 볼 수 있다.
실제로 초창기 Go는 소수의 keyword와 syntactic sugar만 갖고도 네트워크 프로그래밍이나 동시성 프로그래밍을 지원하는 것을 표방했고, 이는 작은 언어에 가까웠다고 할 수 있다. 하지만, 성장하는 언어에 가깝다고 보기는 힘든데, 초기의 Go는 generic과 연산자 오버로딩, 매크로, 상속 등이 없어서, Java 류 언어의 사용자 관점에서 바라봤을 때 생태계에서 자연스럽게 자랄 성장 패턴이 있다고 보기는 힘들었다. 그리고 사용자들의 요구에도 불구하고 설계자들의 철학이 더 우선시 반영되어 generic이 들어오기 까지 10년이 넘는 시간이 걸리기도 했다.
스틸의 관점에서 보는 Go
다음과 같은 Person 타입으로 구성된 동적 배열(엄밀히는 이와 동등한 Go의 slice)을 나이에 따라 정렬하는 것을 살펴보자.
type Person struct {
Name string
Age int
}
Go의 1.7 혹은 그 이전 버전에서는 이를 위해 sort.Interface를 구현하는 타입을 만들어 줘야 했다.
type compareAge []Person
func (a compareAge) Len() int {
return len(a)
}
func (a compareAge) Less(i, j int) bool {
return a[i].Age < a[j].Age
}
func (a compareAge) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
var people []Person = ...
sort.Sort(compareAge(people))
Go의 1.8에서는 sort.Slice 함수가 도입되며, 배열의 index 간의 compare 함수를 정의해주면 충분하게 되었다. 이는 1.20 이하 Go 버전까지 (boilerplate를 적게 만들어내는 관점에서) 최선의 전략이었다.
var people []Person = ...
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
Go 1.18부터 generic이 추가되었고, experimental package로 slices가 지원되기 시작했다. (이는 Go 1.21 부터 정식으로 지원되기 시작했다.) 이 시점부터 비로소 Java, C# 등에서 lambda 함수로 정렬하는 것과 같은 패턴의 정렬 코드 작성이 가능해졌다고 할 수 있다.
var people []Person = ...
slices.SortFunc(people, func(a, b Person) int {
return cmp.Compare(a.Age, b.Age)
})
스틸은 좋은 프로그래머가 단순한 프로그램을 작성하는 사람이 아닌, 자신의 도메인에 맞는 작은 언어를 설계하는 사람이라고 정의했다. 이 단순화된 예시를 통해 봤을 때, Go를 기반으로 자신의 도메인에 맞는 프로그램을 작성하게 되면, 우아한 DSL이 등장하기 보다는 간단한 인터페이스를 가지는 작은 프레임워크를 만드는 것에 가깝다고 볼 수 있다.
즉, 스틸의 강연이 제시하는 관점을 기준으로 바라봤을 때, Go는 작은 언어지만 자연스러운 성장 패턴을 갖추지는 않았고, 창시자들의 의도를 가진 작은 언어라고 할 수 있겠다. 작은 언어의 한계 (혹은 특징) 때문에 Go는 프로그램 작성자를 DSL 설계자로 만들어주지는 않지만, 대부분의 Go를 사용하는 프로그래머들이 평이한 형태의 통일된 코드 스타일을 강제 받게 된다.
Go는 나쁜 언어인가?
필자는 Go가 나쁜 언어라고 생각하지 않는다. Go의 의도된 성장을 지연하고 작은 언어를 유지하는 전략은 프로그래머들의 코드 스타일을 수렴시킨다. 이는 대규모 코드베이스를 관리하는 조직에서 코드 리뷰나 온보딩에 사용되는 비용을 감소시킨다.
또한, 패키지 관리(go mod)나 테스트(go test) 등을 수행하는 툴 들이 Go 언어 자체의 의도된 작은 언어 철학과 강하게 결합되어 있기에, 언어가 성장하지 못하더라도 성장하는 코드베이스를 관리하기 쉽게 만들어준다. 스틸의 관점에서 성장하는 언어가 되지는 못하더라도, 실제 Go를 사용하는 개발 팀이 운용하는 시스템이 쉽게 성장하도록 도와준다는 것이다. 필자는 성장하는 언어의 장점인 추상화의 자유도가 때로는 과도한 추상화나 파편화로 이어지기 쉽다고도 생각한다.
결론적으로, 필자는 Go가 PL 이론가의 입장에서 표현력이 부족하고, 언어 자체의 성장을 위한 패턴을 갖추고 있지 않음을 인정한다. 하지만, 그런 Go가 팀과 시스템의 성장을 오히려 도와준다는 현실이 '언어'의 관점과 언어를 사용하는 '시스템'의 관점 간의 간극을 설명해주는 재미있는 예시인 것 같다.
'Computer Science' 카테고리의 다른 글
| Massively Parallel Computing Model (0) | 2023.05.27 |
|---|---|
| Unique Games Conjecture에 대한 노트 (2) | 2022.11.13 |
| The Short-Side Advantage in Random Matching Markets (0) | 2022.10.01 |
| Computational Complexity Classes (0) | 2022.09.29 |
| Redis는 어떻게 key를 expire 하는가? (2) | 2022.09.14 |