Как стать автором
Обновить

Комментарии 94

interface же еще прям огонь

Нравится возможность из функции возвращать несколько значений

Сорри, а разве в PHP из функции нельзя вернуть массив? И туда набрать несколько значений, какие надо.

Конечно можно вернуть массив.

list($first, $second) = $this->someMethod();

Но на мой взгляд это менее удобно по нескольким причинам:

  1. Нет никаких гарантий что метод вернет именно два параметра в массиве

  2. Нет никаких гарантий что принимаете вы два параметра

  3. Нет никакой гарантии что элементы возвращаемого массива не будут перепутаны.

  4. И так далее.

Когда нужны пункты 1, 2, 3, то надо возвращать уже объект. В PHP есть дурная привычка все делать через массивы с ключами, в результате использовать функцию возвращающую массив без того что бы смотреть сорсы той функции, становится невозможно.

Это тоже самое что в Яве бы все плюнули и стали Map-ами швырятся.

П.С. при возвращении массивов есть и более короткий синтаксис уже, если заранее известны ключи

["one" => $one, "two" => "two"] = $this->someMethod();
[$one, $two] = $this->someMethod();

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

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

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

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

Про сигнатуру согласен, она нагляднее)

Иногда мне не хватает каких-то простых и таких привычных для PHP-разработчиков функций в стандартных библиотеках (или я их плохо искал). Например, определение минимального/максимального числа. Сортировка массива.

Плохо искали. math.Min math.Max для сортировки пакет sort

Но функции min/max для integer там таки нет.

это вопрос времени, дженерики уже практически завезли

Да, ждем.

Под каждой статьей про GoLang с 2015 года вижу подобные комментарии...

Дженерики выйдут в версии Go 1.18 в феврале 2022 г. В ветке master они уже доступны, можно их попробовать, используя gotip:
pkg.go.dev/golang.org/dl/gotip

Пока что generic-функции для работы со slices и maps будут доступны как экспериментальные (чтобы обкатать их), но потом их перенесут в стандартную библиотеку:
slices.go
maps.go

Ну какой же это функциональный язык? Если вы хотели сказать процедурный, то это тоже не совсем верно, потому что ООП там есть, более-менее полноценный, без классов, да, но классы - не основа ООП, можно похоливарить на тему инкапсуляции, но в python ее тогда тоже нет.

Что касается наследования, то его нет, но есть композиция, вкладываете одну структуру в другую и она может вызывать ее методы:

package main

import "fmt"

type Printer struct {
  value string
}

func NewPrinter(value string) *Printer {
  return &Printer{value: value}
}

func (p Printer) Print() string {
  return p.value
}

type ComboPrinter struct {
  Print
  id int
}

func NewComboPrinter(id int, value string) *ComboPrinter {
  return &ComboPrinter {
    Printer: *NewPrinter(value),
    id: id,
  }
}

func (cp ComboPrinter) PrintCombo() string {
  return fmt.Sprintf("%d: %s", cp.id, cp.Print())
}

func main() {
  cp := ComboPrinter{}
  
  fmt.Println(cp.PrintCombo())
  fmt.Println(cp.Print())
}

Таким же образом можно и интерфейсы внедрять в структуры (правда, пока не придумал - зачем в реальном проекте такое).

Про пакет sort вам уже выше сказали, да, не очень удобно, но есть. Тут другая парадигма, и стоит отбросить свои привычки из php и изучить язык поглубже.

Что касается поиска ключа в map - такая функция есть:

value, ok := mymap["search_key"]

if ok {
  // do something
} else {
  // no key
}

Вы правы, про ООП вопрос холиварный. Нужны выверенные формулировки чтобы точно выразить свою мысль :)

Спасибо за примеры.

Про возможность встраивания структуры в структуру знаю, как найти ключ в мапе тоже понятно. Я же писал о том что нет аналога array_search/in_array.

Каждый раз когда я не могу найти какую-то для меня обычную "стандартную" функцию вроде проверка присутствия элемента (не ключа) в мапе (in_array в PHP), я удивляюсь и задаю вопрос: "Ну почему вы ее не реализовали? Неужели только мне она понадобилась?".

