Пользователь
0,1
рейтинг
6 июля 2014 в 15:15

Разработка → [Перевод] Почему Go не так хорош из песочницы

Rust*, Go*, Haskell*
Всем привет! Недавно вышел перевод статьи о том, как TJ Holowaychuk прощался с Node.js, решив двигаться в сторону Go. В конце статьи была ссылка на посвящённый сравнению и критике языка Go пост Уилла Ягера, который просили перевести — собственно, с результатами перевода я и предлагаю ознакомиться. Я пытался более-менее сохранить как многословный стиль изложения, присущий автору, так и оригинальную разбивку на предложения и параграфы.
Буду очень рад любым конструктивным замечаниям и предложениям по переводу, опечаткам и/или оформлению, но очень прошу помнить, что точка зрения переводчика может не совпадать с позицией автора переведённой статьи.

Почему Go не так хорош


Мне нравится Go. Я использую его для некоторых вещей (включая этот блог на момент написания статьи). Go удобен. Тем не менее, Go нельзя назвать хорошим языком. Он, конечно, не плох, но и не хорош.

Нужно быть осторожным в использовании языков, которые не слишком хороши, ведь в конечном итоге можно застрять, и придётся использовать их лет 20 [как с PHP — прим. переводчика].
Ниже я приведу список моих основных претензий к Go; некоторые из них встречаются довольно часто, а некоторые весьма редки.

Также я буду приводить сравнения с языками Rust и Haskell (которые я считаю хорошими языками) — для того, чтобы показать, что проблемы, о которых я буду говорить, на самом деле уже решены [в других языках — прим. переводчика].

Обобщения


Суть проблемы

Пусть мы хотим написать код, который мы могли бы использовать для множества разных вещей. Например, если я пишу функцию для суммирования списка чисел, было бы хорошо, если бы я мог использовать её и для списков чисел с плавающей точкой, списков целых чисел, списков элементов любых других типов, которые также могут быть просуммированы. Ещё было бы круто, если бы этот код обеспечивал такие же типобезопасность и скорость, как и отдельные функции для каждого типа — функции суммирования списков целых чисел, списков чисел с плавающей точкой и т.д.

Правильный подход: обобщения с ограничениями и параметрический полиморфизм

Я думаю, лучшие из существующих реализаций обобщений — те, что присутствуют в языках Rust и Haskell (они ещё могут называться «системами с ограниченными типами»). Версия из Haskell называется «классы типов», а вариант из Rust — «трейты» [или «примеси»/«миксины», в зависимости от перевода — прим. переводчика]. Выглядят они примерно так:

(Rust, версия 0.11)

fn id<T>(item: T) -> T {
    item
}

(Haskell)

id :: t -> t
id a = a

В этом синтетическом примере мы определили функцию id с обобщённым параметром, которая просто возвращает переданный ей параметр. Круто здесь то, что эта функция работает с любыми типами, а не с каким-то одним. И в Haskell, и в Rust эта функция хранит тип переданного параметра, обеспечивает типобезопасность и не создаёт дополнительных издержек во время исполнения из-за поддержки обобщений. По аналогии можно написать, например, функцию clone.

Обобщения можно использовать и для определения структур данных. Например,

(Rust)

struct Stack<T> {
    items: Vec<T>
}

(Haskell)

data Stack t = Stack [t]

И снова обеспечиваются статическая типобезопасность и отсутствие потерь производительности во время исполнения из-за поддержки обобщений.

Если же мы хотим написать обобщённую функцию, которая делает что-либо с параметрами, нужно как-нибудь указать компилятору, что эта функция может работать только с параметрами, для которых определены эти действия. Например, если мы хотим определить функцию, которая складывала бы три параметра и возвращала их сумму, нам нужно объяснить компилятору, что параметры должны поддерживать сложение. Можно сделать это примерно так:

(Rust)

fn add3<T:Num>(a:T, b:T, c:T) -> T {
    a + b + c
}

(Haskell)

add3 :: Num t => t -> t -> t -> t
add3 a b c = a + b + c

Здесь мы как бы говорим компилятору: «параметры функции add3 могут быть любого типа T, но с тем ограничением, что T должен быть одним из подтипов Num(численный тип)». Поскольку компилятор знает, что для численных типов определено сложение, код пройдёт проверку типов. Такие ограничения также могут быть использованы и при определении структур данных. Что ж, это очень элегантный и простой способ типобезопасного и расширяемого программирования с обобщениями.

Подход Go: interface{}

Из-за весьма посредственной системы типов, Go имеет очень плохую поддержку обобщённого программирования.

Подобия обобщённых функций пишутся достаточно легко. Например, вы хотели бы написать функцию, выводящую хэш любого объекта, который может быть захэширован. Для этого вы можете определить интерфейс, который позволяет делать это с гарантией типобезопасности, то есть что-то вроде

type Hashable interface {
    Hash() []byte
}

func printHash(item Hashable) {
    fmt.Println(item.Hash())
}

Теперь вы можете передавать любой реализующий интерфейс Hashable объект, и статическая проверка типов тоже будет выполняться, что, в общем-то, хорошо.

Но что будет, если вы хотите определить структуру данных с обобщёнными типами? Давайте накидаем простой тип связного списка LinkedList. Вот типичный способ сделать это в Go:

type LinkedList struct {
    value interface{}
    next * LinkedList
}

func (oldNode * LinkedList) prepend(value interface{}) * LinkedList {
    return &LinkedList{value, oldNode}
}

func tail(value interface{}) * LinkedList {
    return &LinkedList{value, nil}
}

func traverse(ll * LinkedList) {
    if ll == nil {
        return
    }
    fmt.Println(ll.value)
    traverse(ll.next)
}

func main() {
    node := tail(5).prepend(6).prepend(7)
    traverse(node)
}

Заметили что-нибудь? Тип поля valueinterface{}. Здесь interface{} — то, что мы называем «базовым типом», что означает, что все остальные типы наследуются от него. Это прямой эквивалент класса Object в Java. Чёрт побери.
(Примечание: есть некоторые разногласия о том, существует ли базовый тип в Go, поскольку в Go подразумевается отсутствие подтипов. Несмотря на это, аналогия остаётся.)

«Правильный» путь для построения обобщённых структур в Go — приведение сущностей к базовому типу и последующее добавление их в структуру данных. Это примерно то, как было принято в Java году так в 2004-м. Затем люди поняли, что этот подход полностью ломает систему типов. Когда структуры данных используются таким образом, все преимущества строгой системы типов просто испаряются [на самом деле не вижу здесь большой проблемы — вместо базового interface{} можно, в принципе, указывать более конкретный интерфейс, приводя к нему конкретные реализации и не руша таким образом проверку типов — прим. переводчика].

Например, вот абсолютно корректный код:

node := tail(5).prepend("Hello").prepend([]byte{1,2,3,4})

