The Go Blog
Первая программа на Go
Брэд Фитцпатрик и я (Эндрю Джерранд) недавно начали перераспределение godoc, и мне пришло в голову, что это одна из самых старых программ на Go. Роберт Гриземер начал писать её ещё в начале 2009 года, и мы до сих пор используем её.
Когда я опубликовал твит о этом, Дэйв Чени ответил интересным вопросом: какая самая старая программа на Go? Роб Пайк заглянул в свою почту и нашёл её в старом сообщении для Роберта и Кена Томпсона.
Ниже представлена первая программа на Go. Она была написана Робом в феврале 2008 года, когда команда состояла всего лишь из Роба, Роберта и Кена. У них был чёткий список функций (упомянутый в этом блог-посте) и rough языковая спецификация. Кен только что закончил первую рабочую версию компилятора Go (он не генерировал нативный код, а транслировал код Go в C для быстрой прототипизации) и пришло время попробовать написать программу с его использованием.
Роб отправил письмо "Go team":
<code>From: Rob 'Commander' Pike Date: Wed, Feb 6, 2008 at 3:42 PM To: Ken Thompson, Robert Griesemer Subject: slist it works now. roro=% a.out (defn foo (add 12 34)) return: icounter = 4440 roro=% here's the code. some ugly hackery to get around the lack of strings. </code>
(Строка icounter в выводе программы — это число выполненных
инструкций, выводимых для отладки.)
package main
<span class="comment">// fake stuff</span>
type char uint8;
<span class="comment">// const char TESTSTRING[] = "(defn foo (add 'a 'b))\n";</span>
type Atom struct {
string *[]char
integer int
}
type Slist struct {
car *Slist
cdr *Slist
isatom bool
isstring bool
}
retval := Slist{}
slist := Slist{}
slist.car = nil
slist.cdr = nil
slist.isatom = false
slist.isstring = false
retval = slist
for ;; {
slist.car = Parse()
if token == ')' { <span class="comment">// empty cdr</span>
break
}
if token == EOF { <span class="comment">// empty cdr BUG SHOULD USE ||</span>
break
}
slist.cdr = new(Slist)
slist = slist.cdr
}
return retval
}
function atom(*Slist <- i int) { <span class="comment">// BUG: uses tokenbuf; should take argument</span>
var h, length int
var slist, tail *Slist
slist = new(Slist)
if token == '0' {
slist.atom.integer = i
slist.isstring = false
} else {
slist.atom.string = new([100]char)
var i int
for i = 0; ; i = i + 1 {
(*slist.atom.string)[i] = tokenbuf[i]
if tokenbuf[i] == '\0' {
break
}
}
<span class="comment">//slist.atom.string = "hello"; // BUG! s; //= strdup(s);</span>
slist.isstring = true
}
slist.isatom = true
return slist
}
function atoi(int <-) { <span class="comment">// BUG: uses tokenbuf; should take argument</span>
var v int = 0
for i := 0; '0' <= tokenbuf[i] && tokenbuf[i] <= '9'; i = i + 1 {
v = 10 * v + convert(int, tokenbuf[i] - '0')
}
return v
}
function Parse(*Slist <-) {
var slist *Slist
if token == EOF || token == ')' {
return nil
}
if token == '(' {
NextToken()
slist = ParseList()
Expect(')')
return slist
} else {
<span class="comment">// Atom</span>
switch token {
case EOF:
return nil
case '0':
slist = atom(atoi())
case '"':
case 'A':
slist = atom(0)
case:
slist = nil
print "unknown token" <span class="comment">//, token, tokenbuf;</span>
}
NextToken()
return slist
}
return nil
}
function OpenFile() {
<span class="comment">//strcpy(input, TESTSTRING);</span>
<span class="comment">//inputindex = 0;</span>
<span class="comment">// (defn foo (add 12 34))\n</span>
inputindex = 0
peekc = -1; <span class="comment">// BUG</span>
EOF = -1; <span class="comment">// BUG</span>
i := 0
input[i] = '('; i = i + 1
input[i] = 'd'; i = i + 1
input[i] = 'e'; i = i + 1
input[i] = 'f'; i = i + 1
input[i] = 'n'; i = i + 1
input[i] = ' '; i = i + 1
input[i] = 'f'; i = i + 1
input[i] = 'o'; i = i + 1
input[i] = 'o'; i = i + 1
input[i] = ' '; i = i + 1
input[i] = '('; i = i + 1
input[i] = 'a'; i = i + 1
input[i] = 'd'; i = i + 1
input[i] = 'd'; i = i + 1
input[i] = ' '; i = i + 1
input[i] = '1'; i = i + 1
input[i] = '2'; i = i + 1
input[i] = ' '; i = i + 1
input[i] = '3'; i = i + 1
input[i] = '4'; i = i + 1
input[i] = ')'; i = i + 1
input[i] = ')'; i = i + 1
input[i] = '\n'; i = i + 1
NextToken()
}
Программа разбирает и выводит
S-выражение.
Она не принимает никакого пользовательского ввода и не имеет импортов, полагаясь только на встроенную
print для вывода.
Она была написана буквально в первый день, когда появился
работающий, но примитивный компилятор.
Большая часть языка ещё не была реализована, а некоторые части даже не были задокументированы.
Тем не менее, основной стиль языка сегодня уже можно распознать в этой программе. Объявления типов и переменных, операторы управления и инструкции пакетов не сильно изменились.
Но есть множество отличий и отсутствий. Наиболее значительны отсутствие параллелизма и интерфейсов — оба считаются необходимыми с самого начала, но ещё не были спроектированы.
func был function, и его сигнатура указывала возвращаемые значения
до аргументов, разделяя их с помощью <-, который мы теперь используем как оператор отправки/приёма канала. Например, функция WhiteSpace принимает целое число
c и возвращает логическое значение.
<code>function WhiteSpace(bool <- c int) </code>
Эта стрелка была временным решением, пока не появился более подходящий синтаксис для объявления нескольких возвращаемых значений.
Методы были отдельны от функций и имели свой собственный ключевое слово.
<code>method (this *Slist) Car(*Slist <-) {
return this.list.car;
}
</code>
А методы объявлялись заранее в определении структуры, хотя это вскоре тоже изменилось.
<code>type Slist struct {
...
Car method(*Slist <-);
}
</code>
Строки не существовали, хотя они были описаны в спецификации.
Чтобы обойти это, Робу пришлось собирать входную строку как массив uint8 с громоздкой конструкцией. (Массивы были примитивными, а срезы ещё не были спроектированы,
а тем более реализованы, хотя уже существовал концепт "открытого массива".)
<code>input[i] = '('; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = 'e'; i = i + 1;
input[i] = 'f'; i = i + 1;
input[i] = 'n'; i = i + 1;
input[i] = ' '; i = i + 1;
...
</code>
И panic, и print были встроенными ключевыми словами, а не предварительно объявленными функциями.
<code>print "parse error: expected ", c, "\n"; panic "parse"; </code>
И существует множество других мелких различий; попробуйте найти ещё какие-нибудь.
Менее чем через два года после написания этой программы, Go был выпущен как проект с открытым исходным кодом. Возвращаясь к этому, удивительно, насколько язык вырос и у matureлся. (Последним изменением между этим прото-Go и современным Go оказалось устранение точек с запятой.)
Но ещё более поразительно, насколько мы узнали о написании кода на Go.
Например, Роб называл параметры методов this, но теперь мы используем более короткие, контекстно-специфичные имена. Сотни других подобных примеров, и до сих пор мы продолжаем открывать лучшие способы написания кода на Go.
(Ознакомьтесь с хитрым трюком пакета glog для
обработки уровней подробности.)
Я интересуюсь, что же мы узнаем завтра.
Следующая статья: Массивы, срезы (и строки): Механика функции 'append'
Предыдущая статья: Введение в детектор гонок в Go
Индекс блога