.includes()?

Что вы имеете ввиду? Не смог ничего связанного в интернетах найти.

Ну почему вы ее не реализовали

Предположу, что из-за отсутствия перегрузок и генериков, а реализация через рефлексию будет в 50 раз медленнее.

Отсутствие дженериков - это, безусловно, боль, но это не означает, что вот вашу конкретную задачу невозможно решить. Нужно начать мыслить иначе, так как в Go такие штуки реализуется, так сказать, наизнанку. Вот вам пример Реализации универсальной функции `Includes()` грубо и на коленке за несколько минут, пожалуй, можно и поизящней придумать:

Решение с итератором
package main

import "fmt"

type Iterator interface {
	HasNext() bool
	Next()
	Get() interface{}
}

type Iterable interface {
	MakeIterator() Iterator
}

type Comparable interface {
	Iterable
	Eq(it Iterator, v interface{}) bool
}

func Includes(sl Comparable, v interface{}) bool {
	for it := sl.MakeIterator(); it.HasNext(); it.Next() {
		if sl.Eq(it, v) {
			return true
		}
	}

	return false
}

type IntSlice []int

func (isl *IntSlice) Eq(it Iterator, v interface{}) bool {
	return it.Get() == v
}

func (isl *IntSlice) MakeIterator() Iterator {
	return NewIntSliceInterator(isl)
}

type IntSliceIterator struct {
	isl   *IntSlice
	index int
}

func NewIntSliceInterator(isl *IntSlice) *IntSliceIterator {
	return &IntSliceIterator{isl: isl}
}

func (it *IntSliceIterator) HasNext() bool {
	return it.index < len(*it.isl)-1
}

func (it *IntSliceIterator) Next() {
	if it.index < len(*it.isl)-1 {
		it.index++
	}
}

func (it *IntSliceIterator) Get() interface{} {
	return (*it.isl)[it.index]
}

func main() {
	arr := IntSlice{1, 2, 3, 4, 5}

	fmt.Printf("arr: %v includes %d: %t\n", arr, 4, Includes(&arr, 4))
	fmt.Printf("arr: %v includes %d: %t\n", arr, 0, Includes(&arr, 0))
}
Чуть более примитивный вариант для типовых срезов
package main

import "fmt"

type Comparable interface {
	Len() int
	Eq(index int, v interface{}) bool
}

func Includes(sl Comparable, v interface{}) bool {
	for i := 0; i < sl.Len(); i++ {
		if sl.Eq(i, v) {
			return true
		}
	}

	return false
}

type IntSlice []int

func (isl *IntSlice) Len() int {
	return len(*isl)
}

func (isl *IntSlice) Eq(index int, v interface{}) bool {
	return (*isl)[index] == v
}

func main() {
	arr := IntSlice{1, 2, 3, 4, 5}

	fmt.Printf("arr: %v includes %d: %t\n", arr, 4, Includes(&arr, 4))
	fmt.Printf("arr: %v includes %d: %t\n", arr, 0, Includes(&arr, 0))
}

Да, дженерики позволят решать такие задачи почти также красиво, как в других языках, а тут пока вам нужно реализовать либо примерно также, как это сделано в `sort.Sort`, либо через итераторы. И пока возникает резонный вопрос: а стоит ли овчинка выделки, ведь банальный поиск можно сделать через ту же лямбду, и, кажется, он будет короче по количеству строк:

package main

import "fmt"

func main() {
	arr := []int{1, 2, 3, 4, 5}

	includes := func(arr []int, value int) bool {
		for _, v := range arr {
			if v == value {
				return true
			}
		}

		return false
	}

	fmt.Printf("arr: %v includes %d: %t\n", arr, 4, includes(arr, 4))
	fmt.Printf("arr: %v includes %d: %t\n", arr, 0, includes(arr, 0))
}

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

Строгая типизация. PHP тоже сейчас идет в сторону строгой типизации, 

не надо путать динамическую/статическую и строгую(сильную)/слабую типизацию