Это лишает хорошо структурированную программу её преимуществ. Например, метод ожидает в качестве параметра связный список целых чисел, но вдруг какой-то усталый, упоровшийся кофе программист с дедлайном на носу внезапно передаст строку. И компилятор ничего не заметит, потому что обобщённые структуры в Go ничего не знают о типах их значений, и однажды программа просто упадёт на приведении к interface{}.

Аналогичные проблемы могут возникнуть с любыми обобщёнными структурами — даже с list, map, graph, tree, queue.

Расширяемость языка


Суть проблемы

Языки высокого уровня часто имеют ключевые слова и символы, являющиеся сокращениями для более сложным задач. Например, во многих языках есть способ обхода всех элементов коллекций данных, хотя бы тех же массивов:

(Java):

for (String name : names) { ... }

(Python):

for name in names: ...

Было бы хорошо, если бы мы могли использовать подобный синтаксический сахар для любой коллекции, а не только для встроенных в язык (вроде массивов).

Также было бы удобно, если бы мы могли определять для наших типов операции вроде сложения и писать штуки вроде таких:
[речь, например, о перегрузке операторов — прим. переводчика]

point3 = point1 + point2

Правильный подход: операторы — это функции

Хорошее решение — сделать операторы «ярлыками» к соответствующим функциям/методам, а ключевые слова — псевдонимами к стандартным функциям/методам.

Некоторые языки: Python, Rust, Haskell и т.д. — разрешают переопределять операторы [а Haskell ещё и определять свои собственные — прим. переводчика]. Всё, что нужно сделать — написать методы класса, и тогда при использовании какого-нибудь оператора (например, "+") просто вызывается соответствующий метод. В Python оператор "+" вызывает __add__(). В Rust "+" определён в трейте Add как метод add(). В Haskell "+" соответствует функции +, определённой в типе Num.

Многие языки поддерживают способ расширения области применения разных ключевых слов вроде for-each. В Haskell нет циклов, но в языках вроде Rust, Java и Python есть итераторы, дающие возможность использовать for-each с любыми коллекциями любых типов.

Обратная сторона заключается в том, что можно переопределять операторы так, что они будут делать что-то совершенно не интуитивное. Например, быдлокодер [ориг. «crazy coder» — прим. переводчика] может определить оператор "-" как умножение двух векторов, но это всё же проблема не самой перегрузки операторов, поскольку убого называть функции можно в любом языке.

Подход Go: отсутствует

Go не поддерживает перегрузку операторов и расширение применения ключевых слов.

Но что, если мы вдруг захотим использовать ключевое слово range с чем-нибудь ещё — с деревом или со связным списком? Не получится. Язык это не поддерживает. Использовать range можно только со встроенными структурами. То же самое и с make — его можно использовать для выделения памяти и инициализации только встроенных структур.

Ближайшая доступная аналогия расширяемого итератора — написание обёртки над структурой данных, возвращающей chan (канал — прим. переводчика), и последующая итерация по нему, но это медленно, сложно и может вызвать кучу багов.

Такой подход обосновывают примерно следующим: «легко понять, и код, который написан странице — и есть код, который исполняется». То есть, если Go разрешал бы расширять штуки типа range, это могло бы вызвать путаницу, потому что детали реализации range для конкретного случая могут быть неочевидными. Но для меня этот аргумент мало что значит, ведь людям приходится обходить структуры данных независимо от того, делает Go это простым и удобным или нет. Вместо того, чтобы прятать детали реализацию за range, нам приходится прятать детали реализации за другой вспомогательной функцией — не слишком-то похоже на большое улучшение. Хорошо написанный код легко читать, а плохо написанный код — сложно, и, очевидно, Go не в силах это изменить.

Базовые случаи и проверки на ошибки


Суть проблемы

При работе с рекурсивными структурами данных — связными списками или деревьями — мы хотим иметь способ показать, что конец структуры ещё не достигнут.

Используя же функции, которые могут завершиться неудачей, или структуры данных, в которых могут отсутствовать какие-нибудь данные, мы хотим иметь возможность показать, что задачу выполнить не удастся.

Подход Go: Nil и множественный возврат значений

Я собираюсь сперва поговорить о подходе Go, потому что после этого станет проще объяснить правильный подход.
В Go есть nilнулевой указатель. Я думаю, постыдно, что столь новый и современный язык — так сказать, tabula rasa — реализует эту ненужную, костыльную, приводящую к багам функциональность.

Нулевой указатель имеет давнюю и богатую на баги историю. По историческим и практическим причинам, используемые данные почти никогда не хранились по адресу 0x0, поэтому указатели на 0x0, как правило, использовались для представления некоторой особой ситуации. Например, функция, возвращающая указатель, может вернуть 0x0, если она завершилась неудачей. Рекурсивные структуры данных могут использовать 0x0 для определения некоторого базового случая (как, скажем, того, что текущий узел дерева — лист, или что текущий элемент связного списка — последний). Для всего этого нулевой указатель используется и в Go.

Однако, использование нулевого указателя может быть небезопасным. Фактически этот указатель — нарушение системы типов; он позволяет создавать экземпляр типа, который на самом деле и вовсе не является типом. Крайне распространена ситуация, когда программист забыл проверить указатель на нуль, и это потенциально приводит к падениям и, в ещё более ужасном случае, к уязвимостям. Компилятор же не может просто взять и защитить от этого, потому что нулевой указатель выбивается из принятой системы типов.

К чести Go, корректно и вообще предпочтительно усиливать принятый в Go механизм множественного возврата значений, возвращая второе «неудачное» значение, если есть вероятность того, что функция завершилась неудачей. Впрочем, этот механизм легко может быть проигнорирован или неправильно использован, и, как правило, бесполезен для представления базовых случаев в рекурсивных структурах данных.

Правильный подход: алгебраические типы данных и типобезопасные виды отказов

Вместо насилия над системой типов для представления ошибочных состояний или базовых случаев, мы можем использовать систему типов для безопасного сокрытия всех этих ситуаций.

Допустим, мы хотим реализовать связный список. Мы хотим представить два возможных случая: во-первых, если ещё не достигнут конец списка и у текущего элемента есть данные, и, во-вторых, если достигнут конец списка. Типобезопасный путь подразумевает реализацию двух типов, соответственно представляющих один из этих случаев, и последующее объединение их вместе, используя алгебраические типы данных. Пусть у нас есть тип Cons, представляющий элемент связного списка с какими-то данными, и тип End, представляюший собой конец списка. Мы можем записать это следующим образом:

(Rust)

enum List<T> {
    Cons(T, Box<List<T>>),
    End
}
let my_list = Cons(1, box Cons(2, box Cons(3, box End)));

(Haskell)

data List t = End | Cons t (List t)
let my_list = Cons 1 (Cons 2 (Cons 3 End))

