Go : философия ("фишки Go")

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 05 янв 2023, 09:49

Olej писал(а):
05 янв 2023, 09:45
Отображение (map) меняет порядок перечисления (выборки) не только от прогона к прогону программы, но даже при последовательных вызовах в одном фрагменте кода!
Может это связано? ... а). с версией компилятора, б). с видом компилятора (GoLang / GCC), в). с операционной системой (дистрибутивом)?

Код: Выделить всё

olej@R420:~/2023/Go/types$ which gccgo
/usr/bin/gccgo

Код: Выделить всё

olej@R420:~$ gccgo --version
gccgo (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Код: Выделить всё

olej@R420:~/2023/Go/types$ gccgo map3rand.go -o map3rand

Код: Выделить всё

olej@R420:~/2023/Go/types$ ./map3rand
1:10    2:11    3:53    4:23    5:543   6:513
1:10    2:11    3:53    4:23    5:543   6:513
3:53    4:23    5:543   6:513   1:10    2:11

Код: Выделить всё

olej@R420:~/2023/Go/types$ ./map3rand
6:513   1:10    2:11    3:53    4:23    5:543
4:23    5:543   6:513   1:10    2:11    3:53
6:513   1:10    2:11    3:53    4:23    5:543

Код: Выделить всё

olej@R420:~/2023/Go/types$ ./map3rand
1:10    2:11    3:53    4:23    5:543   6:513
6:513   1:10    2:11    3:53    4:23    5:543
2:11    3:53    4:23    5:543   6:513   1:10
Нет!
Это некий фундаментальный принцип ... "фишка", как сказано в заголовке темы.

Тогда вопрос: зачем? В чём грандиозность замысла?

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 05 янв 2023, 11:33

Olej писал(а):
05 янв 2023, 09:49
Это некий фундаментальный принцип ... "фишка", как сказано в заголовке темы.
Кстати, это, возможно, связано не столько с map, сколько с range ... - он вовсе не обязан перечислять элементы (и не только map) в том порядке в котором вы их туда помещаете ... или предполагаете что они так "расставлены":

Код: Выделить всё

olej@R420:~/2023/Go/types$ go fmt range.go 
range.go

Код: Выделить всё

package main

import "fmt"

func main() {
	numbers := [...]int{
		8:1, 7:2, 6:3, 5:4, 4:5, 3:6, 2:7, 1:8, 0:9,
	}
	fmt.Println(numbers) 
	for i := 0; i < 3; i++ {
		for number := range numbers {
			fmt.Printf("%1d\t", number)
		}
		println()
	}
}

Код: Выделить всё

olej@R420:~/2023/Go/types$ go run range.go 
[9 8 7 6 5 4 3 2 1]
0	1	2	3	4	5	6	7	8	
0	1	2	3	4	5	6	7	8	
0	1	2	3	4	5	6	7	8	
Вот так! rang перечисляет элементы массива в реверсном порядке...
Вложения
range.go
(247 байт) 17 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 05 янв 2023, 12:01

Olej писал(а):
05 янв 2023, 11:33
Вот так! rang перечисляет элементы массива в реверсном порядке...
Нет, это моя ошибка ... ступил - там цикл for ... range для одиночной переменной выбиравет именно индекс!
А значения выглядят вот так:

Код: Выделить всё

package main

import "fmt"

func main() {
	numbers := [...]int{
		8:1, 7:2, 6:3, 5:4, 4:5, 3:6, 2:7, 1:8, 0:9,
	}
	fmt.Println(numbers) 
	for i := 0; i < 3; i++ {
		for index, value := range numbers {
			fmt.Printf("%1d:%1d\t", index, value)
		}
		println()
	}
}

Код: Выделить всё

olej@R420:~/2023/Go/types$ go run range.go 
[9 8 7 6 5 4 3 2 1]
0:9	1:8	2:7	3:6	4:5	5:4	6:3	7:2	8:1	
0:9	1:8	2:7	3:6	4:5	5:4	6:3	7:2	8:1	
0:9	1:8	2:7	3:6	4:5	5:4	6:3	7:2	8:1	
Тогда остаётся вопрос: почему для map (карт, отображений, таблиц, хэшей ... как только их не обзывают) хаотически меняется порядок выборки?
Это какой-то побочный эффект, или какой-то глубокий умысел разработчиков?

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 05 янв 2023, 15:27

Olej писал(а):
05 янв 2023, 09:49
Тогда вопрос: зачем? В чём грандиозность замысла?
Объяснение всего этого механизма находим здесь - Hashmap по версии Golang вместе с реализацией на дженериках
12 декабря 2022 в 01:04
...
- Нельзя получить адрес элемента. Потому что при росте мапы оно переедет в другой бакет и адрес у него, соответственно, поменяется;
...
- Порядок итерации не сохраняется. При каждой новой итерации мапы последовательность возвращаемых элементов может отличаться. Под капотом каждый раз выбирается рандомный бакет, с которого начинается итерация. ...
- При каждом создании мапы генерируется seed для рандомизации хэш-функции. Это сделано для безопасности, так как зная хэш-функцию можно подобрать такие ключи, что все значения попадут в один бакет и мы получим линейную скорость поиска;
...

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 06 янв 2023, 12:01

Olej писал(а):
05 янв 2023, 09:49
Тогда вопрос: зачем? В чём грандиозность замысла?
Ух ты :!: :lol:
Оказывается про "стабильность порядка итераций" обсуждениями Интернет полнится уже не первый год!
Why are iterations over maps random? (перевод, подстрочный, мой - самые пикантные места :lol: ):
При переборе карты с циклом диапазона порядок итераций не указывается и не гарантируется, что он будет одинаковым от одной итерации к другой. Начиная с версии Go 1.0, среда выполнения использует рандомизированный порядок итерации карт. Программисты начали полагаться на стабильный порядок итераций ранних версий Go, который варьировался в зависимости от реализации, что приводило к ошибкам переносимости. Если вам требуется стабильный порядок итераций, вы должны поддерживать отдельную структуру данных, определяющую этот порядок.
Спецификация старого языка не определяла порядок итераций для карт, и на практике он различался для разных аппаратных платформ. Это привело к тому, что тесты, повторяющиеся по картам, стали хрупкими и непереносимыми, с неприятным свойством, заключающимся в том, что тест всегда может пройти на одной машине, но сломаться на другой.

В Go 1 порядок, в котором посещаются элементы при переборе карты с использованием оператора for range, определяется как непредсказуемый, даже если один и тот же цикл выполняется несколько раз с одной и той же картой. Код не должен предполагать, что элементы посещаются в каком-то определенном порядке.

Это изменение означает, что код, который зависит от порядка итерации, скорее всего, сломается раньше и будет исправлен задолго до того, как станет проблемой. Что не менее важно, это позволяет реализации карты обеспечить лучшую балансировку карты, даже когда программы используют циклы диапазона для выбора элемента на карте.
Из документации (go1 (released 2012-03-28)):
Обновление: это одно из изменений, в котором инструменты не могут помочь. Большая часть существующего кода останется неизменной, но некоторые программы могут сломаться или работать некорректно; мы рекомендуем вручную проверять все операторы диапазона по картам, чтобы убедиться, что они не зависят от порядка итерации. В стандартном репозитории таких примеров было несколько; они были исправлены. Обратите внимание, что уже неправильно было зависеть от порядка итераций, который не был указан. Это изменение кодифицирует непредсказуемость.


Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 21 фев 2024, 14:34

Разработка через тестирование — это одна из современных, высоко продуктивных, методик создания программных проектов. Идея состоит в том, что юнит-тесты готовятся сразу при начале проектирования модуля, а затем контроль ведётся непрерывно всё время разработки.
GoLang предоставляет всё необходимое для быстрого создания тестов параллельно с созданием целевых пакетов - пакет тестинг (https://pkg.go.dev/testing).
Для тестирования функций в файле xxx.go тестовый файл будет иметь вид xxx_test.go, и они описывают один пакет...
Сделали пакет:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree$ cat degree.go 
package degr

import "math"

func Sin(dg float64) float64 {
	return math.Sin(dg / 180 * math.Pi)
}
Для него делаем, в отдельном файле, тестирующую часть:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree$ cat degree_test.go 
package degr

import "testing"
import "math"

func TestSin(t *testing.T) {
    got, want := Sin(30.0), 0.5
    if math.Abs(got - want) > 1E-7 {
        t.Errorf("Sin(30) = %f, должно быть %f", got, 0.5)
    }
}
Специально :!: : имя пакета не совпадает с именами файлов - одно от другого не зависит.
Но при таком запуске (в современных версиях GoLang):

Код: Выделить всё

$ go test
go: go.mod file not found in current directory or any parent directory; see 'go help modules'
Это значит, что работает только с модулями, и нужно создать манифест модуля (описание):

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree$ go mod init degree
go: creating new go.mod: module degree
go: to add module requirements and sums:
    go mod tidy
Тоже, имя модуля может быть произвольное (или из соображений размещения), но не привязано ни к имени пакета, ни к имени файла - 3 разных сущности :!:
После чего:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree$ go test
PASS
ok      degree  0.003s
Вот: тестируемая функция выполняется как ожидалось, всё ОК.
Вложения
degree.tgz
(443 байт) 3 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 21 фев 2024, 14:39

Olej писал(а):
21 фев 2024, 14:34
Вот: тестируемая функция выполняется как ожидалось, всё ОК.
А вот как это выглядит когда пакет не соответстввует тесту... Меняю слегка (специально допускаю ошибку):

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree.err$ cat degree_test.go 
package degr

import "testing"
import _ "math"

func TestSin(t *testing.T) {
    got, want := Sin(30.0), 0.5
    if got != want {
        t.Errorf("Sin(30) = %f, должно быть %f", got, 0.5)
    }
}
И выполняется это так:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree.err$ go test
--- FAIL: TestSin (0.00s)
    degree_test.go:9: Sin(30) = 0.500000, должно быть 0.500000
FAIL
exit status 1
FAIL    degree  0.004s
Вот так выглядит вид не срабатывающего теста :!:
Правда ... тут неправильно работает не тестируемая функция, а тест: напоминание того, что нельзя никогда сравнивать вещественные числа в машинном представлении :oops: Вещественные числа — это приближённые представления значений в машинных форматах.
Вложения
degree.err.tgz
(431 байт) 3 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go : философия ("фишки Go")

Непрочитанное сообщение Olej » 21 фев 2024, 14:43

Olej писал(а):
21 фев 2024, 14:34
Разработка через тестирование
Вот с такой "лёгкостью необыкновенной" делается тестирование любого пакета Go кода :!:
Тесты запускаются в разном формате ... - это как показано раньше:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree$ go test
PASS
ok      degree  0.004s
Или то же с явным указанием что это в текущем каталоге:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.3/examples.work/tests/degree$ go test .
ok      degree  0.003s
И так же в любом другом каталоге...


Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 9 гостей