первая это про int x = 1; вторая это про x = 1; echo $x + " banana".

у php слабая динамическая типизация (как у javascript. у python/ruby строгая динамическая типизация). вот как раз строгая типизация php вылечила бы 90% его болячек, но это из области фантастики, т к сломает примерно весь существующий код

ну тут бы скорее согласился с автором, php за последние 5 (или сколько там) лет очень типизировался - по крайней мере, появились возможности задавать типы для аргументов, для возвращаемых значений, сначала это добавили только для скалярки и простых типов, потом добавили классы. По крайней мере мне кажется, что автор имел в виду этот аспект

Конечно, можно по-прежнему в стиле пхп5 писать весь код без единого типа на "магии" и авто-приведении - но при желании можно накрутить "строгий" режим и обвязать поля классов типами как следует и ходят легенды, что это даже помогает ускорить выполнение кода

Да, вы правы, имелась ввиду динамическая типизация. Косноязычность моя проклятая)

При желании всё есть:

<?php declare(strict_types=1);

$x = 1;
echo $x + " banana";

Такой код вызовет TypeError: Unsupported operand types: int + string

О, а вот это полезно. Интересно, на практике используются людьми?

Сам давно не пишу, но у своих коллег в коде - видел :-)

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

Да, уже пару лет пользуемся. В code sniffer есть правила, не дающие заапрувить пулл реквест с методами без заданных типов.

О, а вот это полезно. Интересно, на практике используются людьми?

Да, любой хороший разработчик заходит в настройки phpStorm, правит шаблон создаваемого класса, и в любой созданный класс будет сразу создаваться со строчкой

declare(strict_types=1);

Конечно. В современных PHP проектах сейчас почти все так пишут (субъективное впечатление). Плюс используются стат анализаторы, чтоб поймать косяк с типом заранее, а не в рантайм.

У нас так - весь новый код только со стриктом, в старый добавляется в момент, когда понадобилось его править.

При желании всё есть

Я так оправдываю дженерики в докблоках. ;)

А strict_types действительно очень помогает, да.

У РНР так-то смесь.

http://sandbox.onlinephpfunctions.com/code/60377181cb139ac367c13660a54f4e01733472ec

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

x = 1; echo $x + " banana"

А тут поможет strict_types.

Это вы про статическую типизацию. Строгая это про "1"+1, и это так просто не убрать из языка

Скажите, а зачем вам "сортировка карты по значениям"? Чего вы хотите добиться, какую задачу вы решаете?

Обход в цикле аналогично циклу Для Каждого в 1С. Если обходишь коллекции с индексом, получается детерминированный результат. А коллекции на КлючИЗначение - нет.

А зачем вам "детерминированный результат"? Вот к примеру... ну скажем конфиг, и в нём опции. Вам важно, в каком порядке они располагаются? Точно важно? Вы какую задачу решаете? Может, вы не тут структуру данных используете?

Я сейчас не решаю задачу. Я привел пример необходимости и указал на недостатки. А детерминированный результат при программировании это очень удобно. Обращаешься, например, элемент[0], получаешь первый элемент в коллекции. Удобно, чё. Если был бы рандом, было бы не так удобно.

В карте нет "первого элемента". По смыслу структуры данных. Есть элемент по ключу. Есть ещё структура данных "множество", там тоже нет первого элемента.

Зачем вы мне это пишите? Вы думаете я этого не знаю? Я разве писал что в карте есть первый элемент?

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

Я привел пример детерминированного результата, в котором привел пример получения первого элемента индексированной коллекции.

Мы точно должны вести диалог по вашим правилам: "Вы читаете несколько символов в моем комментарии, а остальное домысливаете, а я оправдываюсь на основе ваших домыслов"?!!

Для карты определен детерминированный результат. Получение элемента по ключу. Или получение множества ключей. Или получение мультимножества значений.

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

PS минусы не мои.

Для карты не определен детерминированный результат при обходе коллекции в цикле Для Каждого (For Each).

https://habr.com/ru/post/591527/comments/#comment_23752001

Эм... На java не пробовали программировать? )

Почему-то уже лет 10, наверно, нет такого желания) До этого времени были попытки.

Наследования реализации нет. И мне его не хватает. Очень непривычно, когда нет возможности расширить реализацию.

В большинстве случаев нужна скорее композиция, чем наследование. А для композиции, как уже сказали выше, существует type embedding.

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

Если нужно протестировать внутренний интерфейс программы, тогда положите тест рядом с исходником, если внешний - в отдельный пакет. Зависит от вашего подхода (blackbox/whitebox). Для облегчения создания моков следуйте правилу: "принимайте интерфейс, возвращайте структуру".

Также очень сильно не хватает исключений (Exception) и возможности их перехватывать и обрабатывать. Приходится городить конструкции с возвратом ошибок из функций. 

Зато всегда четко видно, кто и что может вернуть без необходимости залезать в документацию. Для обработки ошибок по иерархии существуют конструкции errors.Is, errors.As, fmt.Errorf("%w"), для получения контекста ошибки можно использовать type assertion на пользовательский тип.

В Go только строгая типизация.

Я бы ещё обозначил, что типизация структурная, это очень важная особенность.

типизация структурная, это очень важная особенность.
Структурная только для интерфейсов. А для типов — только именная. Причём предельно жёсткая именная. Такой код вызовет ошибку «mismatched types ti and int64»:
type ti int64
var a ti = 1
var b int64 = 2
a += b
Зато всегда четко видно, кто и что может вернуть без необходимости залезать в документацию.

Только там еще panic может вылезти, а ловить их нормального способа нет.
Есть костыль типа


defer func() {
    if err := recover(); err != nil {
        ???
    }
}()

Но тогда не понять в каком месте оно вылетело и как откатывать состояние.


Враппер так же не сделать без дженериков.

Также очень сильно не хватает исключений

Ну panic(Описание исключения) же. Почти то же как в 1С Enterprise ВызватьИсключение(Описание исключения)

Исключения можно ловить, ловить паники можно только через такую-то матерь и только в виде костыля, насколько мне известно. Так что это не одно и то же.

Ловить в смысле попытка-исключение-конецпопытки?

Ага. А еще и по типу определять NPE это или out of bounds какой. Речь в общем-то не про конкретно 1с исключения, а про любые языки с исключениями типа той же Java.

Ну там же есть библиотеки типа syscall, как я понимаю одна из идеологий голэнг - "нету функционала, запили сам."

я слышал, что "запили сам" это не про go — люди много всякого просят отдать на публику, но разработчики языка очень ревностно относятся к такому, хотя внутри языка вполне себе используют самостоятельно (high precision timers как пример). Так что люди может быть и рады бы такое провернуть, но оно оборачивается каким-нибудь костылем типа имплементации этого где-нибудь в сишной либе подключенной через FFI или оберткой поверх fork() которое по факту разрешает чайлдам умирать на панике и просто запускает новый fork(). Ну и конечно форки самого голанга, но такое почти никогда не присутствует вне конкретных кампаний. Короче пока нет нормального способа их реализовать, либо я не слышал о нем.

А есть пример бросания и ловли?

Тоже много лет на PHP/JS и на днях захотелось нового, читаю изучаю Go.

Расскажите, какие задачи вы им решаете? В каких показал себя лучше чем PHP или нода?

Рассказать подробности проекта не могу, информация не публичная.

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

PHP и Go не конкуренты, у них скорее симбиоз.

Например с помощью Go решаются задачи с большим объемом данных и трафиком.

Было бы интересно почитать как организуется такой симбиоз на практике.

Бизнес логика на PHP, микросервисы где критичен перформанс на Go.

У Slack в блоге подобные статьи проскакивали.

Как вариант gRPC и\или очереди RabbitMQ (и ему подобные).

PHP все что касается например интерфейса, работы с пользователями, формочки вот это вот все. На Go можно писать API, сервисы, которые что-то делают в фоне. Я следующий проект так собираюсь делать, если он состоится.