Каждый тип определяет базовый случай (End) для любого рекурсивного алгоритма, производящего операции над структурой данных. Ни Rust, ни Haskell не разрешают нулевые указатели, так что мы стопроцентно уверены, что мы никогда не столкнёмся с багами, связанными с нулевыми указателями (конечно, до тех пор, пока мы не делаем какие-нибудь безумные низкоуровневые штуки).

Эти алгебраические типы данных также позволяют писать невероятно краткий (и поэтому слабо подверженный багам) код благодаря таким возможностям языка, как сопоставление с образцом, что и будет показано ниже.

Что ж, а что мы должны делать, если функция может вернуть либо не вернуть значение некоторого типа, или если структура данных может содержать, а может не содержать данные? То есть, как мы можем сокрыть состояние ошибки в нашей системе типов? Для решения этой проблемы в Rust есть кое-что, называемое Option, а в Haskell есть кое-что, именуемое Maybe.

Представьте себе функцию, которая ищет строку, начинающуюся с 'H', в массиве непустых строк, и возвращает первую подходящую строку или некоторую ошибку, если такая строка не найдена. В Go в случае ошибки пришлось бы вернуть nil. А вот как мы можем сделать это безопасно и без костылей с указателями в Rust и Haskell:

(Rust):

fn search<'a>(strings: &'a[String]) -> Option<&'a str> {
    for string in strings.iter() {
        if string.as_slice()[0] == 'H' as u8 {
            return Some(string.as_slice());
        }
    }
    None
}

(Haskell):

search [] = Nothing
search (x:xs) = if (head x) == 'H' then Just x else search xs

Вместо возврата строки или нулевого указателя мы возвращаем объект, который может содержать, а может не содержать строку. Мы никогда не возвращаем нулевой указатель, и разработчики, использующие search(), знают, что функция может завершить успешно или неудачно, поскольку её тип говорит об этом, и что они должны быть готовы к обоим случаям. Прощайте, связанные с нулевым указателем баги!

Вывод типов


Суть проблемы

Становится немного старомодно указывать тип каждой переменной в коде программы. Есть ситуации, когда тип значения очевиден:

int x = 5
y = x*2

Совершенно ясно, что y тоже имеет тип int. Конечно, есть более сложные ситуации, например, вывод возвращаемого функцией типа на основе типов её параметров (или наоборот).

Правильный подход: общий вывод типов

Поскольку и Rust, и Haskell основаны на системе типов Хиндли-Милнера, они оба очень хороши в выводе типов, и можно делать вот такие крутые штуки:

(Haskell):

map :: (a -> b) -> [a] -> [b]
let doubleNums nums = map (*2) nums
doubleNums :: Num t => [t] -> [t]

Поскольку функция (*2) принимает параметр типа Num и возвращает значение типа Num, Haskell может определить, что тип и a, и bNum, и отсюда может вывести, что функция принимает и возвращает список значений типа Num. Это гораздо мощнее, чем те простые системы вывода типов, что поддерживаются языками вроде Go и C++. Это позволяет делать минимальное число явных указаний типов, а компилятор может правильно определить всё остальное даже в очень сложных программах.

Подход Go: оператор :=

Go поддерживает оператор присваивания :=, который работает следующим образом:

(Go):

foo := bar()

Всё, что он делает — выводит возвращаемый функцией bar() тип и присваивает foo такой же. Получается примерно то же, что и здесь:

(C++):

auto foo = bar();

Это не слишком-то интересно. Всё, что оно делает — избавляет от двухсекундных усилий на то, чтобы посмотреть возвращаемый функцией bar() тип, и от написания нескольких символов названия типа переменной foo.

Неизменяемость состояния


Суть проблемы

Неизменяемость состояния (иммутабельность) — идея, суть которой в том, что значения устанавливается единственный раз в момент создания и затем не могут меняться. Преимущества такого подхода весьма очевидны: если значения неизменны, возможность появления багов, вызванных изменением структуры данных в одном месте в момент использования в другом месте, значительно уменьшается.

Неизменяемость состояния также бывает полезна для некоторых типов оптимизации.

Правильный подход: неизменяемость состояния по умолчанию

Программисты должны пытаться использовать неизменяемые структуры данных так часто, как это возможно. Неизменяемость состояния позволяет намного проще определить возможные побочные эффекты и безопасность программы, избавляя от целых классов возможных ошибок.

В Haskell все значения неизменяемые. Если же вы хотите изменить состояние структуры данных, вам придётся создать другую структуру с нужными значениями. Это по-прежнему быстро, потому что Haskell использует ленивые вычисления и персистентные структуры данных. Rust же — системный язык программирования, поэтому он не может использовать ленивые вычисления, и неизменяемость состояния не может всегда быть такой же практичной, как в Haskell. Тем не менее, в Rust все объявляемые переменные неизменны по умолчанию [в этом случае на совсем корректно называть их переменными, но так было в оригинале — прим. переводчика], но есть и возможность изменять состояние, если она требуется. И это замечательно, потому что принуждает программиста явно указывать, что объявляемая переменная должна быть изменяемой, и это способствует применению лучших практик программирования и позволяет компилятору более качественно проводить оптимизацию.

Подход Go: отсутствует

Go не поддерживает неизменяемость состояния.

Управляющие конструкции


Суть проблемы

Управляющие конструкции [ориг. «control flow structures» — прим. переводчика] — часть того, что отличает языки программирования от машинного кода. Они позволяют нам использовать абстракции для управления исполнением программы в нужном направлении. Очевидно, все языки программирования поддерживают какие-нибудь управляющие конструкции, иначе их бы никто не использовал. Однако, есть несколько управляющих конструкций, которых мне так не хватает в Go.

Правильный подход: сопоставление с образцом и составные выражения

Сопоставление с образцом — действительно крутой способ работы со структурами данных и значениями. Оно похоже на case/switch на стероидах. Сравнивать с образцом можно так:

(Rust):

match x {
    0 | 1  => action_1(),
    2 .. 9 => action_2(),
    _      => action_3()
};

При этом можно работать со структурами подобным образом:

deg_kelvin = match temperature {
  Celsius(t)    => t + 273.15,
  Fahrenheit(t) => (t - 32) / 1.8 + 273.15
};

Предыдущий пример показывает кое-что, называемое «составным выражением» [ориг. «compound expressions» — прим. переводчика]. В языках вроде C и Go операторы if и case/switch просто направляют поток исполнения программы; они не вычисляют значения. В языках вроде Rust и Haskell оператор if и сопоставление с образцом способны вычислять значения, которые могут быть чему-нибудь присвоены. Другими словами, оператор if и сопоставление с образцом действительно могут «возвращать» значения. Вот пример с оператором if:

(Haskell):

x = if (y == "foo") then 1 else 2

Подход Go: операторы без значения в стиле C

