Pull to refresh

man!( Go => D ).basics

Reading time6 min
Views16K
Original author: D Community
Если вы уже наигрались с 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;
Only registered users can participate in poll. Log in, please.
Какую часть переводить следующей?
55.74% Управление потоком исполнения68
31.15% Составные типы38
25.41% Методы31
63.93% Сопрограммы78
122 users voted. 100 users abstained.
Only registered users can participate in poll. Log in, please.
Ну что, на какой прикладной язык переходим?
3.29% C15
11.84% C++54
13.6% D62
25.44% Go116
21.49% Rust98
5.7% Java26
9.21% C#42
9.43% Другой43
456 users voted. 128 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 39: ↑24 and ↓15+9
Comments107

Articles