Про моки доя тестов - в Go тоже все хорошо, и есть отличное решение: https://github.com/golang/mock

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

Отсутствие наследования меня не расстроило, наоборот, навешать какой-то метод на любой объект - некая дополнительная степень свободы.

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

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

для многопоточности от слова "потоки" есть pthreads и parallel.

в parallel, кстати, есть каналы. в остальных подходах они с переменным успехом эмулируются.

А процессы разве не создают своих отдельных потоков?

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

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

mockedObject("Имя функции", параметры...).Return(что должно возвращаться)

Можно ещё указать, например, что он должен вызываться только один раз:

...Return().Once()

И, понятно, что mockedObject должен содержать заглушки этих функций.

У меня была похожая ситуация, но на Go уже давно. Синтаксис и идеология совершенно отличаются от того, к чему привык. Да, тут нет привычного ООП, но много других возможностей. Что-то нравится, что-то нет. В первое время сильно раздражало, что сборка не шла из-за того, что был неиспользуемый пакет или неиспользуемая переменная. Точнее не понимал, почему нельзя было просто игнорировать их. Я понимаю для чего это сделано - Go позиционировался (может и сейчас так же) как язык для обучения разработке, но подобные ошибки не дают сосредоточиться на коде. А, вот, что мне точно нравится в этом языке - результат сборки - статический бинарник, который не зависит от библиотек системы (хотя и бывают некоторые исключения). Достаточно скопировать один бинарник. Без пачки библиотек.

А если Go сравнивать с Rust - какие плюсы и минусы? Вопрос к тем, кто имеет представление об обоих языках)

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

Несмотря на то, что эти языки любят сравнивать, они концептуально очень различные. Go создавался как простой язык для микросервисов в корпорации добра (Google), на нём нормально работает подход "сел и написал". Благодаря некоторой утиной типизации и своим подходам к классам, нет необходимости заранее проектировать интерфейс, потом проектировать иерархию классов, как например в Java. В этом весь Go - быстрый, относительно не сложный, не тебующий большой подготовки. К тому же у Гоши, на мой взгляд, очень удобная нативная система сопрограм.

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

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

Порог входа в Go на порядок ниже, чем в Rust. Во втором придётся знатно вывернуть наизнанку все свои представления о временах жизни и областях видимости.

На расте приятно писать, но через пол года, вернувшись к коду - без пол-литры не разберешься. На GO наоборот, сахара нет, зато читаемость кода лучше. Туда же изменчивость раста и стабильность GO. Ну и если хотите писать низкоуровневые вещи то GC go, его модель памяти и пр. может вставлять палки в колеса, а раст подойдет отлично.

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

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

Разработчику нужно больше интеллектуальных способностей, памяти и внимания, чтобы понять и поддерживать код на Go

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

PS GO строго типизированный

PS GO строго типизированный
Полудинамически типизированный из-за golangdocs.com/type-switches-in-golang и interface{}. В строготипизированном ЯП рантайм не таскает за собой типы ко всем значениям, хотя бы не так явно и легкодоступно.
Вот как раз больше интелектуальных способностей и нужно, чтобы понять, что там очередной разработчик сварганил вооружившись, без спорно, очень мощной системой типов раста
Да не очень мощной, это же не Scala какая-нибудь. Умение читать код на любом ЯП приходит с опытом. Без опыта, конечно, читать некоторые ЯП проще, чем некоторые другие. Я говорю об интеллектуальных требованиях при разработке, которые исходят от сложности самого кода. Сложный код, сложные системы на Go требуют большей памяти и внимательности, чем Rust, где намного проще расслабиться и сосредоточиться на доменной области. Потому что синтаксис и компилятор сильнее держат разработчика в рамках каких-то гарантий.

Полудинамически типизированный из-за golangdocs.com/type-switches-in-golang и interface{}. В строготипизированном ЯП рантайм не таскает за собой типы ко всем значениям, хотя бы не так явно и легкодоступно.

Строго типизированный потому что нет магических преобразований из int32 в int64 например.

Рантайм в GO ничего не таскает, тем более для всех значений. Информация о ниже-лежащем типе действительно вшита внутрь реализации interface{}. Но это касается только интерфейсов.

Сложный код, сложные системы на Go требуют большей памяти и внимательности, чем Rust, где намного проще расслабиться и сосредоточиться на доменной области

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

Строго типизированный потому что нет магических преобразований из int32 в int64 например.
Type juggling не имеет отношения к виду типизации. В динамическом ЯП может отсутствовать (в Python, к примеру).
Динами́ческая типиза́ция — приём, используемый в языках программирования и языках спецификации, при котором переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной.
Это из Википедии. Динамическая типизация — это когда сигнатура нам не говорит о поведении. Как в Go через interface{} и последующую проверку типа переменной, когда она связывается с типом во время исполнения, динамически. В Go можно вообще не использовать типизацию и всё делать через interface{}. Но чаще всё-таки используют статическую типизацию, поэтому я и называю это "полудинамической типизацией" — как и в Python или PHP с использованием статических анализаторов типов (MyPy, Psalm).

Можно, конечно, говорить о практиках использования. К примеру, если бы система типов в Go была достаточно выразительна, и никто бы использовал динамическую типизацию. Наверно, в Scala как-то так. Но я вижу это в Go коде лишь чуть реже, чем всегда, поэтому определение «полудинамический ЯП», я думаю, вполне заслужено.
Вот Rust — это строго-типизированный ЯП без всяких «но» (хотя и там есть мелкое нарушение параметричности).
Рантайм в GO ничего не таскает, тем более для всех значений.
variable.(type) можно на переменной любого типа вызвать. Точнее, на переменной с меткой любого типа (в случае динамики некорректно говорить о типизации). Таскает рантайм за собой метки для всех значений. =)
реализации interface{}.
Что это вообще значит? interface{} — это просто top type, он населён всеми типами. Другими словами, все типы являются interface{}. Это важно лишь для статического анализа.
кроме сосредоточения на доменной области нужно еще будет думать о памяти, синхронизации и пр
Так и на Go вам об этом думать не меньше, а то и больше. Rust — это же не C, там работа в основном под капотом засчет умного компилятора (который может быть умнее из-за сильных гарантий). Но кроме этого с Go ещё нужно ещё держать в голове более полную модель кода, потому что она намного хуже описана типами. В Rust всё же модель кода лучше описана типами и статический анализатор берёт больше работы на себя по контролю над программистом. Поэтому больше личного внимания можно уделить домену. Конечно, после выработки привычки на нем писать.
Это касается больших кодовых баз. Чем меньше и чем проще система, тем меньше проблем с Go, и меньше бенефитов от лучших гарантий. Собственно, с какого-то объёма оно вообще вообще нормально ложится и на скрипты без всяких типов.

Вы точно не путаете статическую/динамическую типизацию с слабой/строгой? Вами было заявлено что GO слабо типизированный, причем тут википедия и динамическая типизация?

Но я вижу это в Go коде лишь чуть реже, чем всегда, поэтому определение «полудинамический ЯП», я думаю, вполне заслужено

Если "чуть реже, чем всегда" то это говорит о качестве таких проектов, кстати пиханием interface{} где надо и не надо страдают в основном новички которые перешли с динамических языков. Далее, в расте тоже есть std::any, он теперь тоже "полудинамический ЯП"?

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

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

    var a interface{} = int64(5) 
    print(unsafe.Sizeof(a)) //16, тут 8 бит значение и 8 - тип

    var b int64 = 5
    print(unsafe.Sizeof(b)) //8, тип известен
    на этапе компиляции, хранить ничего не нужно

Что это вообще значит? interface{} — это просто top type, он населён всеми типами. Другими словами, все типы являются interface{}. Это важно лишь для статического анализа.

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

Чем меньше и чем проще система, тем меньше проблем с Go, и меньше бенефитов от лучших гарантий. Собственно, с какого-то объёма оно вообще вообще нормально ложится и на скрипты без всяких типов.

Вобщем вы частное привели к общему, все остальное - маркетинг.

Go
&plus; супер низкий порог входа. возможно один из простейших способов стать вайтишником за много денег.
&plus; fibers
&plus; все что нужно для написания микросервисом из коробки
− if err != nil
− генерики отсутствуют. хотя выше по треду обещают, но кто знает сколько их еще будут осваивать и как это повлияет на качество библиотек.
− архитектура и библиотеки местами склепаны из заглушек потому что
− не предназначено для general use. например получение прав доступа на разных файловых системах и ОС вернут не то что ожидается, нет доступа к субмилисекундным таймерам и так далее — дьявол кроется в деталях
− написать дедлок довольно просто
− все еще нельзя жить без gc. Хотя вроде wasm направление помаленьку продавливает этот вопрос


Rust
&plus; система типов, включая трейты и optional-типы
&plus; good defaults
&plus; fearless concurrency, memory safety и проче empowering штуки из слоганов по большей части правда
&plus; general use даже больший чем у C/C++
&plus; batteries included по всем направлениям радуют людей — документирование, тестирование, шаблонизация проектов (типа wasm-pack), всякие верификации лицензий, уязвимостей и так далее и тому подобное. Туда же всякие areweXYZyet, где XYZ — какое-нибудь направление в разработке — веб, нейронки, геймдев и тд. Правда не знаю как с этим обстоят дела у go дальше тестов и форматирования.
&plus; отличное исполнение платформозависимых фишек и управление ими в коде.
− высокий порог входа даже для хардкорщиков из плюсов и си
− некоторые вещи выражаются слишком громоздко
− версионирование библиотек часто сопровождаются болью, в частности из-за разделения на stable и nightly
− иногда слишком фанатичное коммунити
− мультипоток хоть и достаточно fearless он все еще довольно болезненный.
− асинхронщину завезли относительно недавно и степень адаптирования довольно низкая. плюс там вроде были какие-то нюансы с executors для них
− народ очень хочет GAT, но его еще вроде не успели завести, хотя оно уже в пути.
− у хаскелистов имеются вопросы относительно генерализации некоторых штук


Но мне до сих пор непонятно почему идет холивар Go vs Rust. Да и минусы начинают всплывать или когда ты только начал и что-то делаешь не так либо когда преисполнился и выжимаешь из языка что можешь.

+ fibers

Всё-таки threads. fibers - немного иное

− if err != nil

Проверять, что действие не возвращает ошибку не такое плохое решение.

− генерики отсутствуют

Без них прям никак?

− все еще нельзя жить без gc

Как вы думаете, почему один из создателей ЯП Go, имевший ранее опыт в создании другого довольно успешного ЯП, пошёл на такой шаг? Ведь Си довольно быстрый и в нём нет никакого gc, правда могут быть серьёзные проблемы, когда прозевают момент с управлением памятью.

Всё-таки threads. fibers - немного иное

Таки горутины ближе к нитям, так как сопрограммы, и уж точно не являются потоками.

Без них прям никак?

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

Как вы думаете, почему один из создателей ЯП Go, имевший ранее опыт в создании другого довольно успешного ЯП, пошёл на такой шаг? Ведь Си довольно быстрый и в нём нет никакого gc, правда могут быть серьёзные проблемы, когда прозевают момент с управлением памятью.мусормус

Это - довольно дискуссионный вопрос, очевидно, что у авторов было ТЗ от Google, чьи задачи и решает язык. Собственно, об этом и авторы рассказывали: низкий порог входа, гарантированная надёжность кода, быстрая компиляция. Очевидно, что какой-то фактор помешал найти подходящую реализацию без GC, возможно, время.

Всё-таки threads. fibers — немного иное

Да, наверное спутал одно с другим. Стоило назвать Goroutines


Проверять, что действие не возвращает ошибку не такое плохое решение.

Есть более эргономичные способы по типу Either или Option. В бум развития JS очень в ходу был паттерн error first и функции возвращали кортеж (error, result), и то же делали if error != undefined всюду, нынче же практически не встретишь такого. Жить с этим можно, но код все равно усложняется.

Без них прям никак?