Я сейчас не хочу унижать Go — в нём есть некоторые годные управляющие структуры для определённых вещей вроде select для распараллеливания. Однако, в нём нет составных выражений и сопоставления с образцом, которые я так люблю. Go поддерживает только присвоение атомарных выражений вроде x := 5 или x := foo().

Программирование для встроенных систем


Написание программ для встроенных систем сильно отличается от написания программ с полнофункциональными операционными системами. Некоторые языки намного лучше подходят для работы с особыми требованиями программирования для встроенных систем.

Я удивляюсь немалому количеству людей, предлагающих Go в качестве языка для программирования роботов. Go не подходит для программирования для встроенных систем по ряду причин. Этот раздел не посвящён критике Go, просто Go не проектировался для этого. Этот раздел — о заблуждениях людей, рекомендующих писать на Go для встроенных систем.

Проблема №1: куча и динамическое выделение памяти

Куча — участок памяти, который может быть использован для хранения произвольного количества объектов, созданных во время исполнения. Использование кучи называется динамическим выделением памяти.

Как правило, неразумно использовать кучу при программировании для встроенных систем. Основная причина в том, что куча, как правило, требует немалых дополнительных издержек памяти и некоторых весьма сложных структур для управления, ни одна из которых не подходит, когда пишешь под восьмимегагерцевый контроллер с двумя килобайтами оперативной памяти.

Ещё, конечно, неразумно использовать кучу в системах реального времени (системах, которые могут отказать, если операция занимает слишком много времени), потому что количество времени, требуемого для выделения и освобождения памяти в куче, не детерминировано. Например, если ваш микроконтроллер управляет ракетным двигателем, будет отстойно, если вы попытаетесь выделить некоторое количество памяти в куче и это займёт на несколько сотен миллисекунд больше, чем обычно, и это приведёт к несвоевременной регулировке клапана и сильному взрыву.

Есть и другие стороны динамического выделения памяти, которые делают его использование непригодным для эффективного программирования под встроенные системы. Например, многие языки, которые используют кучу, полагаются на сборщик мусора, который во время работы обычно приостанавливает выполнение программы, чтобы найти и удалить объекты, которые больше не используются. Это делает работу программы ещё более непредсказуемой, чем просто c использованием динамической памяти.

Правильный подход: сделать динамическую память необязательной

В стандартной библиотеке языка Rust есть построенная на динамической памяти функциональность — например, boxes. Однако, компилятор поддерживает флаги для полного отключения всей использующей динамическую память функциональности и принудительной статической проверки того, что эта функциональность не используется в коде. Rust действительно позволяет писать программы вообще без использования кучи.

Подход Go: отсутствует

Go очень сильно завязан на использовании динамической памяти. Нет ни одного практичного способа заставить код на Go использовать только стек, но для Go это не проблема — конечно, в областях, для которых Go и предназначен.

Go также не язык реального времени. Совершенно невозможно дать жёсткие гарантии времени исполнения любой достаточно большой программы. Это может немного озадачить, поэтому я объясню: Go относительно быстр, но этого недостаточно для реального времени. Есть большая разница: скорость важна для реального времени, но гораздо важнее возможность гарантировать максимальное время отклика, которое не может быть легко предсказано в случае с Go — разумеется, из-за сильного использования кучи и из-за сборки мусора.

Аналогичные проблемы возникают и в случае языков вроде Haskell, непригодных для задач реального времени или для программирования встроенных систем из-за столь же большой завязки на куче. Однако, я никогда не видел кого-нибудь, пропагандирующего Haskell в качестве языка для программирования роботов, поэтому нет необходимости это обсуждать.

Проблема №2: написание небезопасного низкоуровневого кода

Когда приходится писать программы для встроенных систем, практически невозможно избежать написания небезопасного кода (небезопасно приводящего типы или использующего адресную арифметику). В C или C++ делать небезопасные вещи очень просто. Пусть мне нужно включить светодиод, записав 0xFF по адресу 0x1234, тогда я просто могу сделать следующее:

(C/C++):

* (uint8_t *) 0x1234 = 0xFF;

Это исключительно опасно и имеет смысл только в очень низкоуровневом системном программировании, поэтому ни Go, ни Haskell не позволяют легко делать это; это не языки для системного программирования.

Правильный подход: изоляция небезопасного кода

Rust, ориентированный как на безопасность, так и на системное программирование, даёт хороший способ инструмент — блоки небезопасного кода [ориг. «unsafe code blocks» — прим. переводчика], хороший способ явного отделения опасного кода от безопасного. Вот тот же пример с записью 0xFF по адресу 0x1234 на языке Rust:

(Rust):

unsafe{
    * (0x1234 as * mut u8) = 0xFF;
}

Если бы мы попытались сделать это вне блока небезопасного кода, компилятор бы громко возмутился. Это позволяет нам делать все те нерадостные, но необходимые опасные операции, присущие программированию для встроенных систем, при этом максимально сохраняя безопасность кода.

Подход Go: отсутствует

Go не заточен под такие вещи и не имеет для них встроенной поддержки.

TL;DR


Вы всё ещё можете сказать: «Но почему ж Go нехороший? Это просто список жалоб; жаловаться можно вообще на любой язык!». Это правда; нет совершенного языка. Однако, надеюсь, моё нытьё всё же немного показало, что:

  • Go не делает ничего нового;
  • Go не был великолепно спроектирован с нуля;
  • Go — шаг назад по сравнению с другими современными языками.
