Если вы уже наигрались с Go, устали от копипасты, ручного жонглирования типами и подумываете вернуться на какой-нибудь Python или, прости господи, PHP, то позвольте предложить вам попробовать язык D, где типизация хоть и тоже статическая, но она не путается под ногами и позволяет писать не менее выразительный код, чем на языках с динамической типизацией. А чтобы переход был не такой болезненный, вашему вниманию предлагается перевод Tour of the Go c эквивалентным кодом на D и краткими пояснениями.

Часть первая. Основы.


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 человек.

Ну что, на какой прикладной язык переходим?

Проголосовало 411 человек. Воздержалось 115 человек.

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

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


  1. xlin
    19.03.2016 22:12
    +3

    Мне как не программисту Go более понятен и читабелен.


  1. beduin01
    19.03.2016 23:41

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

    А вот Go… больше всего тянет на хипстерское поделие с непонятными перспективами. В начале куча народу на него бросилось переходить, а потом оказалось, что что-то сложнее микросервисов на нем писать не удобно. Вон тот же DropBox с Go уже на Rust переписывать бросились. Тоже конечно сомнительный в плане продуктивности язык, но судя по тому как на него Сишники переходят свои задач решать позволяет.


    1. 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++, буду всеми способами обходить его стороной в этих задачах. Он не дает никаких преимуществ и возвращает нас в прошлое к колбекам для хоть какой-то асинхронности.


      1. bfDeveloper
        20.03.2016 00:17
        +3

        Про асинхронность не правда. Корутины есть (называются Fiber), кроме того есть целый фреймвёрк для асинхронного io: vibe.d


        1. creker
          20.03.2016 00:28
          +2

          Я не говорил, что там ничего нет. Из того, что я видел, код получается такой же нечитабельный, как и раньше. Fibers, как бы, и раньше были в других языках. Проблема с ними, что пользоваться ими все так же неудобно, как и обычными потоками, плюс у них нет нормального планировщика и D тут не исключение. А для сокетов предлагают всю туже событийную модель и колбеки — нет уж спасибо. Мне этого ужаса уже хватало в прошлом. Спасибо, что пришли Go и C# с асинками, которые дают простой линейный сетевой код, который прекрасно использует конкурентность.


          1. 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#.


            1. cy-ernado
              20.03.2016 08:51

              То есть придется для асинхронной работы искать библиотеки, которые её поддерживают? (как в питоне, там еще с вариациями под разные эвентлупы)


              1. bfDeveloper
                20.03.2016 15:06
                +1

                Fiber в стандартной библиотеке. Vibe.d поддерживает множество ивентлупов (libevent, libev, win32, собственная библиотека libasync). Большинство библиотек для асинхронных операций основываются на vibe.d, он стал почти стандартом, поэтому проблемы совместимости нет. Кроме того модель сопрограм такова, что если функция не выполняет асинхронных действий сама, то ей не требуется какая-то особая поддержка асинхронности. То есть любые синхронные библиотеки отлично работают в асинхронном коде.


                1. vintage
                  20.03.2016 17:47

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


  1. creker
    19.03.2016 23:51
    +7

    Странно. Оригинал пропал с сайта языка. Остались только "C to D" и "C++ to D".

    А так, отсутствие нескольких возвращаемых значений печалит. Ремарки про "ручного жонглирования типами" вообще не понятны, а судя по исходникам рантайма, ничего дельного в плане поддержки конкурентности в рантайме не видно, что еще более печально. Судя по всему, для написания серверного кода Go так и останется более предпочтительным. В общем, все более укрепляется мнение, что D это C++ со словом import. Такой же сложный, напичканный всем чем можно язык, который не имеет четкого назначения. А как мне кажется, без чего-то эдакого он никуда не пробьется. У конкурентов в лице Go и Rust оно есть в полной мере.


    1. vintage
      20.03.2016 00:20
      -7

      А зачем вам несколько возвращаемых значений?

      Ручное жонглирование — это про все эти ручные преобразования типов и копипаста одного и того же для разных типов.

      Что именно вам не хватает для конкурентности?

      Для разработки сервера есть VibeD.


      1. creker
        20.03.2016 00:37
        +5

        Затем, чтобы возвращать несколько значений, не используя костыли в виде массивов и указателей/ссылок. Это удобно.

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

        Конкурентности. D возвращает нас в прошлое, где конкурентность реализована в какой-то убогой библиотеке и вообще всем на нее пофиг, сам как-нить разбирайся. А хочу то, что смогли Go, C# и иже с ними — предельная простота конкуретного кода. Предельная простота работы с сетью. Ты просто пишешь так, как требует того логика. Пришел запрос — сразу его отправил, сразу получил статус отправки, сразу обработал ошибки. Мне не хочется снова пробираться сквозь колбеки и обработчики входящих событий. Пока что я не видел на D простых примеров. Все даже самые примитивные примеры это портянки на весь экран.

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


        1. bfDeveloper
          20.03.2016 00:48
          +1

          "Сложный" пример предлагал чуть выше, а за совсем простыми можете обратиться сюда. Пример оттуда:

          auto conn = connectTCP("time-c.nist.gov", 13);
          logInfo("The time is: %s", conn.readAllUTF8());

          В одной строке подключились, в другой уже читаем. Не знаю, как можно это сделать ещё проще.


          1. creker
            20.03.2016 17:55
            +1

            Это не пример, а просто порнография какая-то, я видел его и не посчитал уместным даже считать примером. Другие примеры были на HTTP, что тем более примитив. Простой пример — это написание маломальски простого протокола обмена между сервером и клиентом на TCP. И мне нужно, чтобы операции с сетью не блокировали все приложение, чтобы код оставался линейным, чтобы были простые механизмы прерывания блокирующих операций по запросу или таймеру.

            Вот к примеру, на C# отправка email сообщения полностью асинхронно на голом TCP без блокировок с таймаутами и отменой влезает в одну небольшую функцию с полностью процедурным кодом. А до этого было уродство с тучей колбеков и необходимостью локов для защиты данных.
            Go — будет еще проще и яснее, потому что весь сетевой стек построен вокруг зеленых потоков. В рантайме выделены отдельные потоки (network poller), которые сидят на select функции и управляют всеми read/write операциями в приложении. Закрой в любом потоке сокет и все заблокированные потоки разблокируются и вернут ошибку. Асинхронность ведь придумана от того, что блокирующие операции требуют использовать слишком много ОС потоков, что делать нельзя. В Go горутины практически бесплатны, а значит и все костыли событийной модели и колбеков не нужны. Можно просто писать код, наконец-то. Наконец-то можно создавать поток на каждое подключение и на каждую фоновую операцию и не бояться, что это сожрет всю память.

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


            1. bfDeveloper
              20.03.2016 18:20

              И вот из того, что я видел

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

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

              Это точно так же можно делать в D. Fiber по легковесности эта та же самая горутина, можете создавать десятками и сотнями тысяч.

              в D предлагают опять эти уродливые события и колбеки

              Я вас не понимаю, честно. Найдите в vibe.d хоть один колбек. Кроме разве что onConnection и onTimer. Но они и инициируются не кодом, а некой третьей стороной, для них нет линейного кода. Так проиходит и в Go и в C#.
              А для чтения, записи, подключения к кому-то нет никаких колбеков или событий. События есть для общения между сопрограммами, но это тот же самый select в Go, только гораздо универсальнее.
              Бывают ситуации, где предлагается альтернатива: колбек или возвращаемое значение. Просто так получилось, что чистые колбеки работают чуть-чуть быстрее и это API оставлено. Но код всё равно можно писать синхронно.
              Всё сделано так чтобы было удобно писать именно линейный код: предоставляются асинхронные линивые диапазоны для чтения/записи, автоматически закрываются соединения при выходе из скоупа и тд, и тп. Чего-чего, а callback hell, это точно не про vibe.d


            1. 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 

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


        1. vintage
          20.03.2016 01:03

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

          Не покажете реальный код? А то у меня без дженериков не получается ничего.


          1. arvitaly
            20.03.2016 09:36
            -7

            ok, countBytes, err := writeTo(...);

            А дженерики — это в любом случае, замедление в real-time, ну или отказ от типобезопасности. Я лично ставлю на кодогенерацию, как, с одной стороны, возможность обобщений, а с другой высокая производительность.


            1. vintage
              20.03.2016 11:45

              Не очень понял, что означает ok, но err в D принято бросать исключением, так что код получится такой, если использовать столь же низкоуровневые средства:

              auto countBytes = core.sys.posix.unistd.write(fd, buffer.ptr, size)

              Или даже такой, при высокоуровневых:

              "output.text".write( "hello!" )

              Есть два варианта реализации дженериков:

              1. Как в C# или Java, где для любых типов реализауется один машинный код. Это даёт малый размер бинарника, но не даёт его толком оптимизировать.
              2. Как в D и C++, где для каждой комбинации типов генерируется свой машинный код. Это даёт максимальную производительность ценой увеличения бинарника.

              Кодогенерация в Go — это фактически менее эффективная реализация второй стратегии.


              1. arvitaly
                20.03.2016 13:21

                Я так понял, речь шла о примере с множественными результатами функции. ok — это признак успешной записи, count — количество записанных байт, err — ошибка. Суть в том, что возможность вернуть множество результатов позволяет совершенно по другому композировать функции (а конкретно не плодить их), но не является священной пулей. И многие оценили по достоинству такую возможность.
                «Кодогенерация в Go — это фактически менее эффективная реализация второй стратегии. „
                Основной принцип go: “явное лучше неявного», здесь все то же самое, вместо кучи сложных настроек компилятора (по сути изучение еще одного языка) — примитивный подход, и это оправдывает себя.


                1. 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".


                  1. arvitaly
                    20.03.2016 18:13
                    -1

                    «Разве отсутствие ошибок не является признаком успешности записи?»
                    Ну вот, а кто-то не хотел тему ошибок поднимать) Другой пример: x, err:= getA() и возвращаемое значение, в x — пустая структура, а в err — ошибки. Кому нужно — игнорирует ошибки, а кого-то не устроит значение по умолчанию.
                    «Функции со множеством возвращаемых значений как раз сложнее композировать. Например, с кортежем я могу написать так:»
                    Не увидел сложности композиции, увидел разное количество кода, причем в примере с go у вас явно игнорируются ошибки (_), т.е. код не равнозначный. И никто не мешает в go точно так же вернуть структуру с полем Total.
                    «Все остальные языки вводят высокоуровневые абстракции, которые инкапсулируют в себе некоторые паттерны, и неявно для прикладного программиста разворачиваются компилятором/рантаймом в машинный код.»
                    Все верно, и чем больше таких абстракций, тем сложнее масштабировать код — выше порог входа, меньше программистов, больше число сочетаний возможных решений. Это хорошо для небольших команд, где можно собраться в одной комнате и объяснить друг другу сложнейшие архитектуры, но плохо для больших. Поэтому, никто и не призывает использовать go всегда и везде, зато, в определенный момент времени, он становится очень удобен.

                    К чему был пример не понял.


                    1. vintage
                      20.03.2016 18:46
                      +1

                      "в примере с go у вас явно игнорируются ошибки (_)"

                      А с чего вы взяли, что там ошибки? :-)

                      "Другой пример: x, err:= getA() и возвращаемое значение, в x — пустая структура, а в err — ошибки. Кому нужно — игнорирует ошибки, а кого-то не устроит значение по умолчанию."

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

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

                      Как раз таки наоборот. JS — весьма абстрактный относительно железа язык, но какой бешенной популярностью он пользуется. А Python, Ruby, PHP в конце концов.

                      Пример был к ущербрости кодогенерации перед лицом метапрограммирования.


                      1. arvitaly
                        20.03.2016 19:04
                        -1

                        «А с чего вы взяли, что там ошибки? :-)»
                        Я все еще думал, мы рассматриваем мой пример. Да и суть не изменилось, в примере с go явно игнорируются два результата функции, т.е. примеры не эквивалентны.
                        «И то, что вы приняли игнорируемое значение за объект ошибки, говорит о том, что других полезный применений множественным возвращаемым значениям нет.»
                        С учетом того, что вы проигнорировали мой пример со множественными значениями, а взяли другой, показывающий возможность возврата одновременно и ошибки и значения — то да. Суть в том, что результатов не обязательно должно быть два и не обязательно одним из результатов является ошибка. В целом, в go точно так же, как и в сильно-функциональных языках удобно работать с I/O на границах, а внутри оперировать чистой моделью без error.
                        Исключения в go тоже есть, там где и должны быть, не для описания логики, а для критических (в пределах данного пакета) ситуаций, когда пакет просто не знает, что с этим делать.

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


              1. igrishaev
                21.03.2016 09:24
                -1

                ok означает признак успеха операции, true/false. Вторая переменная — результат или ошибка. В Гоу любая IO-операция возвращает пару ok, result_or_error.


            1. pav5000
              20.03.2016 13:37
              +1

              Кодогенерация, кстати, в D простая и изящная. И с проверкой при компиляции.


              1. Source
                21.03.2016 11:42

                Самая изящная кодогенерация, на мой вгляд, в Elixir: http://slides.com/chrismccord/elixir-macros


                1. nwalker
                  21.03.2016 19:59

                  Макросы, скажем, clojure еще круче. Макросы Elixir весьма неплохи, но это все же недо-лисп.


          1. mirrr
            20.03.2016 13:33
            +1

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

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


            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

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


              1. mirrr
                20.03.2016 18:20

                Без типов все выглядит красиво) Но даже в таком случае, это читаемее:

                html, ns := log(makeElement("div", date))

                А как это выглядело бы в D?


                1. 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))


                  1. mirrr
                    20.03.2016 21:01

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


                    1. vintage
                      20.03.2016 21:12

                      В том-то и дело, что обычно хватает 2 параметров и 2 возвращаемых значений, только в разных местах разных параметров и разных возвращаемых значений.

                      html, ns := log(makeElement("div", date))
                      parentNode, previousSibling := log(makeElement("lalala", "div", "datepicker"))


        1. timfactory
          20.03.2016 11:33
          +1

          Для возврата несколько значений Александреску советует использовать Tuple из std.typecons.


      1. cy-ernado
        20.03.2016 14:44

        1. bfDeveloper
          20.03.2016 14:57
          +1

          Хорошо у него с производительностью. Сравнивал простые HTTP сервер и клиент с Go, получилось примерно одинакого. Причём масштабируются и Go и D одинакого хорошо. С учётом обработки и всяких парсингов JSON D оказывается быстрее. Есть мнение, что самый быстрый JSON как раз написан на D. Бенчмарк конечно не совсем честный, но точно претендент на лидерство.
          В бенчмарке по вашей ссылке vibe.d есть, правда работал в один поток (была взята старая версия фреймвёрка с досадным багом). PR с нормальной многопоточной версией был отправлен вовремя, но почему-то его не приняли. Ждём следующего запуска, чтобы увидеть правильные результаты.


          1. cy-ernado
            20.03.2016 17:34
            +1

            Теперь понятно, почему такая производительность. Я уже начал думать, что у D совсем все плохо.
            Было бы интересно увидеть JSON в этом же бенчмарке, тогда D должен выбиться в лидеры в "JSON serialization".
            Ждем 13 раунда, там как раз fasthttp с prefork будет.
            Спасибо за ответ


          1. vintage
            20.03.2016 17:41

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


  1. rshadow
    20.03.2016 02:42
    +12

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


    1. dmrt
      22.03.2016 10:36

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


  1. lega
    20.03.2016 07:00
    +1

    Не помню спрашивал у вас или нет, какие преимущества у D перед Nim, Crystal, Julia?


    1. vintage
      20.03.2016 11:54

      К сожалению, не работал с ними. От слова совсем. Так что ничего сказать не могу.


  1. degs
    20.03.2016 09:19

    Забавно, никогда не подумал бы что Go и D похожи.
    D меня сразу зацепил с первого взгляда, а Go смотрел пару лет назад и совершенно он у меня не пошел. А здесь смотришь — код один в один.


    1. vintage
      20.03.2016 11:49

      Это свойство всех C-like языков — базовый синтаксис у всех примерно одинаков. :-)


      1. timfactory
        20.03.2016 11:57

        Разве что только препроцессора не хватает. Магию #ifdef'ов заменили на магию static if, статические конструкторы/деструкторы и т.п.


        1. vintage
          20.03.2016 12:25

          Магия #ifdef — причина очень медленной сборки C++ проектов и трудноуловимых багов, так что молодцы, что заменили. Какой именно функциональности препроцессора вам не хватает?


          1. timfactory
            20.03.2016 14:38

            Нет, тут, скорее, разрыв шаблона «язык для разработки + язык для языка». Поначалу немного непривычно.


  1. dbelka
    20.03.2016 10:28
    +4

    Немного имхо по сравнению языков:

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

    Определение функции всегда должно быть четким и однозначным с указанием всех входящих и возвращаемых типов. Модификатору auto тут не место. При вызове функции — пожалуйста.

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


    1. dbelka
      20.03.2016 10:32
      +1

      А вообще, мне больше Rust нравится :)


    1. vintage
      20.03.2016 12:21
      -4

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

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

      Если приведение из uint в int, то да, источник ошибок, но если это приведение из short в int, то никаких ошибок тут быть не может.


      1. degs
        20.03.2016 17:43
        +2

        Не согласен, если вам необходим или хотя бы всерьез нужен IDE чтобы разобраться с кодом, вы пишете уже не на языке а на некоей комбинации язык+IDE. На мой взгляд такой подход — безусловное зло.
        То же самое с возвращением auto из функции — да, без него в D никак, но читаемость кода это безусловно ухудшает. Если документация необходима чтобы разобраться с интерфейсом — она становится частью кода, со всеми вытекающими.
        Однако в D с этим начинает вырисовываться любопытная концепция — при правильном проектировании вам часто просто не нужно знать детали возвращаемого типа, достаточно знать что это например range, или нечто с именованными полями .x и .y известного типа. Подход совершенно для меня новый и открывает очень интересные варианты, время покажет насколько это изменит стиль программирования.


        1. vintage
          20.03.2016 18:11

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

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

          Ну, утиной типизации сто лет в обед :-)


          1. ZurgInq
            20.03.2016 20:58
            +1

            Как мне в Go по голому коду получить простой список методов с сигнатурами, не выискивая их по всему файлу вперемешу с реализациями?

            Просто к слову. Я бы почти уверен, что для этих целей есть специальная консольная утилита, но не смог найти её. Учитывая, что есть "стандартная" консольная утилита для переименования методов, утилиту для отображения списка методов соорудить на её основе не составит труда.


            1. vintage
              20.03.2016 21:14

              О том и речь, только удобнее, когда такие утилиты встроены в IDE.


              1. creker
                21.03.2016 00:05
                +2

                В мире Go людей учат документировать код, чтобы godoc все эти функции вам сразу показал. Плюс документировать так, чтобы grep или какой другой инструмент поиска запросто нашел нужную функцию. Ну и синтаксис языка помогает — func всегда будет первым слово. Люди в команде Go вон в виме без подсветки синтаксиса рантайм пишут спокойно. Поэтому и Go делали таким, чтобы он работал без огромной IDE. Что только дает плюсы в конечном итоге. IDE действительно работает как бонус, а не необходимость.


  1. mirrr
    20.03.2016 13:12
    -1

    Для тех, кому лень читать статью:
    D совсем как Go, но всё, чего нет в D — всего лишь сахар, он ненужен. Все, чего нет в Go — добавляет выразительности, читаемости и удобства в D, без этого никак. D рулит.


    1. timfactory
      20.03.2016 14:32

      Само собой, такие статьи пишутся с большой долей восхищения. Было-бы интересно почитать симметричный ответ от разработчиков на Go.


      1. ewgRa
        20.03.2016 16:14

        Симметричный ответ от маркетологов Go? :)

        Тут не может быть нехоливарного ответа, но динамика развития и внедрения Go говорит в его пользу.

        Go действительно хорош, как сказал один человек на митапе: "как language энтузиаст я не люблю Go, но как CTO я в восторге". Вот у многих такие чувства.

        Go сильно ломает шаблоны, особенно тем кто привык к широким возможностям ООП, и поэтому вызвает отторжение.


        1. creker
          20.03.2016 17:32

          Мне Go напоминает эдакий коммерческий продукт. Он создан для решения конкретных бизнес задач. Он не создан для статей, которые восхищаются конструкциям и абстракциями, которые можно написать просто потому что их можно написать. Вот D сейчас пиарят именно в этом ключе — смотрите сколько синтаксических конструкций, сколько всего красивого можно написать. А как Go пиарят — берется конкретный кейс, конкретная реальная задача и просто решается средствами языка. И Rust тоже больше в эту сторону тяготеет, потому что и мозила его создает не для того, чтобы language-гики могли восторгаться, а потому что есть конкретная проблема и ее нужно как-то решить, в чем может помочь новый язык.


          1. degs
            20.03.2016 17:57

            D при близком знакомстве вызывает что-то вроде восхищения (не у всех разумеется), поэтому люди в таком тоне и пишут.
            Я всю жизнь писал на C++, иногда, для быстрых набросков и в характерных случаях, там где надо что-нибудь быстренько распарсить и т.д., добавлял Perl. Сейчас я пересматриваю несколько своих домашних проектов и осознаю что обе части — и C++ и Perl, могут быть целиком переписаны на D с той же практически скоростью и эффективностью.


  1. 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)
    }


    1. vintage
      20.03.2016 18:53
      +1

      Это будет в главе по сопрограммы :-)


    1. bfDeveloper
      20.03.2016 19:42
      +1

      void 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 тут тоже нет равных, но в обратном смысле — этой фичи нет вообще и без нормальных шаблонов быть не может.


      1. vintage
        20.03.2016 20:21

        runTask(toDelegate(&mySum), s[0..$/2], c);

        Лучше заменить на:

        runWorkerTask(&mySum, s[0..$/2], c);

        Но конкретно в данном случае проще использовать async.


      1. ZurgInq
        20.03.2016 20:36

        Про цикл суммирования. В go его тоже можно убрать: https://play.golang.org/p/N__JPxjUqu


        1. vintage
          20.03.2016 20:50

          Вы его не убрали, а вынесли в отдельную функцию.


          1. ZurgInq
            20.03.2016 21:06

            Если быть точнее, то в метод. Синтаксически получается почти то же самое, вызов метода sum на массиве.
            Однако, верно то, что для других типов — float, int64 и т.д. придётся копировать код или городить костыли из рефлексии или типа interface.


    1. creker
      21.03.2016 00:08

      Самое интересное это "select" и то, как в Go устроена кооперативная многозадачность.



  1. merhalak
    20.03.2016 20:22

    Эта статья сработала как антиреклама D, в сравнении с Go.

    Go выглядит вылизанным и лаконичным, D — сильно перегруженным. Даже учитывая то, что сейчас осиливаю отнюдь не самый простой ++.


    1. vintage
      20.03.2016 20:29
      +2

      Тут на D реализуются идиомы Go. Если попытаться перенести идиомы D на Go, то всё будет куда хуже.


  1. how
    20.03.2016 22:01
    +2

    Всё прочитал и мне Go кажется более логичным. Наверное, примеры слишком простые или go-оптимизированные.

    Но основное в новом языке — это то, что не знаешь как на нём сделать привычные вещи и ищешь на StackOverflow.
    А там Go почти в 7 раз популярнее, чем D (хотя в 60 раз менее популярный, чем PHP и в 70 раз — чем Java).


    1. vintage
      22.03.2016 15:11

      не знаешь как на нём сделать привычные вещи и ищешь на StackOverflow
      А документация и книги нынче не в тренде?

      А там Go почти в 7 раз популярнее, чем D (хотя в 60 раз менее популярный, чем PHP и в 70 раз — чем Java).
      Про D, внезапно, обсуждения ведут на forum.dlang.org


  1. kronos
    21.03.2016 10:41
    +1

    Я не в восторге ни от того, ни от другого, но вот мои мысли:
    AliasSeq! очень костыльно смотрится. Ну и жутко бесят отступы от скобочек при перечислении параметров. Зачем они?
    Go создал сильно больше Buzz-а, чем D, также, я не знаю ни одного проекта на D.


    1. vintage
      22.03.2016 15:06

      AliasSeq! очень костыльно смотрится.

      В D вообще не принято возвращать несколько параметров.

      Ну и жутко бесят отступы от скобочек при перечислении параметров. Зачем они?

      Вы совсем не на том акцентируете своё внимание.

      Go создал сильно больше Buzz-а, чем D

      А вы попсу слушаете или что-то другое?

      я не знаю ни одного проекта на D

      http://wiki.dlang.org/Current_D_Use


  1. vechnoe
    22.03.2016 13:12

    У нас несколько десятков микросервисов для разных нужд, написанных на Python. Постепенно приходит понимание, что проще этот зоопарк переписать на Go (хотя бы для удобства деплоя). Не потому что Go такой крутой язык (вот не люблю этой маркетинговой шумихи), просто взял из коробки и оно работает (а все микросервисы написанны в ассинхронном стиле), просто потому что удобно. Но вот какие use case использования D в продакшнене, так и непонято.


    1. vintage
      22.03.2016 14:52
      -1

      А что для вас удобство деплоя?
      Всё те же use case, только без копипасты.