жить без них конечно можно, и заниматься копипаст программированием, но нельзя назвать это чем-то хорошим. Кроме того вещи типа вышеупомянутых Either и Option нельзя сделать без генериков насколько мне известно.


почему один из создателей ЯП Go, имевший ранее опыт в создании другого довольно успешного ЯП, пошёл на такой шаг?

Я прекрасно понимаю почему в прод добираются managed языки. Однако для большинста таких языков существует опция выключения гц или смены алгоритма управления, то бишь кастомные аллокаторы. Насколько мне известно сейчас есть люди которые пытаются решить эту проблему, чтобы иметь тонкий рантайм для wasm, например, но сталкиваются с ограничениями, наложенными автором на системные API. Естественно все это не first class проблемы, о чем я и написал выше в последнем предложении.

− все еще нельзя жить без gc. Хотя вроде wasm направление помаленьку продавливает этот вопрос

А можете дать какие-нибудь ссылки на информацию про реализацию Go без GC, а то ничего, кроме пары предложений а issue github и форумах, не удалось.

Сходу едва ли найду. Лучше забегите в телеграм чат по wasm, там народ внимательнее следит за таким. Велик шанс, что пока все парой issue и ограничивается, а сам wasm таргет у go пока только для галочки.

Подскажите в Go нет ни исключений ни "монады" Either (когда можно писать весь код, исходя из предположения, что у вас Left значение ("ok"), а Right-значение ("Error") обрабатывать отдельно там, где вы хотите?

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

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

>> А в чем беда? В том, что каждый вызов нужно явно обрабатывать?

Дело не в явной обработке (её где-то надо делать), а в том, что основная логика "разрывается" обработкой ошибок, как в условном C.

А разные техники (например монадическая с Maybe \ Either или Exceptions) - они позволяют писать основной код так, будто ошибок нет вообще, а потом все ошибки разобрать где-то в специально-отведённом месте.
Вот в том и дело - есть ли такие техники в Go.

ПС
Хотел приложить код на условных C и Haskell - но обнаружил, что не знаю как вставить на хабре код.
< code > < source > < src > - не работают и за 5 минут не нагуглилось ((

Как работают исключения, Maybe возврат ошибок в C мне известно. Под явной обработкой и имел ввиду обработку непосредственно за вызовом. Именно решением этой проблемы сейчас и заняты разработчики языка, так как хотелось бы поместить один общий handler в пределах функции, и там обрабатывать все ошибки, которые не нужно обрабатывать на месте, собственно, примерно то, о чем вы и пишите. Если найду ссылку на rfc - могу сбросить, с ходу не нашел, но там предлагается что-то вроде defer, только для ошибок, опять же, если все правильно помню.

P.S.: Если с телефона, то там внизу для нового абзаца есть tool buttons, а если с ПК, то /.

Дело не в явной обработке (её где-то надо делать), а в том, что основная логика "разрывается" обработкой ошибок, как в условном C.

Так в том и дело, что GO'шники считают обработку ошибок частью основной логики программы. Мне например текущая обработка ошибок в GO очень даже заходит.

Кстати писать системный код на GO поэтому тоже довольно удобно, C код с errno отлично мапится в GOшный.

ага еще впечатления голанг от уборщицы осталось только написать

В Go только строгая типизация.
В Go скорее полудинамическая типизация. В Go рантайм таскает для каждого значения его тип. Приведение типов используется везде, т.к. часто используется interface{}, что есть аналог any из Typescript. Динамическая диспатчизация (проверка на тип) используется во все ворота, как и в РНР, что есть отказ от строгой типизации.

С массивами вообще, мне кажется, в PHP все намного проще. В PHP, в отличие от Golang, массив может быть и собственно массивом, и списком, и хеш-таблицей, и словарем, и коллекцией, и очередью, и стеком. И это иногда очень удобно

Это удобно, но концептуально неверно. В языках где есть чёткое разделение между set, tuple, dict, list у разработчиков часто есть понимание о сложности выполнения тех или иных операций с каждым типом, что бывает полезно.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации