The Go Blog

Первая программа на Go

Эндрю Джерранд
18 июля 2013

Брэд Фитцпатрик и я (Эндрю Джерранд) недавно начали перераспределение 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
Индекс блога

GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang