Часть первая. Основы.
Hello World
Go
package main
import "fmt"
func main() {
fmt.Println("Hello, ??")
}
D
module main;
import std.stdio;
void main()
{
// stdout.writeln( "Hello, ??" );
writeln( "Hello, ??" );
}
Разница не значительная, разве что в D пространство имён можно не указывать, если нет конфликта имён импортированных из разных модулей. Также стоит обратить внимание на обязательные точки с запятыми в конце строк — в D они, к сожалению, обязательны.
Packages
Go
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
D
module main;
import std.stdio;
import std.random;
void main()
{
writeln( "My favorite number is ", uniform( 0 , 10 ) );
}
Тут тоже всё одинаково, разве что в Go при импорте указывается путь к модулю, а в D используется имя модуля, задаваемое директивой "module", или автоматически выводимое из пути к файлу, если эта директива не указана.
Imports
В Go рекомендуется группировать импорты в одну директиву.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.", math.Sqrt(7))
}
В D тоже так можно, но особенности синтаксиса не располагают к этому:
module main;
import
std.stdio,
std.math;
void main()
{
writefln( "Now you have %f problems.", 7f.sqrt );
}
Кроме того, в D импорты можно указывать в любом блоке, а не только в начале файла:
module main;
void main()
{
import std.stdio;
{
import std.math;
writefln( "Now you have %f problems.", 7f.sqrt );
}
writefln( "Now you have %f problems.", 7f.sqrt ); // Error: no property 'sqrt' for type 'float'
}
Exported names
В Go модуль экспортирует лишь то, что начинается с большой буквы:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.pi) // cannot refer to unexported name math.pi
}
В D же экспортируется лишь то, что объявлено в public секции модуля (которая по умолчанию), либо помечено модификатором доступа public:
module math;
import std.math;
auto PI = std.math.PI;
private:
public auto pip = std.math.PI;
auto pi = std.math.PI;
module main;
import std.stdio;
import math;
void main()
{
writeln( PI );
writeln( pi ); // Error: module main variable math.pi is private
writeln( pip );
}
Подробнее о модульной системе D.
Functions
Go
package main
import "fmt"
// func add(x int, y int) int {
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
D
module main;
import std.stdio;
int add( int x , int y )
{
return x + y;
}
void main()
{
// writeln( add( 42 , 13 ) );
writeln( 42.add( 13 ) );
}
В Go тип обычно следует в конце, а в D — более традиционно — в начале. Кроме того, любую функцию в D можно вызвать как метод, что позволяет элегантно расширять сторонние типы. Go же позволяет не повторять одинаковые типы идущих друг за другом параметров. Тут же стоит упомянуть отсутствующее в Go обобщённое программирование, позволяющее реализовать функцию сразу для любых подходящих типов:
module main;
import std.stdio;
auto add( X , Y )( X x , Y y ) {
return x + y; // Error: incompatible types for ((x) + (y)): 'int' and 'string'
}
void main()
{
// writeln( 42.add!( int , float )( 13.3 ) );
writeln( 42.add( 13.3 ) ); // 55.3
writeln( 42.add( "WTF?" ) ); // Error: template instance main.add!(int, string) error instantiating
}
В D для любой функции можно указать в дополнительных круглых скобках параметры времени компиляции, куда можно либо явно передать типы, либо они могут быть выведены автоматически компилятором из типов аргументов.
Multiple results
Go
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
В D нет возможности возвратить из функции несколько отдельных значений, но можно вернуть кортеж:
module main;
import std.stdio;
import std.typecons;
auto swap( Item )( Item[2] arg... )
{
return tuple( arg[1] , arg[0] );
}
void main()
{
auto res = swap( "hello" , "world" );
writeln( res[0] , res[1] ); // worldhello
}
А при необходимости можно и распаковать возвращаемый кортеж в уже существующие переменные:
module main;
import std.stdio;
import std.meta;
import std.typecons;
auto swap( Item )( Item[2] arg... )
{
return tuple( arg[1] , arg[0] );
}
void main()
{
string a , b;
AliasSeq!( a , b ) = swap( "hello" , "world" );
writeln( a , b ); // worldhello
}
Named return values
Go
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
Достаточно сомнительный синтаксический сахар. D ничего такого не поддерживает, так что возвращаем либо структуру, либо опять же кортеж, но с именованными элементами:
module main;
import std.stdio;
import std.typecons;
auto split( int sum )
{
auto x = sum * 4 / 9;
auto y = sum - x;
return tuple!( "x" , "y" )( x , y );
}
void main()
{
// auto res = split( 17 ); writeln( res.x , res.y );
// writeln( split( 17 )[] );
writeln( 17.split[] ); // 710
}
Оператор [] возвращает так называемый "срез", то есть массив элементов.
Подробнее о кортежах в D.
Variables
Go
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
D
module main;
import std.stdio;
// bool c , python , java;
bool c;
bool python;
bool java;
void main()
{
int i;
writeln( i , c , python , java ); // 0falsefalsefalse
}
В целом, объявления переменных очень похожи, разве что синтаксис Go несколько более многословен.
Short variable declarations
Go
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
D
module main;
import std.stdio;
void main()
{
int i = 1 , j = 2;
auto k = 3;
auto c = true , python = false , java = "no!";
writeln( i , j , k , c , python , java ); // 123truefalseno!
}
Оба языка умеют выводить тип переменной из инициализирующего выражения. Однако подход Go с разделением объявления переменных на список имён и список значений довольно не нагляден и провоцирует ошибки.
Basic types
Таблица соответствия типов:
Go D
---------------------
void
bool bool
string string
int int
byte byte
int8 byte
int16 short
int32 int
int64 long
uint unint
uint8 ubyte
uint16 ushort
uint32 uint
uint64 ulong
uintptr size_t
ptrdiff_t
float32 float
float64 double
real
ifloat
idouble
ireal
complex64 cfloat
complex128 cdouble
creal
char
wchar
rune dchar
Существенное различие в том, что размер int и uint в Go зависит от платформы, а в D — не зависит. Также D контролирует, чтобы мнимые числа не перепутались с реальными. Кроме того, D позволяет работать с вещественными числами большего размера (80 бит), а с символами — меньшего (8 и 16 бит). Подробнее о типах в D.
Go
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
const f = "%T(%v)\n"
fmt.Printf(f, ToBe, ToBe)
fmt.Printf(f, MaxInt, MaxInt)
fmt.Printf(f, z, z)
}
D
module main;
import std.stdio;
import std.math;
bool ToBe = false;
ulong MaxInt = ulong.max;
cdouble z = sqrt( -5 + 12i );
void main()
{
enum f = "%s(%s)";
writefln( f , typeid( ToBe ) , ToBe ); // bool(false)
writefln ( f , typeid( MaxInt ) , MaxInt ); // ulong(18446744073709551615)
writefln( f , typeid( z ) , z ); // cdouble(2+3i)
}
В D у каждого типа есть свойства, позволяющие получить основные связанные с типом константы. Стоит обратить внимание, что в D константы времени компиляции создаются через ключевое слово "enum" — их значение инлайнится в место их использования. А вот ключевое слово "const" имеет несколько иное значение — это модификатор доступа, запрещающий нам изменять значение переменной (но в другом месте программы у нас может быть доступ на редактирование).
Zero values
Go
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s) // 0 0 false ""
}
D
module main;
import std.stdio;
void main()
{
writefln( "%s %s %s \"%s\"" , int.init , double.init , bool.init , string.init ); // 0 nan false ""
}
В D у каждого типа есть специальное поле "init", хранящее значение по умолчанию для этого типа.
Type conversions
Go требует ручного перевода значения из одного типа в другой:
package main
import (
"fmt"
"math"
)
func main() {
var x int = 3
var y uint = 4
var f float64 = math.Sqrt(float64(uint(x*x) + y*y))
var z uint = uint(f)
fmt.Println(x, y, z) // 345
}
D достаточно умён, чтобы требовать ручного перевода типов лишь когда это может привести к потере данных:
module main;
import std.stdio;
import std.conv;
void main()
{
int x = 3;
uint y = 4;
double f = ( x^^2 + y^^2 )^^0.5;
uint z = f.to!uint;
writeln( x , y , z ); // 345
}
Numeric Constants
Численные константы в Go позволяют задавать числа, которые невозможно использовать в рантайме без потерь:
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small)) // 21
fmt.Println(needInt(Big)) // constant 1267650600228229401496703205376 overflows int
fmt.Println(needFloat(Small)) // 0.2
fmt.Println(needFloat(Big)) // 1.2676506002282295e+29
}
В D при компиляции используются те же типы, что и при выполнении, так что и значения констант имеют те же ограничения:
module main;
import std.stdio;
enum Big = 1L << 100; // Error: shift by 100 is outside the range 0..63
enum Small = Big >> 99;
56% (68) |
Управление потоком исполнения |
31% (38) |
Составные типы |
25% (31) |
Методы |
64% (78) |
Сопрограммы |
Проголосовало 122 человека. Воздержалось 100 человек.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (107)
beduin01
19.03.2016 23:41К сожалению на HelloWorld примерах преимущества D над Go раскрыть не получится ну никак. Вот когда код начинает расти и появляется потребность гибко выражать свои мысли, то тут D равных нет. Как я уже много раз писал Go не простой, он примитивный. Вот Python простой и из-за этого с ним иногда бывает очень удобно.
А вот Go… больше всего тянет на хипстерское поделие с непонятными перспективами. В начале куча народу на него бросилось переходить, а потом оказалось, что что-то сложнее микросервисов на нем писать не удобно. Вон тот же DropBox с Go уже на Rust переписывать бросились. Тоже конечно сомнительный в плане продуктивности язык, но судя по тому как на него Сишники переходят свои задач решать позволяет.
creker
20.03.2016 00:07Прочитайте лучше, почему они переписывать решили http://www.wired.com/2016/03/epic-story-dropboxs-exodus-amazon-cloud-empire/ Все дело в потреблении памяти, что для модели памяти Go объяснимо.
Как я уже много раз писал Go не простой, он примитивный
больше всего тянет на хипстерское поделие с непонятными перспективами
Я теперь начинаю понимать divan0 и его реакцию на комментарии от людей, которые явно не понимают то, о чем пишут. Перспективы у него более чем радужные. Он все таки же прекрасен для backend кода, где нужна конкурентность и простота работы с сетью. К сожалению, судя по возможностям D, я, так же как и C++, буду всеми способами обходить его стороной в этих задачах. Он не дает никаких преимуществ и возвращает нас в прошлое к колбекам для хоть какой-то асинхронности.bfDeveloper
20.03.2016 00:17+3Про асинхронность не правда. Корутины есть (называются Fiber), кроме того есть целый фреймвёрк для асинхронного io: vibe.d
creker
20.03.2016 00:28+2Я не говорил, что там ничего нет. Из того, что я видел, код получается такой же нечитабельный, как и раньше. Fibers, как бы, и раньше были в других языках. Проблема с ними, что пользоваться ими все так же неудобно, как и обычными потоками, плюс у них нет нормального планировщика и D тут не исключение. А для сокетов предлагают всю туже событийную модель и колбеки — нет уж спасибо. Мне этого ужаса уже хватало в прошлом. Спасибо, что пришли Go и C# с асинками, которые дают простой линейный сетевой код, который прекрасно использует конкурентность.
bfDeveloper
20.03.2016 00:43Не совсем понял, что вам не понравилось в файберах. Асинхронный код превосходно пишется линейно:
while(true) { c.write(buf.data); c.write(['\0']); buf.clear(); c.readUntil(buf, ['\0'], SIZE); } }
Это кусок одного очень простого теста, слегка усложнённый вариант эхо сервера. Пишет в сокет то, что получил. Что c.write, что c.readUntil — асинхронные операции, в которых произойдёт переключение волокон исполнения. С моей точки зрения Fibers — абсолютный эквивалент goroutines и, что уже субъективно, гораздо удобнее async из C#.cy-ernado
20.03.2016 08:51То есть придется для асинхронной работы искать библиотеки, которые её поддерживают? (как в питоне, там еще с вариациями под разные эвентлупы)
bfDeveloper
20.03.2016 15:06+1Fiber в стандартной библиотеке. Vibe.d поддерживает множество ивентлупов (libevent, libev, win32, собственная библиотека libasync). Большинство библиотек для асинхронных операций основываются на vibe.d, он стал почти стандартом, поэтому проблемы совместимости нет. Кроме того модель сопрограм такова, что если функция не выполняет асинхронных действий сама, то ей не требуется какая-то особая поддержка асинхронности. То есть любые синхронные библиотеки отлично работают в асинхронном коде.
vintage
20.03.2016 17:47Только могут заблокировать поток своими синхронными вызовами. Go же изначально затачивался на такую архитектуру, поэтому в нём все синхронные вызовы приводят к предварительмому перекидыванию ожидающих горутин на другие воркеры.
creker
19.03.2016 23:51+7Странно. Оригинал пропал с сайта языка. Остались только "C to D" и "C++ to D".
А так, отсутствие нескольких возвращаемых значений печалит. Ремарки про "ручного жонглирования типами" вообще не понятны, а судя по исходникам рантайма, ничего дельного в плане поддержки конкурентности в рантайме не видно, что еще более печально. Судя по всему, для написания серверного кода Go так и останется более предпочтительным. В общем, все более укрепляется мнение, что D это C++ со словом import. Такой же сложный, напичканный всем чем можно язык, который не имеет четкого назначения. А как мне кажется, без чего-то эдакого он никуда не пробьется. У конкурентов в лице Go и Rust оно есть в полной мере.vintage
20.03.2016 00:20-7А зачем вам несколько возвращаемых значений?
Ручное жонглирование — это про все эти ручные преобразования типов и копипаста одного и того же для разных типов.
Что именно вам не хватает для конкурентности?
Для разработки сервера есть VibeD.creker
20.03.2016 00:37+5Затем, чтобы возвращать несколько значений, не используя костыли в виде массивов и указателей/ссылок. Это удобно.
Ручное жонглирование. Мне тоже так казалось раньше. Реальный код что-то у меня вообще нигде не требует этих преобразований повсюду и копипасты. Я не библиотеки пишу на все случаи жизни, а реальный код. Дженерики мне пока ниразу не понадобились. Вот вообще.
Конкурентности. D возвращает нас в прошлое, где конкурентность реализована в какой-то убогой библиотеке и вообще всем на нее пофиг, сам как-нить разбирайся. А хочу то, что смогли Go, C# и иже с ними — предельная простота конкуретного кода. Предельная простота работы с сетью. Ты просто пишешь так, как требует того логика. Пришел запрос — сразу его отправил, сразу получил статус отправки, сразу обработал ошибки. Мне не хочется снова пробираться сквозь колбеки и обработчики входящих событий. Пока что я не видел на D простых примеров. Все даже самые примитивные примеры это портянки на весь экран.
Поэтому я выбрал Go для последних backend проектов и ниразу не пожалел. Получилось настолько просто, понятно, а главное, все это заработало буквально сразу. Вот честно, не нарадуюсь. Поэтому я совсем не удивлен, почему этот язык так полюбился в этой области.bfDeveloper
20.03.2016 00:48+1"Сложный" пример предлагал чуть выше, а за совсем простыми можете обратиться сюда. Пример оттуда:
auto conn = connectTCP("time-c.nist.gov", 13); logInfo("The time is: %s", conn.readAllUTF8());
В одной строке подключились, в другой уже читаем. Не знаю, как можно это сделать ещё проще.creker
20.03.2016 17:55+1Это не пример, а просто порнография какая-то, я видел его и не посчитал уместным даже считать примером. Другие примеры были на HTTP, что тем более примитив. Простой пример — это написание маломальски простого протокола обмена между сервером и клиентом на TCP. И мне нужно, чтобы операции с сетью не блокировали все приложение, чтобы код оставался линейным, чтобы были простые механизмы прерывания блокирующих операций по запросу или таймеру.
Вот к примеру, на C# отправка email сообщения полностью асинхронно на голом TCP без блокировок с таймаутами и отменой влезает в одну небольшую функцию с полностью процедурным кодом. А до этого было уродство с тучей колбеков и необходимостью локов для защиты данных.
Go — будет еще проще и яснее, потому что весь сетевой стек построен вокруг зеленых потоков. В рантайме выделены отдельные потоки (network poller), которые сидят на select функции и управляют всеми read/write операциями в приложении. Закрой в любом потоке сокет и все заблокированные потоки разблокируются и вернут ошибку. Асинхронность ведь придумана от того, что блокирующие операции требуют использовать слишком много ОС потоков, что делать нельзя. В Go горутины практически бесплатны, а значит и все костыли событийной модели и колбеков не нужны. Можно просто писать код, наконец-то. Наконец-то можно создавать поток на каждое подключение и на каждую фоновую операцию и не бояться, что это сожрет всю память.
И вот из того, что я видел, в D предлагают опять эти уродливые события и колбеки, которые просто невозможно читать. Встречал даже попытки обернуть во что-то похоже на Go, но выглядит это все так же нечитабельно. И мне опять вспоминается C++ с вечными попытками сделать так же красиво, как могут другие, ведь у нас же такой мощный язык. Но получается нечитабельная хрень, которую потом задолбаешься поддерживать в рабочем состоянии.bfDeveloper
20.03.2016 18:20И вот из того, что я видел
Мне кажется проблема в том, что асинхронность в D вы не видели.
Наконец-то можно создавать поток на каждое подключение и на каждую фоновую операцию и не бояться, что это сожрет всю память.
Это точно так же можно делать в D. Fiber по легковесности эта та же самая горутина, можете создавать десятками и сотнями тысяч.
в D предлагают опять эти уродливые события и колбеки
Я вас не понимаю, честно. Найдите в vibe.d хоть один колбек. Кроме разве что onConnection и onTimer. Но они и инициируются не кодом, а некой третьей стороной, для них нет линейного кода. Так проиходит и в Go и в C#.
А для чтения, записи, подключения к кому-то нет никаких колбеков или событий. События есть для общения между сопрограммами, но это тот же самый select в Go, только гораздо универсальнее.
Бывают ситуации, где предлагается альтернатива: колбек или возвращаемое значение. Просто так получилось, что чистые колбеки работают чуть-чуть быстрее и это API оставлено. Но код всё равно можно писать синхронно.
Всё сделано так чтобы было удобно писать именно линейный код: предоставляются асинхронные линивые диапазоны для чтения/записи, автоматически закрываются соединения при выходе из скоупа и тд, и тп. Чего-чего, а callback hell, это точно не про vibe.d
vintage
20.03.2016 18:35+1Вы какие-то глупости говорите. VibeD, Go, MeteorJS и тп — одного поля ягоды, в том плане, что везде есть "зелёные потоки", они же "волокна", они же "сопрограммы", они же "файберы", они же "корутины", везде на каждый запрос создаётся отдельная задача, везде есть пул воркеров, которые эти задачи выполняют, везде задачи могут блокироваться в ожидании событий, позволяя воркеру заняться тем временем другими задачами, везде код этой задачи является синхронным, без каких-либо колбэков. Откуда вы взяли уродливые колбэки в D?
Держите простой пример в стиле го:
auto go( alias task , Arg )( Arg arg... ) { return runTask({ task( arg ); }); } void say( string s ) { for( int i = 0 ; i < 5 ; ++i ) { sleep( 100.dur!"msecs" ); writeln( Thread.getThis().id , " " , s ); } } shared static this() { go!say( "world" ); say( "hello" ); setIdleHandler({ exitEventLoop; }); }
25764 hello 25764 world 25764 hello 25764 world 25764 hello 25764 world 25764 hello 25764 world 25764 hello
В данном примере я намеренно запускаю задачи на одном и том же воркере, чтобы показать их кооперативность.
vintage
20.03.2016 01:03Всё же не очень понятно чем не угодили массивы и структуры — специально предназначенные для группировки сущности. Можно хотя бы один наглядный пример? Кроме возврата ошибок — это тема отдельного холивара.
Не покажете реальный код? А то у меня без дженериков не получается ничего.arvitaly
20.03.2016 09:36-7ok, countBytes, err := writeTo(...);
А дженерики — это в любом случае, замедление в real-time, ну или отказ от типобезопасности. Я лично ставлю на кодогенерацию, как, с одной стороны, возможность обобщений, а с другой высокая производительность.vintage
20.03.2016 11:45Не очень понял, что означает ok, но err в D принято бросать исключением, так что код получится такой, если использовать столь же низкоуровневые средства:
auto countBytes = core.sys.posix.unistd.write(fd, buffer.ptr, size)
Или даже такой, при высокоуровневых:
"output.text".write( "hello!" )
Есть два варианта реализации дженериков:
- Как в C# или Java, где для любых типов реализауется один машинный код. Это даёт малый размер бинарника, но не даёт его толком оптимизировать.
- Как в D и C++, где для каждой комбинации типов генерируется свой машинный код. Это даёт максимальную производительность ценой увеличения бинарника.
Кодогенерация в Go — это фактически менее эффективная реализация второй стратегии.arvitaly
20.03.2016 13:21Я так понял, речь шла о примере с множественными результатами функции. ok — это признак успешной записи, count — количество записанных байт, err — ошибка. Суть в том, что возможность вернуть множество результатов позволяет совершенно по другому композировать функции (а конкретно не плодить их), но не является священной пулей. И многие оценили по достоинству такую возможность.
«Кодогенерация в Go — это фактически менее эффективная реализация второй стратегии. „
Основной принцип go: “явное лучше неявного», здесь все то же самое, вместо кучи сложных настроек компилятора (по сути изучение еще одного языка) — примитивный подход, и это оправдывает себя.vintage
20.03.2016 17:22+2Разве отсутствие ошибок не является признаком успешности записи?
Функции со множеством возвращаемых значений как раз сложнее композировать. Например, с кортежем я могу написать так:
writeln( getStat().total )
Вместо такого:
_ , _ , total := getStat() fmt.Println(total)
Более того, я могу вернуть структуру с ленивыми полями, так что не нужные мне данные даже не будут вычислены.
Явное лучше неявного — это основный принцип ассемблера. Все остальные языки вводят высокоуровневые абстракции, которые инкапсулируют в себе некоторые паттерны, и неявно для прикладного программиста разворачиваются компилятором/рантаймом в машинный код.
Возьмём, например, кодогенератор stringer. Это более 600 замысловатых строчек, которые по такому определению:
type Pill int const ( Placebo Pill = iota Aspirin Ibuprofen Paracetamol Acetaminophen = Paracetamol )
Генерируют следующую реализацию интерфейса Stringer:
const _Pill_name = "PlaceboAspirinIbuprofenParacetamol" var _Pill_index = [...]uint8{0, 7, 14, 23, 34} func (i Pill) String() string { if i < 0 || i+1 >= Pill(len(_Pill_index)) { return fmt.Sprintf("Pill(%d)", i) } return _Pill_name[_Pill_index[i]:_Pill_index[i+1]] }
В D мало того, что все энумы и так умеют выдавать своё текстовое представление, так и добавить чего-то своего во все энумы не составляет труда:
string toPrettyString( Value )( Value value ) if( is( Value == enum ) ) { return Value.stringof ~ ":" ~ value.to!string; }
Тут мы в трёх строчках добавили всем значениям всех энумов метод toPrettyString, который возвращает строку вида "Pill:Paracetamol".arvitaly
20.03.2016 18:13-1«Разве отсутствие ошибок не является признаком успешности записи?»
Ну вот, а кто-то не хотел тему ошибок поднимать) Другой пример: x, err:= getA() и возвращаемое значение, в x — пустая структура, а в err — ошибки. Кому нужно — игнорирует ошибки, а кого-то не устроит значение по умолчанию.
«Функции со множеством возвращаемых значений как раз сложнее композировать. Например, с кортежем я могу написать так:»
Не увидел сложности композиции, увидел разное количество кода, причем в примере с go у вас явно игнорируются ошибки (_), т.е. код не равнозначный. И никто не мешает в go точно так же вернуть структуру с полем Total.
«Все остальные языки вводят высокоуровневые абстракции, которые инкапсулируют в себе некоторые паттерны, и неявно для прикладного программиста разворачиваются компилятором/рантаймом в машинный код.»
Все верно, и чем больше таких абстракций, тем сложнее масштабировать код — выше порог входа, меньше программистов, больше число сочетаний возможных решений. Это хорошо для небольших команд, где можно собраться в одной комнате и объяснить друг другу сложнейшие архитектуры, но плохо для больших. Поэтому, никто и не призывает использовать go всегда и везде, зато, в определенный момент времени, он становится очень удобен.
К чему был пример не понял.vintage
20.03.2016 18:46+1"в примере с go у вас явно игнорируются ошибки (_)"
А с чего вы взяли, что там ошибки? :-)
"Другой пример: x, err:= getA() и возвращаемое значение, в x — пустая структура, а в err — ошибки. Кому нужно — игнорирует ошибки, а кого-то не устроит значение по умолчанию."
Как я уже сказал, в D используются исключения, так что этот пример показывает лишь полезность возврата множественных значений в Go, но не в других языках. И то, что вы приняли игнорируемое значение за объект ошибки, говорит о том, что других полезный применений множественным возвращаемым значениям нет.
" чем больше таких абстракций, тем сложнее масштабировать код — выше порог входа, меньше программистов, больше число сочетаний возможных решений."
Как раз таки наоборот. JS — весьма абстрактный относительно железа язык, но какой бешенной популярностью он пользуется. А Python, Ruby, PHP в конце концов.
Пример был к ущербрости кодогенерации перед лицом метапрограммирования.arvitaly
20.03.2016 19:04-1«А с чего вы взяли, что там ошибки? :-)»
Я все еще думал, мы рассматриваем мой пример. Да и суть не изменилось, в примере с go явно игнорируются два результата функции, т.е. примеры не эквивалентны.
«И то, что вы приняли игнорируемое значение за объект ошибки, говорит о том, что других полезный применений множественным возвращаемым значениям нет.»
С учетом того, что вы проигнорировали мой пример со множественными значениями, а взяли другой, показывающий возможность возврата одновременно и ошибки и значения — то да. Суть в том, что результатов не обязательно должно быть два и не обязательно одним из результатов является ошибка. В целом, в go точно так же, как и в сильно-функциональных языках удобно работать с I/O на границах, а внутри оперировать чистой моделью без error.
Исключения в go тоже есть, там где и должны быть, не для описания логики, а для критических (в пределах данного пакета) ситуаций, когда пакет просто не знает, что с этим делать.
«Пример был к ущербрости кодогенерации перед лицом метапрограммирования.»
Я увидел пример, но не увидел аргументов. Я уже написал, что чем больше можно «добавить» такого, что потом придется гадать всем миром, тем хуже. И да, документация — это тяжелый труд, который тоже нужно автоматизировать.
igrishaev
21.03.2016 09:24-1ok
означает признак успеха операции,true/false
. Вторая переменная — результат или ошибка. В Гоу любая IO-операция возвращает паруok, result_or_error
.
pav5000
20.03.2016 13:37+1Кодогенерация, кстати, в D простая и изящная. И с проверкой при компиляции.
Source
21.03.2016 11:42Самая изящная кодогенерация, на мой вгляд, в Elixir: http://slides.com/chrismccord/elixir-macros
nwalker
21.03.2016 19:59Макросы, скажем, clojure еще круче. Макросы Elixir весьма неплохи, но это все же недо-лисп.
mirrr
20.03.2016 13:33+1Всё же не очень понятно чем не угодили массивы и структуры — специально предназначенные для группировки сущности.
Вот именно, что для группировки. Кто сказал, что возвращаемые данные необходимо группировать? Сначала группируем, потом разбираем, лишние телодвижения, которые к тому же ухудшают читаемость и докумментируемость кода.
Если провести аналогию, то зачем нам множество входящих параметров в функцию, ведь есть массивы и структуры?vintage
20.03.2016 17:36Проиллюстрирую кодом на JS:
function makeElement({ tagName , textContent }) { var el = document.createElement( tagName ) el.textContent = textContent return el } function log( v ) { console.log( v ) return v } var { html : outerHTML , ns : nameSpace } = log( makeElement({ tagName : 'div' , textContent : date }) ) document.body.innerHTML = ns + '<br/>' + html
Тут принимается и возвращается некоторая структура, но синтаксический сахар позволяет довольно удобно с этим работать.mirrr
20.03.2016 18:20Без типов все выглядит красиво) Но даже в таком случае, это читаемее:
html, ns := log(makeElement("div", date))
А как это выглядело бы в D?vintage
20.03.2016 18:52+1А вот так уже не читаемее:
html, ns, attrs, childNodes, id, offset, size, scrollPos := log(makeElement("div",date,"datepicker",null,null,true,0,document.body))
mirrr
20.03.2016 21:01Ну и что мешает в таком случае вернуть массив или структуру? Каждый инструмент для своего случая. Не обязательно во всем выходить на крайности.
vintage
20.03.2016 21:12В том-то и дело, что обычно хватает 2 параметров и 2 возвращаемых значений, только в разных местах разных параметров и разных возвращаемых значений.
html, ns := log(makeElement("div", date))
parentNode, previousSibling := log(makeElement("lalala", "div", "datepicker"))
timfactory
20.03.2016 11:33+1Для возврата несколько значений Александреску советует использовать Tuple из std.typecons.
cy-ernado
20.03.2016 14:44А как у VideD с производительностью?
bfDeveloper
20.03.2016 14:57+1Хорошо у него с производительностью. Сравнивал простые HTTP сервер и клиент с Go, получилось примерно одинакого. Причём масштабируются и Go и D одинакого хорошо. С учётом обработки и всяких парсингов JSON D оказывается быстрее. Есть мнение, что самый быстрый JSON как раз написан на D. Бенчмарк конечно не совсем честный, но точно претендент на лидерство.
В бенчмарке по вашей ссылке vibe.d есть, правда работал в один поток (была взята старая версия фреймвёрка с досадным багом). PR с нормальной многопоточной версией был отправлен вовремя, но почему-то его не приняли. Ждём следующего запуска, чтобы увидеть правильные результаты.cy-ernado
20.03.2016 17:34+1Теперь понятно, почему такая производительность. Я уже начал думать, что у D совсем все плохо.
Было бы интересно увидеть JSON в этом же бенчмарке, тогда D должен выбиться в лидеры в "JSON serialization".
Ждем 13 раунда, там как раз fasthttp с prefork будет.
Спасибо за ответ
vintage
20.03.2016 17:41Емнип, там ещё и сборка была дебажная, а не релизная, ибо в релизе почему-то не собиралось.Может уже и пофиксили, конечно.
degs
20.03.2016 09:19Забавно, никогда не подумал бы что Go и D похожи.
D меня сразу зацепил с первого взгляда, а Go смотрел пару лет назад и совершенно он у меня не пошел. А здесь смотришь — код один в один.vintage
20.03.2016 11:49Это свойство всех C-like языков — базовый синтаксис у всех примерно одинаков. :-)
timfactory
20.03.2016 11:57Разве что только препроцессора не хватает. Магию #ifdef'ов заменили на магию static if, статические конструкторы/деструкторы и т.п.
vintage
20.03.2016 12:25Магия #ifdef — причина очень медленной сборки C++ проектов и трудноуловимых багов, так что молодцы, что заменили. Какой именно функциональности препроцессора вам не хватает?
timfactory
20.03.2016 14:38Нет, тут, скорее, разрыв шаблона «язык для разработки + язык для языка». Поначалу немного непривычно.
dbelka
20.03.2016 10:28+4Немного имхо по сравнению языков:
Указание названия функции вместе с названием пакета гораздо более читабельнее, сразу понятно откуда функция. Меня бесит ситуация, когда вверху куча импортов, а внизу какой-то набор функций, и что к чему не понятно. Я думаю это наиболее актуально для изучаемых языков. Так что тут я на стороне Go.
Определение функции всегда должно быть четким и однозначным с указанием всех входящих и возвращаемых типов. Модификатору auto тут не место. При вызове функции — пожалуйста.
Неявного приведения типов в Go нет, потому что это источник сложно находимых ошибок, и это тоже правильно.vintage
20.03.2016 12:21-4Вы всегда можете воспользоваться средствами IDE, чтобы уточнить откуда взялась та или иная функция.
Почему это оно должно? Если возвращаемое значение зависит от входных параметров, то вы не можете заранее указать тип возвращаемого значения — оно определяется в месте вызова функции, а не в месте её определения. Характерный пример — функция add из начала статьи.
Если приведение из uint в int, то да, источник ошибок, но если это приведение из short в int, то никаких ошибок тут быть не может.degs
20.03.2016 17:43+2Не согласен, если вам необходим или хотя бы всерьез нужен IDE чтобы разобраться с кодом, вы пишете уже не на языке а на некоей комбинации язык+IDE. На мой взгляд такой подход — безусловное зло.
То же самое с возвращением auto из функции — да, без него в D никак, но читаемость кода это безусловно ухудшает. Если документация необходима чтобы разобраться с интерфейсом — она становится частью кода, со всеми вытекающими.
Однако в D с этим начинает вырисовываться любопытная концепция — при правильном проектировании вам часто просто не нужно знать детали возвращаемого типа, достаточно знать что это например range, или нечто с именованными полями .x и .y известного типа. Подход совершенно для меня новый и открывает очень интересные варианты, время покажет насколько это изменит стиль программирования.vintage
20.03.2016 18:11Как мне в Go по голому коду получить простой список методов с сигнатурами, не выискивая их по всему файлу вперемешу с реализациями? В стародавние времена эта задача решалась вручную написанием заголовочных файлов. Сейчас так уже никто не делает. Не вижу никакого смысла держаться за компромисы прошлого и не использовать современные инструменты. IDE мне необходима, как минимум, чтобы быстро переходить от места использования к месту определения.
Похожая ситуация — CheckedExceptions в Java. Вместо того, чтобы допилить IDE, чтобы она анализируя исходники могла в любом месте выдавать список возможных в этом месте исключений, авторы языка ввели ручное указание в сигнатуре функции всех исключений, которые может бросить как она сама, так и любая косвенно вызываемая ею функция. Ни к чему хорошему это не привело — кода стало существенно больше, поддерживать его стало сложнее, а заставить программиста думать над тем, что не имеет для него значения, так и не получилось.
Ну, утиной типизации сто лет в обед :-)ZurgInq
20.03.2016 20:58+1Как мне в Go по голому коду получить простой список методов с сигнатурами, не выискивая их по всему файлу вперемешу с реализациями?
Просто к слову. Я бы почти уверен, что для этих целей есть специальная консольная утилита, но не смог найти её. Учитывая, что есть "стандартная" консольная утилита для переименования методов, утилиту для отображения списка методов соорудить на её основе не составит труда.vintage
20.03.2016 21:14О том и речь, только удобнее, когда такие утилиты встроены в IDE.
creker
21.03.2016 00:05+2В мире Go людей учат документировать код, чтобы godoc все эти функции вам сразу показал. Плюс документировать так, чтобы grep или какой другой инструмент поиска запросто нашел нужную функцию. Ну и синтаксис языка помогает — func всегда будет первым слово. Люди в команде Go вон в виме без подсветки синтаксиса рантайм пишут спокойно. Поэтому и Go делали таким, чтобы он работал без огромной IDE. Что только дает плюсы в конечном итоге. IDE действительно работает как бонус, а не необходимость.
mirrr
20.03.2016 13:12-1Для тех, кому лень читать статью:
D совсем как Go, но всё, чего нет в D — всего лишь сахар, он ненужен. Все, чего нет в Go — добавляет выразительности, читаемости и удобства в D, без этого никак. D рулит.timfactory
20.03.2016 14:32Само собой, такие статьи пишутся с большой долей восхищения. Было-бы интересно почитать симметричный ответ от разработчиков на Go.
ewgRa
20.03.2016 16:14Симметричный ответ от маркетологов Go? :)
Тут не может быть нехоливарного ответа, но динамика развития и внедрения Go говорит в его пользу.
Go действительно хорош, как сказал один человек на митапе: "как language энтузиаст я не люблю Go, но как CTO я в восторге". Вот у многих такие чувства.
Go сильно ломает шаблоны, особенно тем кто привык к широким возможностям ООП, и поэтому вызвает отторжение.creker
20.03.2016 17:32Мне Go напоминает эдакий коммерческий продукт. Он создан для решения конкретных бизнес задач. Он не создан для статей, которые восхищаются конструкциям и абстракциями, которые можно написать просто потому что их можно написать. Вот D сейчас пиарят именно в этом ключе — смотрите сколько синтаксических конструкций, сколько всего красивого можно написать. А как Go пиарят — берется конкретный кейс, конкретная реальная задача и просто решается средствами языка. И Rust тоже больше в эту сторону тяготеет, потому что и мозила его создает не для того, чтобы language-гики могли восторгаться, а потому что есть конкретная проблема и ее нужно как-то решить, в чем может помочь новый язык.
degs
20.03.2016 17:57D при близком знакомстве вызывает что-то вроде восхищения (не у всех разумеется), поэтому люди в таком тоне и пишут.
Я всю жизнь писал на C++, иногда, для быстрых набросков и в характерных случаях, там где надо что-нибудь быстренько распарсить и т.д., добавлял Perl. Сейчас я пересматриваю несколько своих домашних проектов и осознаю что обе части — и C++ и Perl, могут быть целиком переписаны на D с той же практически скоростью и эффективностью.
Shchvova
20.03.2016 18:26+3Почему же нету самого интересного?
package main import "fmt" func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum // send sum to c } func main() { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x+y) }
bfDeveloper
20.03.2016 19:42+1void mySum(int[] r, Task tid) { int result = r.sum; tid.send(result); } void example() { auto s = [7, 2, 8, -9, 4, 0]; auto c = Task.getThis; runTask(toDelegate(&mySum), s[0..$/2], c); runTask(toDelegate(&mySum), s[$/2..$], c); int x = receiveOnly!int(); int y = receiveOnly!int(); logInfo("%d %d = %d", x, y, x+y); }
Проект, который можно запустить, здесь.
Это почти полный эквивалент. По крайней мере делает ровно то же самое, и я постарался сделать код максимально похожим, чтобы прослеживались параллели.
Основное отличие — отсутствие канала. Вместо этого посылается сообщение. Полной аналогии типизированных каналов в vibe.d нет, есть две альтернативы:
- Сообщения. Буферизированые в очереди, типизированные, но посылаются в сопрограмму, а не в отдельный объект. Минимальная шаблонная обёртка и это будет полная аналогия каналов
- TaskPipe. Ведёт себя как канал, можно буфферизировать, можно не буфферизировать, но предназначен только для данных. То есть только ubyte[]
Цикл суммирования я тоже убрал, это слишком много бессмысленного кода. Благодаря нормальным шаблонам в D есть нормальные обобщённые алгоритмы. Их не надо писать каждый раз заново, при этом они не теряют в производительности. Это как раз та область, где D нет равных. Go тут тоже нет равных, но в обратном смысле — этой фичи нет вообще и без нормальных шаблонов быть не может.ZurgInq
20.03.2016 20:36Про цикл суммирования. В go его тоже можно убрать: https://play.golang.org/p/N__JPxjUqu
vintage
20.03.2016 20:50Вы его не убрали, а вынесли в отдельную функцию.
ZurgInq
20.03.2016 21:06Если быть точнее, то в метод. Синтаксически получается почти то же самое, вызов метода sum на массиве.
Однако, верно то, что для других типов — float, int64 и т.д. придётся копировать код или городить костыли из рефлексии или типа interface.
creker
21.03.2016 00:08Самое интересное это "select" и то, как в Go устроена кооперативная многозадачность.
merhalak
20.03.2016 20:22Эта статья сработала как антиреклама D, в сравнении с Go.
Go выглядит вылизанным и лаконичным, D — сильно перегруженным. Даже учитывая то, что сейчас осиливаю отнюдь не самый простой ++.vintage
20.03.2016 20:29+2Тут на D реализуются идиомы Go. Если попытаться перенести идиомы D на Go, то всё будет куда хуже.
how
20.03.2016 22:01+2Всё прочитал и мне Go кажется более логичным. Наверное, примеры слишком простые или go-оптимизированные.
Но основное в новом языке — это то, что не знаешь как на нём сделать привычные вещи и ищешь на StackOverflow.
А там Go почти в 7 раз популярнее, чем D (хотя в 60 раз менее популярный, чем PHP и в 70 раз — чем Java).vintage
22.03.2016 15:11не знаешь как на нём сделать привычные вещи и ищешь на StackOverflow
А документация и книги нынче не в тренде?
А там Go почти в 7 раз популярнее, чем D (хотя в 60 раз менее популярный, чем PHP и в 70 раз — чем Java).
Про D, внезапно, обсуждения ведут на forum.dlang.org
kronos
21.03.2016 10:41+1Я не в восторге ни от того, ни от другого, но вот мои мысли:
AliasSeq! очень костыльно смотрится. Ну и жутко бесят отступы от скобочек при перечислении параметров. Зачем они?
Go создал сильно больше Buzz-а, чем D, также, я не знаю ни одного проекта на D.vintage
22.03.2016 15:06AliasSeq! очень костыльно смотрится.
В D вообще не принято возвращать несколько параметров.
Ну и жутко бесят отступы от скобочек при перечислении параметров. Зачем они?
Вы совсем не на том акцентируете своё внимание.
Go создал сильно больше Buzz-а, чем D
А вы попсу слушаете или что-то другое?
я не знаю ни одного проекта на D
http://wiki.dlang.org/Current_D_Use
vechnoe
22.03.2016 13:12У нас несколько десятков микросервисов для разных нужд, написанных на Python. Постепенно приходит понимание, что проще этот зоопарк переписать на Go (хотя бы для удобства деплоя). Не потому что Go такой крутой язык (вот не люблю этой маркетинговой шумихи), просто взял из коробки и оно работает (а все микросервисы написанны в ассинхронном стиле), просто потому что удобно. Но вот какие use case использования D в продакшнене, так и непонято.
xlin
Мне как не программисту Go более понятен и читабелен.