Денис @devlato
карма
18,0
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (91)

  • +1
    Идеальный язык системного программирования пока не сделан, увы. И Go конечно даже и не претендует на это звание:) Но в Go есть некоторые идеи, которые лично мне понравились (и которые ИМХО вполне могли бы войти в «идеальный язык» в качестве фич):
    1. Структурная типизация. Как дополнение к классической номинативной она была бы очень интересна.
    2. Встраивание вместо наследования. Удивительно простая и очевидная вещь, которую следовало бы ввести еще в Си. Можно сказать что это более низкоуровневая реализация наследования. А возможность «встраивать» указатели вообще дает крайне интересые эффекты.
    3. Ну и оператор вывода типов := сам по себе весьма милый… знаете, мелочь — а приятно, что именно так сделали. Правда недоделали до конца, и внутри выражений объявлять таким образом новые переменные нельзя.
    • 0
      Немного не ясно значение понятия «системное программирование». Вот в зарубежных версиях документации даже Swift подаётся как «a systems programming language», а wiki говорит:
      A System programming language is usually used to mean «a language for system programming»: that is, a language designed for writing system software as distinct from application software. System software is computer software designed to operate and control the computer hardware and to provide a platform for running application software, and includes such things as operating systems, utility software, device drivers, compilers, and linkers.
      То есть, следует различать системное программирование от программирования микроконтроллеров с 4кб памяти.
      • +1
        Я имел в виду всего лишь «нескриптовое» программирование. Не знаю есть ли правильный адекватный термин. То есть все то, что обычно делают на C/С++, включая и классическое «системное» программирование, и «встраиваемое» (для микроконтроллеров, в том числе самостоятельные прошивки без ОС), и достаточно большую часть «прикладного», включая и софт с GUI, и геймдев, и серверное ПО.
      • +6
        У этого термина есть очень много трактовок. Самая строгая — язык может считаться подходящим для системного программирования если позволяет прямой доступ к железу и на нём можно написать hard real-time OS. C / Rust / D этим критериям соответствуют, Go — нет.

        Процитированное определение из вики довольно бесполезно, так как под него подходят чуть ли не все промышленные языки, оно описывает скорее намерения, чем формальные свойства.
    • +2
      Все эти три пункта в том или ином виде присутствуют во всех трёх нативных языках «новой волны» (Go / D / Rust), едва ли тут можно говорить о новаторстве :)
      • 0
        Я бы так не сказал. В D и Rust присутствуют разные не менее интересные фичи, но конкретно структурная типизация и встраивание вместо наследования, насколько я помню, только в Go.
        • +4
          Встраивание вместо наследования: traits в Rust (частично, проблемы с встраиванием переменных), template mixin + alias this в D (полностью)
          Структурная типизация: traits в Rust (полностью), template constraints в D (частично, подмножество duck typing)

          Мне кажется, что сообщество всех трёх языков вообще настолько сильно «в теме» дизайна языков программирования, что о новаторстве вообще трудно говорить применительно к отдельным фичам, только о сумме принятых решений.
    • +2
      И тем не менее на Go написан некоторый достаточно серьезный софт, например, Docker.
      • +3
        «Некоторый достаточно серьезный софт» можно написать практически на чём угодно. Наличие такого софта — это, скорее, показатель не какой-либо характеристики языка, а степени мотивированности разработчиков.
        • 0
          И порога входа.
          • +2
            При достаточной мотивированности порог входа практически не важен.
  • +8
    > как с PHP — прим. переводчика
    Не ради холивара… просто ради интереса: а какой, по мнению автора, язык (с прицелом на веб, раз уж речь зашла о гипертекстовом препроцессоре), достаточно хорош, чтобы его использовать 20 лет?
    • +2
      Может быть Java?
      • +6
        Почему? Просто потому, что ею пользуются уже 20 лет? Это не критерий, вот Хосни Мубарак тоже 20 лет просидел в кресле, но не обязательно потому, что был хорош:)
        • +4
          1. Для нее созданы тонны фреймворков и библиотек на все случаи жизни.
          2. Это не просто язык, а платформа. Причем достаточно широкая.
          3. Которую использует серьезный бизнесэнтерпрайз.
          4. Она все еще жива и неплохо развивается по разным направлениям.

          Про плюсы языка я писать не буду, дабы не провоцировать холивар. На вкус и цвет все фломастеры разные.
          • +7
            Третий пункт доставляет) А ещё энтерпрайз использует мейнфреймы и кобол. Потому что 40 лет назад ничего другого для их задач не было, и за все эти годы они вложили в эту тему стотыщ миллионов денег. И да, тоже написали тонны фреймворков и библиотек на все случаи жизни. И кобол/бейсик/1С/java — давно уже не просто язык, а платформа.
            )))
            • –2
              Хорошо, каковы ваши варианты? :)

              PS:
              Платформа — это не просто «вагон библиотек», а нечто немного более другое. Вспомним Java ME, Java SE, Java Embedded и т.д.
              • +5
                Да уж, кого-то, видимо, Java совсем обидела, что даже в карму не поленились зайти и отметить это.
          • 0
            ИМХО Всё, что вы привели, на мой взгляд, отнюдь не доказывает, что Java хороша :)
            • 0
              Так я и не утверждаю, что это silver bullet. Просто факт в том, что она уже десятки лет активно используется в достаточно серьезных вещах и активно развивается.
              И кстати, а какие параметры однозначно указывали бы, что язык Z хорош, а Y, по сравнению с ним — плохой?
    • +3
      perl
    • 0
      Не уверен, что мы до автора докричимся (ибо перевод), но Ruby и Python — практически ровесники PHP, насколько мне известно.
    • 0
      Lisp. Абстрактные синтаксические деревья никогда не теряют популярности c:
    • 0
      Haskell, ML, Lisp, Prolog, Oz.
      Можно еще Erlang добавить.
  • –11
    > Нужно быть осторожным в использовании языков, которые не слишком хороши, ведь в конечном итоге можно застрять, и придётся использовать их лет 20 [как с PHP — прим. переводчика].
    Как некрасиво такой жир в переводы пихать. PHP отлично подходит для бизнес-задач, имеет низкий порог вхождения, имеет превосходную BC. Поэтому он и популярнен.
    • +6
      Он популярен по той причине, что оказался в нужное время в нужном месте. Вот хорошая статья где расписаны некоторые его минусы (англ).
    • +30
      Ничего не скажу про PHP, но пихать личное мнение в переводы и правда не очень красиво.
    • 0
      А я лично скажу, что PHP великолепный язык, если из него вырезать половину легаси-функций, остальные стандартизировать и перелопатить парсер, что бы он не валился на на строках вида: "(function(){})();", короче… Если весь пых переписать, оставив лишь поведение и объектную модель, то будет великолепным =)
      • +3
        Если весь пых переписать, оставив лишь поведение и объектную модель, то будет великолепным =)

        Если сделать из пыха скриптовую stateless-джаву?)
        • 0
          Ну есть ещё один вариант, переименовать Java в PHP… ну допустим PHP версии 6, её ведь все ждут? И добавить доллары. Я считаю — это уж точно победа будет. Go будет стоять в сторонке и нервно покусывать свои паскалеобразные выводы типов.
          • 0
            Ага, а import заменить на include_once =)
            • 0
              Почему же, оставить. Всё это можно списать на неумолимый прогресс, ядерное развитие и небольшой отказ от некоторой обратной совместимости ;)
      • 0
        перелопатить парсер, что бы он не валился на на строках вида: "(function(){})();"

        Уже сделано.

        вырезать половину легаси-функций, остальные стандартизировать

        Совсем глупости, типа magic quotes — выбрасываются помаленьку. В остальном, совсем выбросить не получится, для BC придется оставить как алиасы, а так — проводятся эксперименты, думаю, что-то такое увидим в php 7 или php 8.
  • +3
    Всё описанное в статье (включая мощную систему типов, обобщения с ограничениями, интерфейс Iterable для реализации типов с поддержкой for-each, ковариацию и контравариацию, отсутствие nil) есть в языке Ceylon (http://ceylon-lang.org/). При этом язык уже добрался до 1.0.
    По непонятной для меня причине, информационные источники обходят этот язык стороной. Единственное моё предположение, что причина это JVM (хотя Scala и Clojure всё-таки более-менее известны). Кроме JVM в качестве бэкенда поддерживается JS, у авторов есть мысли по поддержке LLVM.
    Рекомендую всем, у кого есть время и интерес в подобных языках, ознакомиться. Просто не мог пройти мимо статьи, в которой опять обделили Ceylon.
    • +1
      Миллионы кодеманкей не могут ошибаться!
    • +21
      Если вы достаточно хорошо знакомы с этим языком — предлагаю вам написать краткую вводную статью здесь, для более широкого «продвижения в массы».
      • +2
        Присоединюсь к этому пожеланию.
    • +2
      Не, julia (http://julialang.org) лучше… Он даже быстрее Go… ))
      • 0
        Слишком далеко до первой версии. Хотя, конечно же, не известна изначально задуманная ветка версий.
    • +1
      У него проблема с кросс-взаимодействием с джавой, так же как и у скалы в некоторых (многих) случаях. В отличие от него, Kotlin как раз на эту тему заморочен. И тоже умеет компилиться в JS. Но вот до 1й версии пока не добрался, да
  • +4
    Ну не знаю, неизменяемые состояния — штука очень специфическая. Изначально она вообще появилась потому, что в математике практически невозможно определить операцию присваивания. Когда математики изучали лямбда-исчисление, они как раз придумали, как обойтись вообще без присваивания.
    Другими словами, фатальный недостаток изменяемых состояний — они плохо описываются математической теорией.
    Но железо-то, точнее кремний, только с присваиванием и работает, и компилятор Haskell таки выдаёт на выходе километры MOV-ов.
    Другими словами, фатальный недостаток неизменяемых состояний — они не поддерживаются оборудованием.
    Вот и думайте, какой из недостатков менее фатален.
    • НЛО прилетело и опубликовало эту надпись здесь
      • +3
        Изменяемые состояния очень просто и понятно описываются конечными автоматами.
        Которые, конечно же, очень просты и понятны для анализа, когда у них 1050 состояний (примерно столько состояний необходимо для расчёта SHA-1). Так-то состояние можно и некоторым количеством функций памяти представлять. Проблема же в том, что подавляющее большинство математических методов опираются на выражения, а не вычисления — а выражения не имеют состояния и неотъемлемого понятия времени.
    • +2
      в математике практически невозможно определить операцию присваивания

      Ошибаетесь. Изменяемое состояние представляется математически (и в чистом функциональном программировании) без каких-либо осложнений. В том же Хаскеле для него есть аж четыре монады (State, ST, STM, IO) с разной спецификой. Первые две из этих монад совершенно чистые, причем.

      В самом простом случае, достаточно всего лишь завести некую струкутуру, которая хранит в себе глобальное состояние, а каждая функция будет принимать ее и возвращать помимо своего значения еще и новое состояние (так работает монада State).
    • +1
      А какая разница, поддерживается неизменяемое состояние оборудованием или нет, если программы с этим самым неизменяемым состоянием всё равно достаточно быстры? Программы всё-таки пишутся для людей, и не имеет никакого значения, нравится процессору исполнять программу или нет.
    • +1
      Изменяемые состояния объединяют время и состояние в одну сущность, порождая кучу ошибок. И это фатальный недостаток изменяемых состояний — с ними сложней работать, они создают ошибки и дают меньше гарантий.

      А не то что они плохо описываются математической теорией.
    • +1
      Самое интересное, что в промежуточном представлении современных компиляторов переменные неизменяемые (single assigment) — при каждом присваивании компилятор заводит новую переменную. А только на последнем этапе неизменяемые переменные с учетом их времени жизни мапятся на реальные изменяемые регистры и ячейки памяти. Так удобнее для оптимизации.
  • +5
    Столько копий сломано в холиварах, а программируют все в мейнстриме до cих пор на Алголе в разных модификациях :)
    • +3
      Это же здорово, огрехи в языке не значат что срочно нужно заменять его другим, в тоже время расширяет кругозор. Я из застрявших на php, но с лёгкостью, когда понадобилось сделал проект на nodejs, в другой раз чтобы не переписывать с нуля, продолжил писать на RoR. Просто сравниваешь плюсы и минусы и выбираешь победителя для конкретного проекта, а не язык на всю жизнь.
  • 0
    Статья неплоха, но излишне придирчива. Видно, что автор не особо программировал на Го, иначе он увидел бы ещё кучу других недостатков. Но в любом случае финальное «заключение» автора отбивает охоту читать всю статью ибо нет ему веры:

    > Go не делает ничего нового
    Полная чепуха. Горутины, каналы, интерфейсы — это замечательные инновационные парадигмы.

    > Go не был великолепно спроектирован с нуля
    Не был, факт. Как и все без исключения другие языки программирования. Недостатки есть в каждом из них.

    > Go — шаг назад по сравнению с другими современными языками
    У нас появилась линейная шкала, где можно отмерить вперед и назад? Вот приснопамятный PHP — это шаг вперед, назад, или все-таки вбок (или вообще в другое измерение ;))? Го прекрастно заполняет нишу между низкоуровневыми языками типа C/C++, Java и медленными, но удобными Ruby,Python и другие. Недаром на Го переходят в основном не С-шники, а как раз рубисты и питонисты.
    • +16
      Горутины, каналы, интерфейсы — это замечательные инновационные парадигмы.

      Да я вас умоляю. Этим идеям десяток лет, не меньше.

      Не был, факт. Как и все без исключения другие языки программирования. Недостатки есть в каждом из них.

      Ага, но не такие недостатки. Авторы Go решили просто проигнорировать успешные разработки в области языков программирования, чтобы сделать попроще. (К сожалению язык проще, чем Brainfuck, придумать сложно).

      У нас появилась линейная шкала, где можно отмерить вперед и назад?

      Можно сравнивать отдельные параметры, что автор на протяжении всей статьи и делает.
      • +3
        Горутины, каналы, интерфейсы — это замечательные инновационные парадигмы.

        Да я вас умоляю. Этим идеям десяток лет, не меньше.

        Совершенно верно. Ровно то же можно сказать про все инновационные фичи других свежих языков программирования, таких как Rust, D, Julia… Так что если отвлечься от занятий казуистикой и копанием в архивах научных статей по ИТ, то приходится признать, что исходное утверждение «Go doesn't really do anything new.» не так уж соответствует действительности, как может хотеться некоторым.

        Не был, факт. Как и все без исключения другие языки программирования. Недостатки есть в каждом из них.

        Ага, но не такие недостатки. Авторы Go решили просто проигнорировать успешные разработки в области языков программирования, чтобы сделать попроще.

        Если бы авторы Go решили все делать как в Rust, то у них и получился бы Rust. В таком непростом деле как проэктирование ЯП не все решения являются единственно правильными — чаще приходится идти на компромисы. Да, Go мог бы быть лучше в некоторых аспектах, но не факт что цена этого улучшения стоила бы того.

        У нас появилась линейная шкала, где можно отмерить вперед и назад?

        Можно сравнивать отдельные параметры, что автор на протяжении всей статьи и делает.

        Сравнивать нужно сравнимое, не так ли? Зачем утверждать что Go не подходит для программирования микроконтроллеров, если Go и не проэктировался для этого? Вот PHP не подходит для программирования микроконтроллеров, теперь сжечь все учебники по PHP?
      • +3
        | Да я вас умоляю. Этим идеям десяток лет, не меньше.

        Идеям да, даже больше десятка лет… А реализациям? Можете привести примеры более-менее успешных языков реализующих Хоаровскую СSP?

        | Ага, но не такие недостатки. Авторы Go решили просто проигнорировать успешные разработки в области языков программирования…

        Поверьте авторы с их опытом разработки языков точно знали, что делали… А вы явно пытаетесь судить о вкусе устриц, которых не ели… Если вам нужны реально работающие приложения, это одно. А если вам нужны «успешные разработки в области языков программирования», это другое. Напишите пару нормальных проектов на Haskell'е, потом обсудим…
        • 0
          >> Можете привести примеры более-менее успешных языков реализующих Хоаровскую СSP
          Occam? :-)
          • 0
            Я могу еще пару языков привести… Например, лимбо… Но назвать их сколь-либо популярными сложно…
            • 0
              Думаю на транспьютерах язык был весьма популярен
              • 0
                Ну да… Только сами эти транспьютеры можно было на пальцах пересчитать… )
        • 0
          >> Можете привести примеры более-менее успешных языков реализующих Хоаровскую СSP?
          Erlang
    • +1
      Забавно что при этом у низкоуровневой Java есть generics, в отличие от go.
      • 0
        Ну вроде это просто сахар для Object, т.е. тот же Interface а не полноценные generics как в C#/C++. Может что в последних версиях Java поменялось, я не в курсе.
        • 0
          На уровне тип-системы и compile-time это +- полноценные generics. А то что в runtime их не существует — особенность, с которой надо мириться.

          В контексте статьи выше — они покрывают те примеры что у автора.
  • +11
    В своё время высказывался по оригинальному варианту статьи. Сильно сомневаюсь, что автор на самом деле хоть что-то пишет на Go. Ни rust ни haskell он сам не использует, судя по его репозиторию. Там в основном питон и cи. На Go там только клон btcd без единого коммита. В общем компетенция автора во всех трёх языках упоминаемых в статье, вызывает сомнения…

    Тем кто реально писал на Go подобное сравнение делать бы даже в голову не пришло. Go реально рабочий язык разработки, успешно решающий вполне конкретные практические задачи. И решающий явно лучше других.
    Rust даже в beta не вышел и у него даже нет формализованнного стандарта. Грубо говоря, языка как такового еще нет, есть только название языка и базовые концепции синтаксиса.
    Нaskell — академический язык, с очень высоким порогом входа. В мире очень мало (относительно других языков) команд, пишущих реальные проекты на Haskell'е. Он эдакий современный smalltalk, из которого идеи и приемы разработки растаскивают по другим языкам…

    Он просто не в курсе, чем силён Go…
    • +1
      Кстати, можете пояснить, почему любители Go постоянно упоминают «формальный стандарт» или «спецификацию». Ни от кого больше такого упора на её наличие не встречал.
      У Rust есть doc.rust-lang.org/rust.html — тут весь язык расписан довольно подробно. Крупные публичные проекты на нём уже есть. Сторонних библиотек пока не так много разве что.
      • +4
        Дело в том, что у языка Go есть дизайн. В спеках и сопутствующих документах рассказано, почему и для чего было сделано именно так, а не по другому. Следование указаниям спецификации позволяет избежать тех ошибок, от которых авторы языка и хотели избавить программистов.
      • +1
        Не знаю, почему другие не интересуются спецификациями. Без этого совершенно не понятно как разрабатывать на rust'е проекты с более-менее длительным жизненным циклом. Элементарный style guide невозможно устаканить без стабильного синтаксиса. Выход новой версии компилятора ломающего обратную совместимость автоматически ведет к переписыванию проекта.
        Сразу скажу, лично я к Rust'у отношусь позитивно и стараюсь следить за развитием языка. Но на данном этапе он с Go не конкурент. Более того, складывается впечатление что конкурентами в полной мере они и не станут. Скорее Rust будет вытеснять тяжеловесный Haskell и прочие… А Go так и останется языком дуболомной сетевой и системной разработки, когда надо написать быстрые и конкретные инструменты без заумных абстракций…
        • 0
          Rust и Haskell по областям применения практически не пересекаются.
          Концепция времени жизни в Rust очень сложна. Разбираться в связанных с ней ошибках при компиляции тяжело (хотя и проще, чем отлаживать код на C++ :-)). В приложениях, где допустима ленивость и сборка мусора Haskell еще долго останется предпочтительнее.
          Теснить Rust будет в первую очередь C и C++. А потом Java, Go и Python из области разработки GUI (где отказ от GC и хорошая параллельность поможет обеспечить хорошую реактивность не слишком большой ценой).
    • 0
      Rust даже в beta не вышел и у него даже нет формализованнного стандарта. Грубо говоря, языка как такового еще нет, есть только название языка и базовые концепции синтаксиса.
      Rust готовится в этом году зарелизиться. Afaik, особых синтаксических изменений больше не предвидится (до 2.0; остальное прячут под feature gates), сейчас доделывают какие-то специфические вещи и шлифуют стандартную библиотеку.
    • 0
      Нaskell — академический язык...

      Что значит «академический»? Вы полагаете, что Haskell можно использовать исключительно для академических целей?

      … с очень высоким порогом входа.

      Смотря как входить…
  • –9
    По прочтении статьи создаётся впечатление, что этот ваш Go — просто попытка переизобретения Java десяти-пятнадцатилетней давности, поправьте, если ошибаюсь. А еще в нём можно не писать тип переменной при присваивании — большой шаг вперёд :)
    • +3
      Вы хотя бы прочли спецификации языка перед тем как делать такие утверждения. А то судите по Go только со слов других людей, которые сами не факт что разбираются в предмете обсуждения. Go проэктировался в первую очередь для высококонкурентной обработки данных, призваный сместить текущую парадигму потребления ресурсов многоядерных CPU через использование тредов.
      • –1
        Да, я действительно не прочитал, поэтому оговорился о том, что впечатление такое создалось после прочтения статьи. Всё равно, я не очень понимаю, почему смещение текущей парадигмы потребления ресурсов так помешало сделать те же дженерики, например.
        • +1
          Конкретно дженерики ради простоты. Язык все ещё находится в стадии становления, думаю, вопрос дженериков просто отложили как не самый важный, возможно в версии 2.0 что-то такое добавят.
        • 0
          Где-то слышал, что отсутствие некоторых фич, вроде шаблонов типов, связано с желанием максимально ускорить компиляцию.
          Правда ль, нет? Призываю в ветку экспертов!
          • +2
            Я не эксперт, но был на нескольких митапах по Go в San Francisco и задавал вопрос по generic'ам. Мой вольный пересказ: generics — это конструкция, которая повлияет на все другие аспекты языка — структуры, функции, методы, интерфейсы, каналы, пакеты — на все. На данный момент в языке все эти конструкции — ортогональны. Таким образом ввод generics сильно усложнит язык — как реализацию, так и использование. Также generics сами по себе — сложная вещь, есть подходить основательно (variance, type constraints). Разработчики Go не хотят вводить generics «чтобы было». Они пока не нашли решения, как можно ввести generics в go, чтобы это не навредило языку. Может и не найдут, но от generics окончательно и бесповоротно они не отказываются пока.
            • 0
              Вот грустно это, что на дворе 21-й век, космические корабли бороздят просторы хабра, а гугл релизит язык с претензиями на мэйнстрим, и без вменяемой поддержки generic programming. Ну не такая это сложная штука, и много где уже реализована, так что задизайнить базовую поддержку вполне можно было (вариантность, сложные type constraints и прочее можно оставить на потом, это все прекрасно прикручивается поверх).
          • 0
            Вряд ли проблема со скоростью компиляции дженериков есть где-то, кроме С++
  • +2
    вариант из Rust — «трейты» [или «примеси»/«миксины», в зависимости от перевода — прим. переводчика]
    Всё же traits и mixins — разные зверюшки независимо от перевода :)
  • +4
    Смотрю я на этот Rust и ничегошеньки в этих !"№;%:?*()_ не понимаю. Очень много пунктуации.
    Не зря Роб Пайк говорит на каждой конференции (по крайней мере из просмотренных мною):
    Go разрабатывался как компилируемый язык, который бы ощущался как скриптовый.

    Это у разработчиков на ура получилось.

    Отсутствие возможностей переделать синтаксис языка «под себя» как по мне — только плюс, хоть бы потому, что в среде разработки на функцию можно попасть в один клик, а перегруженный оператор поди найди — где и как.
    Возмущения по поводу отсутствия аналогов Option и Maybe в Go тоже мне не ясны, хоть бы потому, что при желании сделать объект-обертку для подобной схемы — дело нескольких минут.

    Короче говоря — на фоне преимуществ (простота, скорость, параллелизм) аргументы «против» как-то не очень убедительны.
    • +2
      Насчёт Rust'a — язык от переизбытка символов успешно избавляется с новыми версиями.
    • +3
      Возмущения по поводу отсутствия аналогов Option и Maybe в Go тоже мне не ясны, хоть бы потому, что при желании сделать объект-обертку для подобной схемы — дело нескольких минут.
      Вы, кажется, недооцениваете полезность Option. Когда в самом типе содержится информация о том, может ли указатель быть нулевым, компилятор всегда может проследить, как используется объект этого типа и гарантировать, что если программа скомпилировалась, то она не упадёт во время исполнения от разыменования нулевого указателя. Обёртку-то сделать можно, но одно дело, когда это реализовано и активно используется в стандартной библиотеке и рекомендуется к использованию программистом, а другое, когда это отдано на откуп программисту. Я не знаком с Go и мне не понятно как вы реализуете такую обёртку без сопоставления с образцом. В Rust сопоставление с образцом гарантирует, что, если указатель храниться в Option, то будет рассмотрен случай когда он равен None. К тому же, судя по данной статье, любому указателю в Go можно присвоить значение Nil, что лишает вашу обёртку особого смысла.
      • 0
        В Go методы можно вызывать и на Nil'ах тоже, обрабатывая этот случай, если это нужно. В терминах Java получается, что внутри метода this==null, а непосредственно вызов метода не приводит к NPE. Также go-way подразумевает корректную обработку таких нулевых значений, например Nil слайсы ничем не должны отличаться от пустых (и не отличаются в стандартных функциях).
        Это не прямая альтернатива Option, но по-своему позволяет решить заметную часть связанных с Nil'ами проблем.
      • +1
        Прочитал о Option более детально — согласен, недооценил. В Go отловить на этапе компиляции указатель на nil не получится.
    • +1
      Не параллелизм, а конкурентность: talks.golang.org/2012/waza.slide#1
    • 0
      >> Отсутствие возможностей переделать синтаксис языка «под себя» как по мне — только плюс, хоть бы потому, что в среде разработки на функцию можно попасть в один клик, а перегруженный оператор поди найди — где и как.

      Это скорее претензии к тем IDE, которые не умеют делать Go to Definition на операторах. Например, VS это замечательно умеет для C# — если навести мышой на оператор, подсказка дает его полное название (включая класс, где он определен) и сигнатуру, а Go to Definition прыгает туда.
  • +2
    Перевод приличный, спасибо за старания! Могу посоветовать дальше просто начать переводить смысл пар предложений, не хватаясь за структуру. Потом надо просто перепрочесть, чтобы проверить смысл. Тогда стиль будет более естественный русскому языку.

    У вас часто появляются слово «вещей», но в английском никаких «вещей» по смыслу нет, и в русском по смыслу должно идти не всегда дополнение, а скорее обстоятельство. Например «для многих разных вещей» можно заменить «множеством способов» или «совершенно по-разному».

    > Вместо возврата строки или нулевого указателя мы возвращаем объект, который может содержать, а может не содержать строку.

    Более свободный перевод даст возможность использовать порядок слов, ведь в русском языке он заменяет и логическое ударение, и артикль, то есть степень определённости. Здесь, например, «строку» лучше поставить перед «может содержать, а может и не содержать», которая будет замыкать предложение, ведь логическое ударение именно на эту конструкцию.

    В целом же хороший перевод, спасибо!
    • 0
      Отдельное спасибо за перевод словосочетания «встроенные системы»! :)
  • +2
    После прочтения оригинальной статьи тоже возникло ощущение, что автор — студент-теоретик, и в реальных проектах не участвовал (тем более, не писал на Go что-то кроме своего блога).

    Прочел его резюме на сайте и так и оказалось — мальчик еще не выпустился из универа, что, в общем-то. его взгляды на идеальный язык программирования объясняет.
  • 0
    > я никогда не видел кого-нибудь, пропагандирующего Haskell в качестве языка для программирования роботов

    www.haskell.org/haskellwiki/Applications_and_libraries/Robotics
    Не совсем то, конечно, т.к. тут команды поступают через внешний интерфейс. Один мой друг программировал лего через NXT, хвалил библиотеку.

    Го во многом хорош, но возможностей интеграции с языком и zero cost abstractions всё-же сильно не хватает.
  • 0
    Что в Гоу нет указателей и небезопасного кода — враньё. Там есть модуль unsafe как раз для этого и предназначенный.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.